diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..5890462 --- /dev/null +++ b/.htaccess @@ -0,0 +1,23 @@ + + + Options -MultiViews + + + RewriteEngine On + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^(.*)/$ /$1 [L,R=301] + + # Handle Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + + +# php -- BEGIN cPanel-generated handler, do not edit +# Set the “ea-php73” package as the default “PHP” programming language. + + AddHandler application/x-httpd-ea-php73 .php .php7 .phtml + +# php -- END cPanel-generated handler, do not edit diff --git a/assets/css/fonts.css b/assets/css/fonts.css new file mode 100644 index 0000000..64b72a2 --- /dev/null +++ b/assets/css/fonts.css @@ -0,0 +1,9 @@ +@font-face { + font-family: Shabnam; + src: url('../fonts/shabnam/Shabnam.eot'); + src: url('../fonts/shabnam/Shabnam.eot?#iefix') format('embedded-opentype'), + url('../fonts/shabnam/Shabnam.woff2') format('woff2'), + url('../fonts/shabnam/Shabnam.woff') format('woff'), + url('../fonts/shabnam/Shabnam.ttf') format('truetype'); + font-weight: normal; +} \ No newline at end of file diff --git a/assets/css/fonts.min.css b/assets/css/fonts.min.css new file mode 100644 index 0000000..cefb144 --- /dev/null +++ b/assets/css/fonts.min.css @@ -0,0 +1 @@ +@font-face{font-family:Shabnam;src:url('../fonts/shabnam/Shabnam.eot');src:url('../fonts/shabnam/Shabnam.eot?#iefix') format('embedded-opentype'),url('../fonts/shabnam/Shabnam.woff2') format('woff2'),url('../fonts/shabnam/Shabnam.woff') format('woff'),url('../fonts/shabnam/Shabnam.ttf') format('truetype');font-weight:normal} \ No newline at end of file diff --git a/assets/css/rtl.css b/assets/css/rtl.css new file mode 100644 index 0000000..bddfc3d --- /dev/null +++ b/assets/css/rtl.css @@ -0,0 +1,2919 @@ +html, body { + direction: rtl; + text-align: right; +} + +.container { + width: 100%; + padding-left: 15px; + padding-right: 15px; + margin-left: auto; + margin-right: auto; +} + +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +.container-fluid { + width: 100%; + padding-left: 15px; + padding-right: 15px; + margin-left: auto; + margin-right: auto; +} + +.row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-left: -15px; + margin-right: -15px; +} + +.no-gutters { + margin-left: 0; + margin-right: 0; +} + +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-left: 0; + padding-right: 0; +} + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.col-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; +} + +.col-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; +} + +.col-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} + +.col-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +.col-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; +} + +.col-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; +} + +.col-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; +} + +.col-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; +} + +.col-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; +} + +.col-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; +} + +.order-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; +} + +.order-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; +} + +.order-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; +} + +.offset-1 { + margin-right: 8.333333%; +} + +.offset-2 { + margin-right: 16.666667%; +} + +.offset-3 { + margin-right: 25%; +} + +.offset-4 { + margin-right: 33.333333%; +} + +.offset-5 { + margin-right: 41.666667%; +} + +.offset-6 { + margin-right: 50%; +} + +.offset-7 { + margin-right: 58.333333%; +} + +.offset-8 { + margin-right: 66.666667%; +} + +.offset-9 { + margin-right: 75%; +} + +.offset-10 { + margin-right: 83.333333%; +} + +.offset-11 { + margin-right: 91.666667%; +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + + .col-sm-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + + .col-sm-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + + .col-sm-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + + .col-sm-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-sm-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + + .col-sm-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + + .col-sm-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + + .col-sm-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + + .col-sm-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + + .col-sm-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + + .col-sm-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + + .col-sm-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + + .col-sm-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + + .order-sm-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + + .order-sm-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + + .order-sm-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + + .order-sm-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + + .order-sm-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + + .order-sm-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + + .order-sm-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + + .order-sm-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + + .order-sm-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + + .order-sm-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + + .order-sm-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + + .order-sm-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + + .order-sm-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + + .order-sm-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + + .order-sm-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + + .offset-sm-0 { + margin-right: 0; + } + + .offset-sm-1 { + margin-right: 8.333333%; + } + + .offset-sm-2 { + margin-right: 16.666667%; + } + + .offset-sm-3 { + margin-right: 25%; + } + + .offset-sm-4 { + margin-right: 33.333333%; + } + + .offset-sm-5 { + margin-right: 41.666667%; + } + + .offset-sm-6 { + margin-right: 50%; + } + + .offset-sm-7 { + margin-right: 58.333333%; + } + + .offset-sm-8 { + margin-right: 66.666667%; + } + + .offset-sm-9 { + margin-right: 75%; + } + + .offset-sm-10 { + margin-right: 83.333333%; + } + + .offset-sm-11 { + margin-right: 91.666667%; + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + + .col-md-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + + .col-md-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + + .col-md-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + + .col-md-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-md-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + + .col-md-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + + .col-md-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + + .col-md-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + + .col-md-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + + .col-md-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + + .col-md-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + + .col-md-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + + .col-md-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + + .order-md-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + + .order-md-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + + .order-md-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + + .order-md-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + + .order-md-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + + .order-md-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + + .order-md-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + + .order-md-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + + .order-md-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + + .order-md-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + + .order-md-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + + .order-md-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + + .order-md-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + + .order-md-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + + .order-md-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + + .offset-md-0 { + margin-right: 0; + } + + .offset-md-1 { + margin-right: 8.333333%; + } + + .offset-md-2 { + margin-right: 16.666667%; + } + + .offset-md-3 { + margin-right: 25%; + } + + .offset-md-4 { + margin-right: 33.333333%; + } + + .offset-md-5 { + margin-right: 41.666667%; + } + + .offset-md-6 { + margin-right: 50%; + } + + .offset-md-7 { + margin-right: 58.333333%; + } + + .offset-md-8 { + margin-right: 66.666667%; + } + + .offset-md-9 { + margin-right: 75%; + } + + .offset-md-10 { + margin-right: 83.333333%; + } + + .offset-md-11 { + margin-right: 91.666667%; + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + + .col-lg-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + + .col-lg-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + + .col-lg-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + + .col-lg-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-lg-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + + .col-lg-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + + .col-lg-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + + .col-lg-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + + .col-lg-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + + .col-lg-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + + .col-lg-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + + .col-lg-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + + .col-lg-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + + .order-lg-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + + .order-lg-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + + .order-lg-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + + .order-lg-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + + .order-lg-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + + .order-lg-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + + .order-lg-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + + .order-lg-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + + .order-lg-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + + .order-lg-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + + .order-lg-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + + .order-lg-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + + .order-lg-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + + .order-lg-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + + .order-lg-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + + .offset-lg-0 { + margin-right: 0; + } + + .offset-lg-1 { + margin-right: 8.333333%; + } + + .offset-lg-2 { + margin-right: 16.666667%; + } + + .offset-lg-3 { + margin-right: 25%; + } + + .offset-lg-4 { + margin-right: 33.333333%; + } + + .offset-lg-5 { + margin-right: 41.666667%; + } + + .offset-lg-6 { + margin-right: 50%; + } + + .offset-lg-7 { + margin-right: 58.333333%; + } + + .offset-lg-8 { + margin-right: 66.666667%; + } + + .offset-lg-9 { + margin-right: 75%; + } + + .offset-lg-10 { + margin-right: 83.333333%; + } + + .offset-lg-11 { + margin-right: 91.666667%; + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + + .col-xl-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + + .col-xl-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + + .col-xl-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + + .col-xl-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-xl-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + + .col-xl-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + + .col-xl-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + + .col-xl-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + + .col-xl-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + + .col-xl-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + + .col-xl-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + + .col-xl-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + + .col-xl-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + + .order-xl-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + + .order-xl-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + + .order-xl-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + + .order-xl-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + + .order-xl-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + + .order-xl-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + + .order-xl-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + + .order-xl-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + + .order-xl-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + + .order-xl-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + + .order-xl-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + + .order-xl-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + + .order-xl-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + + .order-xl-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + + .order-xl-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + + .offset-xl-0 { + margin-right: 0; + } + + .offset-xl-1 { + margin-right: 8.333333%; + } + + .offset-xl-2 { + margin-right: 16.666667%; + } + + .offset-xl-3 { + margin-right: 25%; + } + + .offset-xl-4 { + margin-right: 33.333333%; + } + + .offset-xl-5 { + margin-right: 41.666667%; + } + + .offset-xl-6 { + margin-right: 50%; + } + + .offset-xl-7 { + margin-right: 58.333333%; + } + + .offset-xl-8 { + margin-right: 66.666667%; + } + + .offset-xl-9 { + margin-right: 75%; + } + + .offset-xl-10 { + margin-right: 83.333333%; + } + + .offset-xl-11 { + margin-right: 91.666667%; + } +} + +.ml-auto, +.mx-auto { + margin-left: unset !important; + margin-right: auto !important; +} + + +.w-25 { + width: 25% !important; +} + +.w-50 { + width: 50% !important; +} + +.w-75 { + width: 75% !important; +} + +.w-100 { + width: 100% !important; +} + +.h-25 { + height: 25% !important; +} + +.h-50 { + height: 50% !important; +} + +.h-75 { + height: 75% !important; +} + +.h-100 { + height: 100% !important; +} + +.mw-100 { + max-width: 100% !important; +} + +.mh-100 { + max-height: 100% !important; +} + +.m-0 { + margin: 0 !important; +} + +.mt-0, +.my-0 { + margin-top: 0 !important; +} + +.mr-0, +.mx-0 { + margin-left: 0 !important; +} + +.mb-0, +.my-0 { + margin-bottom: 0 !important; +} + +.ml-0, +.mx-0 { + margin-right: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.mt-1, +.my-1 { + margin-top: 0.25rem !important; +} + +.mr-1, +.mx-1 { + margin-left: 0.25rem !important; +} + +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; +} + +.ml-1, +.mx-1 { + margin-right: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.mt-2, +.my-2 { + margin-top: 0.5rem !important; +} + +.mr-2, +.mx-2 { + margin-left: 0.5rem !important; +} + +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; +} + +.ml-2, +.mx-2 { + margin-right: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.mt-3, +.my-3 { + margin-top: 1rem !important; +} + +.mr-3, +.mx-3 { + margin-left: 1rem !important; +} + +.mb-3, +.my-3 { + margin-bottom: 1rem !important; +} + +.ml-3, +.mx-3 { + margin-right: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.mt-4, +.my-4 { + margin-top: 1.5rem !important; +} + +.mr-4, +.mx-4 { + margin-left: 1.5rem !important; +} + +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; +} + +.ml-4, +.mx-4 { + margin-right: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.mt-5, +.my-5 { + margin-top: 3rem !important; +} + +.mr-5, +.mx-5 { + margin-left: 3rem !important; +} + +.mb-5, +.my-5 { + margin-bottom: 3rem !important; +} + +.ml-5, +.mx-5 { + margin-right: 3rem !important; +} + +.p-0 { + padding: 0 !important; +} + +.pt-0, +.py-0 { + padding-top: 0 !important; +} + +.pr-0, +.px-0 { + padding-left: 0 !important; +} + +.pb-0, +.py-0 { + padding-bottom: 0 !important; +} + +.pl-0, +.px-0 { + padding-right: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.pt-1, +.py-1 { + padding-top: 0.25rem !important; +} + +.pr-1, +.px-1 { + padding-left: 0.25rem !important; +} + +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; +} + +.pl-1, +.px-1 { + padding-right: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.pt-2, +.py-2 { + padding-top: 0.5rem !important; +} + +.pr-2, +.px-2 { + padding-left: 0.5rem !important; +} + +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; +} + +.pl-2, +.px-2 { + padding-right: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.pt-3, +.py-3 { + padding-top: 1rem !important; +} + +.pr-3, +.px-3 { + padding-left: 1rem !important; +} + +.pb-3, +.py-3 { + padding-bottom: 1rem !important; +} + +.pl-3, +.px-3 { + padding-right: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.pt-4, +.py-4 { + padding-top: 1.5rem !important; +} + +.pr-4, +.px-4 { + padding-left: 1.5rem !important; +} + +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; +} + +.pl-4, +.px-4 { + padding-right: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.pt-5, +.py-5 { + padding-top: 3rem !important; +} + +.pr-5, +.px-5 { + padding-left: 3rem !important; +} + +.pb-5, +.py-5 { + padding-bottom: 3rem !important; +} + +.pl-5, +.px-5 { + padding-right: 3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mt-auto, +.my-auto { + margin-top: auto !important; +} + +.mr-auto, +.mx-auto { + margin-left: auto !important; +} + +.mb-auto, +.my-auto { + margin-bottom: auto !important; +} + +.ml-auto, +.mx-auto { + margin-right: auto !important; +} + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; + } + + .mr-sm-0, + .mx-sm-0 { + margin-left: 0 !important; + } + + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; + } + + .ml-sm-0, + .mx-sm-0 { + margin-right: 0 !important; + } + + .m-sm-1 { + margin: 0.25rem !important; + } + + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; + } + + .mr-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; + } + + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; + } + + .ml-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; + } + + .m-sm-2 { + margin: 0.5rem !important; + } + + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; + } + + .mr-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; + } + + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; + } + + .ml-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; + } + + .m-sm-3 { + margin: 1rem !important; + } + + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; + } + + .mr-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; + } + + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; + } + + .ml-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; + } + + .m-sm-4 { + margin: 1.5rem !important; + } + + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; + } + + .mr-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; + } + + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; + } + + .ml-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; + } + + .m-sm-5 { + margin: 3rem !important; + } + + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; + } + + .mr-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; + } + + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; + } + + .ml-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; + } + + .p-sm-0 { + padding: 0 !important; + } + + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; + } + + .pr-sm-0, + .px-sm-0 { + padding-left: 0 !important; + } + + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; + } + + .pl-sm-0, + .px-sm-0 { + padding-right: 0 !important; + } + + .p-sm-1 { + padding: 0.25rem !important; + } + + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; + } + + .pr-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; + } + + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; + } + + .pl-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; + } + + .p-sm-2 { + padding: 0.5rem !important; + } + + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; + } + + .pr-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; + } + + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; + } + + .pl-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; + } + + .p-sm-3 { + padding: 1rem !important; + } + + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; + } + + .pr-sm-3, + .px-sm-3 { + padding-left: 1rem !important; + } + + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; + } + + .pl-sm-3, + .px-sm-3 { + padding-right: 1rem !important; + } + + .p-sm-4 { + padding: 1.5rem !important; + } + + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; + } + + .pr-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; + } + + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; + } + + .pl-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; + } + + .p-sm-5 { + padding: 3rem !important; + } + + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; + } + + .pr-sm-5, + .px-sm-5 { + padding-left: 3rem !important; + } + + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; + } + + .pl-sm-5, + .px-sm-5 { + padding-right: 3rem !important; + } + + .m-sm-auto { + margin: auto !important; + } + + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; + } + + .mr-sm-auto, + .mx-sm-auto { + margin-left: auto !important; + } + + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; + } + + .ml-sm-auto, + .mx-sm-auto { + margin-right: auto !important; + } +} + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; + } + + .mr-md-0, + .mx-md-0 { + margin-left: 0 !important; + } + + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; + } + + .ml-md-0, + .mx-md-0 { + margin-right: 0 !important; + } + + .m-md-1 { + margin: 0.25rem !important; + } + + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; + } + + .mr-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; + } + + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; + } + + .ml-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; + } + + .m-md-2 { + margin: 0.5rem !important; + } + + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; + } + + .mr-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; + } + + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; + } + + .ml-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; + } + + .m-md-3 { + margin: 1rem !important; + } + + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; + } + + .mr-md-3, + .mx-md-3 { + margin-left: 1rem !important; + } + + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; + } + + .ml-md-3, + .mx-md-3 { + margin-right: 1rem !important; + } + + .m-md-4 { + margin: 1.5rem !important; + } + + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; + } + + .mr-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; + } + + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; + } + + .ml-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; + } + + .m-md-5 { + margin: 3rem !important; + } + + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; + } + + .mr-md-5, + .mx-md-5 { + margin-left: 3rem !important; + } + + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; + } + + .ml-md-5, + .mx-md-5 { + margin-right: 3rem !important; + } + + .p-md-0 { + padding: 0 !important; + } + + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; + } + + .pr-md-0, + .px-md-0 { + padding-left: 0 !important; + } + + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; + } + + .pl-md-0, + .px-md-0 { + padding-right: 0 !important; + } + + .p-md-1 { + padding: 0.25rem !important; + } + + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; + } + + .pr-md-1, + .px-md-1 { + padding-left: 0.25rem !important; + } + + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; + } + + .pl-md-1, + .px-md-1 { + padding-right: 0.25rem !important; + } + + .p-md-2 { + padding: 0.5rem !important; + } + + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; + } + + .pr-md-2, + .px-md-2 { + padding-left: 0.5rem !important; + } + + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; + } + + .pl-md-2, + .px-md-2 { + padding-right: 0.5rem !important; + } + + .p-md-3 { + padding: 1rem !important; + } + + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; + } + + .pr-md-3, + .px-md-3 { + padding-left: 1rem !important; + } + + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; + } + + .pl-md-3, + .px-md-3 { + padding-right: 1rem !important; + } + + .p-md-4 { + padding: 1.5rem !important; + } + + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; + } + + .pr-md-4, + .px-md-4 { + padding-left: 1.5rem !important; + } + + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; + } + + .pl-md-4, + .px-md-4 { + padding-right: 1.5rem !important; + } + + .p-md-5 { + padding: 3rem !important; + } + + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; + } + + .pr-md-5, + .px-md-5 { + padding-left: 3rem !important; + } + + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; + } + + .pl-md-5, + .px-md-5 { + padding-right: 3rem !important; + } + + .m-md-auto { + margin: auto !important; + } + + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; + } + + .mr-md-auto, + .mx-md-auto { + margin-left: auto !important; + } + + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; + } + + .ml-md-auto, + .mx-md-auto { + margin-right: auto !important; + } +} + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; + } + + .mr-lg-0, + .mx-lg-0 { + margin-left: 0 !important; + } + + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; + } + + .ml-lg-0, + .mx-lg-0 { + margin-right: 0 !important; + } + + .m-lg-1 { + margin: 0.25rem !important; + } + + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; + } + + .mr-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; + } + + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; + } + + .ml-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; + } + + .m-lg-2 { + margin: 0.5rem !important; + } + + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; + } + + .mr-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; + } + + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; + } + + .ml-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; + } + + .m-lg-3 { + margin: 1rem !important; + } + + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; + } + + .mr-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; + } + + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; + } + + .ml-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; + } + + .m-lg-4 { + margin: 1.5rem !important; + } + + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; + } + + .mr-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; + } + + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; + } + + .ml-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; + } + + .m-lg-5 { + margin: 3rem !important; + } + + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; + } + + .mr-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; + } + + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; + } + + .ml-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; + } + + .p-lg-0 { + padding: 0 !important; + } + + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; + } + + .pr-lg-0, + .px-lg-0 { + padding-left: 0 !important; + } + + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; + } + + .pl-lg-0, + .px-lg-0 { + padding-right: 0 !important; + } + + .p-lg-1 { + padding: 0.25rem !important; + } + + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; + } + + .pr-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; + } + + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; + } + + .pl-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; + } + + .p-lg-2 { + padding: 0.5rem !important; + } + + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; + } + + .pr-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; + } + + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; + } + + .pl-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; + } + + .p-lg-3 { + padding: 1rem !important; + } + + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; + } + + .pr-lg-3, + .px-lg-3 { + padding-left: 1rem !important; + } + + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; + } + + .pl-lg-3, + .px-lg-3 { + padding-right: 1rem !important; + } + + .p-lg-4 { + padding: 1.5rem !important; + } + + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; + } + + .pr-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; + } + + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; + } + + .pl-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; + } + + .p-lg-5 { + padding: 3rem !important; + } + + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; + } + + .pr-lg-5, + .px-lg-5 { + padding-left: 3rem !important; + } + + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; + } + + .pl-lg-5, + .px-lg-5 { + padding-right: 3rem !important; + } + + .m-lg-auto { + margin: auto !important; + } + + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; + } + + .mr-lg-auto, + .mx-lg-auto { + margin-left: auto !important; + } + + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; + } + + .ml-lg-auto, + .mx-lg-auto { + margin-right: auto !important; + } +} + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; + } + + .mr-xl-0, + .mx-xl-0 { + margin-left: 0 !important; + } + + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; + } + + .ml-xl-0, + .mx-xl-0 { + margin-right: 0 !important; + } + + .m-xl-1 { + margin: 0.25rem !important; + } + + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; + } + + .mr-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; + } + + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; + } + + .ml-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; + } + + .m-xl-2 { + margin: 0.5rem !important; + } + + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; + } + + .mr-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; + } + + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; + } + + .ml-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; + } + + .m-xl-3 { + margin: 1rem !important; + } + + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; + } + + .mr-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; + } + + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; + } + + .ml-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; + } + + .m-xl-4 { + margin: 1.5rem !important; + } + + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; + } + + .mr-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; + } + + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; + } + + .ml-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; + } + + .m-xl-5 { + margin: 3rem !important; + } + + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; + } + + .mr-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; + } + + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; + } + + .ml-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; + } + + .p-xl-0 { + padding: 0 !important; + } + + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; + } + + .pr-xl-0, + .px-xl-0 { + padding-left: 0 !important; + } + + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; + } + + .pl-xl-0, + .px-xl-0 { + padding-right: 0 !important; + } + + .p-xl-1 { + padding: 0.25rem !important; + } + + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; + } + + .pr-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; + } + + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; + } + + .pl-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; + } + + .p-xl-2 { + padding: 0.5rem !important; + } + + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; + } + + .pr-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; + } + + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; + } + + .pl-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; + } + + .p-xl-3 { + padding: 1rem !important; + } + + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; + } + + .pr-xl-3, + .px-xl-3 { + padding-left: 1rem !important; + } + + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; + } + + .pl-xl-3, + .px-xl-3 { + padding-right: 1rem !important; + } + + .p-xl-4 { + padding: 1.5rem !important; + } + + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; + } + + .pr-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; + } + + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; + } + + .pl-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; + } + + .p-xl-5 { + padding: 3rem !important; + } + + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; + } + + .pr-xl-5, + .px-xl-5 { + padding-left: 3rem !important; + } + + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; + } + + .pl-xl-5, + .px-xl-5 { + padding-right: 3rem !important; + } + + .m-xl-auto { + margin: auto !important; + } + + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; + } + + .mr-xl-auto, + .mx-xl-auto { + margin-left: auto !important; + } + + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; + } + + .ml-xl-auto, + .mx-xl-auto { + margin-right: auto !important; + } +} + +.navbar-nav { + padding-right: 0; +} + +.dropdown-item { + text-align: right; +} + +.form-check { + padding-left: unset; + padding-right: 1.25rem; +} + +.form-check-input { + margin-left: unset; + margin-right: -1.25rem; +} + +.input-group input, .input-group label { + border-radius: 0; +} + +.navbar-brand { + margin-left: 1rem;; + margin-right: unset; +} \ No newline at end of file diff --git a/assets/css/rtl.min.css b/assets/css/rtl.min.css new file mode 100644 index 0000000..877bc02 --- /dev/null +++ b/assets/css/rtl.min.css @@ -0,0 +1 @@ +html,body{direction:rtl;text-align:right}.container{width:100%;padding-left:15px;padding-right:15px;margin-left:auto;margin-right:auto}@media(min-width:576px){.container{max-width:540px}}@media(min-width:768px){.container{max-width:720px}}@media(min-width:992px){.container{max-width:960px}}@media(min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-left:15px;padding-right:15px;margin-left:auto;margin-right:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.no-gutters{margin-left:0;margin-right:0}.no-gutters>.col,.no-gutters>[class*="col-"]{padding-left:0;padding-right:0}.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col,.col-auto,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm,.col-sm-auto,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md,.col-md-auto,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg,.col-lg-auto,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-left:15px;padding-right:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-right:8.333333%}.offset-2{margin-right:16.666667%}.offset-3{margin-right:25%}.offset-4{margin-right:33.333333%}.offset-5{margin-right:41.666667%}.offset-6{margin-right:50%}.offset-7{margin-right:58.333333%}.offset-8{margin-right:66.666667%}.offset-9{margin-right:75%}.offset-10{margin-right:83.333333%}.offset-11{margin-right:91.666667%}@media(min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-right:0}.offset-sm-1{margin-right:8.333333%}.offset-sm-2{margin-right:16.666667%}.offset-sm-3{margin-right:25%}.offset-sm-4{margin-right:33.333333%}.offset-sm-5{margin-right:41.666667%}.offset-sm-6{margin-right:50%}.offset-sm-7{margin-right:58.333333%}.offset-sm-8{margin-right:66.666667%}.offset-sm-9{margin-right:75%}.offset-sm-10{margin-right:83.333333%}.offset-sm-11{margin-right:91.666667%}}@media(min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-right:0}.offset-md-1{margin-right:8.333333%}.offset-md-2{margin-right:16.666667%}.offset-md-3{margin-right:25%}.offset-md-4{margin-right:33.333333%}.offset-md-5{margin-right:41.666667%}.offset-md-6{margin-right:50%}.offset-md-7{margin-right:58.333333%}.offset-md-8{margin-right:66.666667%}.offset-md-9{margin-right:75%}.offset-md-10{margin-right:83.333333%}.offset-md-11{margin-right:91.666667%}}@media(min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-right:0}.offset-lg-1{margin-right:8.333333%}.offset-lg-2{margin-right:16.666667%}.offset-lg-3{margin-right:25%}.offset-lg-4{margin-right:33.333333%}.offset-lg-5{margin-right:41.666667%}.offset-lg-6{margin-right:50%}.offset-lg-7{margin-right:58.333333%}.offset-lg-8{margin-right:66.666667%}.offset-lg-9{margin-right:75%}.offset-lg-10{margin-right:83.333333%}.offset-lg-11{margin-right:91.666667%}}@media(min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-right:0}.offset-xl-1{margin-right:8.333333%}.offset-xl-2{margin-right:16.666667%}.offset-xl-3{margin-right:25%}.offset-xl-4{margin-right:33.333333%}.offset-xl-5{margin-right:41.666667%}.offset-xl-6{margin-right:50%}.offset-xl-7{margin-right:58.333333%}.offset-xl-8{margin-right:66.666667%}.offset-xl-9{margin-right:75%}.offset-xl-10{margin-right:83.333333%}.offset-xl-11{margin-right:91.666667%}}.ml-auto,.mx-auto{margin-left:unset !important;margin-right:auto !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-left:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-right:0 !important}.m-1{margin:.25rem !important}.mt-1,.my-1{margin-top:.25rem !important}.mr-1,.mx-1{margin-left:.25rem !important}.mb-1,.my-1{margin-bottom:.25rem !important}.ml-1,.mx-1{margin-right:.25rem !important}.m-2{margin:.5rem !important}.mt-2,.my-2{margin-top:.5rem !important}.mr-2,.mx-2{margin-left:.5rem !important}.mb-2,.my-2{margin-bottom:.5rem !important}.ml-2,.mx-2{margin-right:.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-left:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-right:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-left:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-right:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-left:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-right:3rem !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-left:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-right:0 !important}.p-1{padding:.25rem !important}.pt-1,.py-1{padding-top:.25rem !important}.pr-1,.px-1{padding-left:.25rem !important}.pb-1,.py-1{padding-bottom:.25rem !important}.pl-1,.px-1{padding-right:.25rem !important}.p-2{padding:.5rem !important}.pt-2,.py-2{padding-top:.5rem !important}.pr-2,.px-2{padding-left:.5rem !important}.pb-2,.py-2{padding-bottom:.5rem !important}.pl-2,.px-2{padding-right:.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-left:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-right:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-left:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-right:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-left:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-right:3rem !important}.m-auto{margin:auto !important}.mt-auto,.my-auto{margin-top:auto !important}.mr-auto,.mx-auto{margin-left:auto !important}.mb-auto,.my-auto{margin-bottom:auto !important}.ml-auto,.mx-auto{margin-right:auto !important}@media(min-width:576px){.m-sm-0{margin:0 !important}.mt-sm-0,.my-sm-0{margin-top:0 !important}.mr-sm-0,.mx-sm-0{margin-left:0 !important}.mb-sm-0,.my-sm-0{margin-bottom:0 !important}.ml-sm-0,.mx-sm-0{margin-right:0 !important}.m-sm-1{margin:.25rem !important}.mt-sm-1,.my-sm-1{margin-top:.25rem !important}.mr-sm-1,.mx-sm-1{margin-left:.25rem !important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem !important}.ml-sm-1,.mx-sm-1{margin-right:.25rem !important}.m-sm-2{margin:.5rem !important}.mt-sm-2,.my-sm-2{margin-top:.5rem !important}.mr-sm-2,.mx-sm-2{margin-left:.5rem !important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem !important}.ml-sm-2,.mx-sm-2{margin-right:.5rem !important}.m-sm-3{margin:1rem !important}.mt-sm-3,.my-sm-3{margin-top:1rem !important}.mr-sm-3,.mx-sm-3{margin-left:1rem !important}.mb-sm-3,.my-sm-3{margin-bottom:1rem !important}.ml-sm-3,.mx-sm-3{margin-right:1rem !important}.m-sm-4{margin:1.5rem !important}.mt-sm-4,.my-sm-4{margin-top:1.5rem !important}.mr-sm-4,.mx-sm-4{margin-left:1.5rem !important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem !important}.ml-sm-4,.mx-sm-4{margin-right:1.5rem !important}.m-sm-5{margin:3rem !important}.mt-sm-5,.my-sm-5{margin-top:3rem !important}.mr-sm-5,.mx-sm-5{margin-left:3rem !important}.mb-sm-5,.my-sm-5{margin-bottom:3rem !important}.ml-sm-5,.mx-sm-5{margin-right:3rem !important}.p-sm-0{padding:0 !important}.pt-sm-0,.py-sm-0{padding-top:0 !important}.pr-sm-0,.px-sm-0{padding-left:0 !important}.pb-sm-0,.py-sm-0{padding-bottom:0 !important}.pl-sm-0,.px-sm-0{padding-right:0 !important}.p-sm-1{padding:.25rem !important}.pt-sm-1,.py-sm-1{padding-top:.25rem !important}.pr-sm-1,.px-sm-1{padding-left:.25rem !important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem !important}.pl-sm-1,.px-sm-1{padding-right:.25rem !important}.p-sm-2{padding:.5rem !important}.pt-sm-2,.py-sm-2{padding-top:.5rem !important}.pr-sm-2,.px-sm-2{padding-left:.5rem !important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem !important}.pl-sm-2,.px-sm-2{padding-right:.5rem !important}.p-sm-3{padding:1rem !important}.pt-sm-3,.py-sm-3{padding-top:1rem !important}.pr-sm-3,.px-sm-3{padding-left:1rem !important}.pb-sm-3,.py-sm-3{padding-bottom:1rem !important}.pl-sm-3,.px-sm-3{padding-right:1rem !important}.p-sm-4{padding:1.5rem !important}.pt-sm-4,.py-sm-4{padding-top:1.5rem !important}.pr-sm-4,.px-sm-4{padding-left:1.5rem !important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem !important}.pl-sm-4,.px-sm-4{padding-right:1.5rem !important}.p-sm-5{padding:3rem !important}.pt-sm-5,.py-sm-5{padding-top:3rem !important}.pr-sm-5,.px-sm-5{padding-left:3rem !important}.pb-sm-5,.py-sm-5{padding-bottom:3rem !important}.pl-sm-5,.px-sm-5{padding-right:3rem !important}.m-sm-auto{margin:auto !important}.mt-sm-auto,.my-sm-auto{margin-top:auto !important}.mr-sm-auto,.mx-sm-auto{margin-left:auto !important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto !important}.ml-sm-auto,.mx-sm-auto{margin-right:auto !important}}@media(min-width:768px){.m-md-0{margin:0 !important}.mt-md-0,.my-md-0{margin-top:0 !important}.mr-md-0,.mx-md-0{margin-left:0 !important}.mb-md-0,.my-md-0{margin-bottom:0 !important}.ml-md-0,.mx-md-0{margin-right:0 !important}.m-md-1{margin:.25rem !important}.mt-md-1,.my-md-1{margin-top:.25rem !important}.mr-md-1,.mx-md-1{margin-left:.25rem !important}.mb-md-1,.my-md-1{margin-bottom:.25rem !important}.ml-md-1,.mx-md-1{margin-right:.25rem !important}.m-md-2{margin:.5rem !important}.mt-md-2,.my-md-2{margin-top:.5rem !important}.mr-md-2,.mx-md-2{margin-left:.5rem !important}.mb-md-2,.my-md-2{margin-bottom:.5rem !important}.ml-md-2,.mx-md-2{margin-right:.5rem !important}.m-md-3{margin:1rem !important}.mt-md-3,.my-md-3{margin-top:1rem !important}.mr-md-3,.mx-md-3{margin-left:1rem !important}.mb-md-3,.my-md-3{margin-bottom:1rem !important}.ml-md-3,.mx-md-3{margin-right:1rem !important}.m-md-4{margin:1.5rem !important}.mt-md-4,.my-md-4{margin-top:1.5rem !important}.mr-md-4,.mx-md-4{margin-left:1.5rem !important}.mb-md-4,.my-md-4{margin-bottom:1.5rem !important}.ml-md-4,.mx-md-4{margin-right:1.5rem !important}.m-md-5{margin:3rem !important}.mt-md-5,.my-md-5{margin-top:3rem !important}.mr-md-5,.mx-md-5{margin-left:3rem !important}.mb-md-5,.my-md-5{margin-bottom:3rem !important}.ml-md-5,.mx-md-5{margin-right:3rem !important}.p-md-0{padding:0 !important}.pt-md-0,.py-md-0{padding-top:0 !important}.pr-md-0,.px-md-0{padding-left:0 !important}.pb-md-0,.py-md-0{padding-bottom:0 !important}.pl-md-0,.px-md-0{padding-right:0 !important}.p-md-1{padding:.25rem !important}.pt-md-1,.py-md-1{padding-top:.25rem !important}.pr-md-1,.px-md-1{padding-left:.25rem !important}.pb-md-1,.py-md-1{padding-bottom:.25rem !important}.pl-md-1,.px-md-1{padding-right:.25rem !important}.p-md-2{padding:.5rem !important}.pt-md-2,.py-md-2{padding-top:.5rem !important}.pr-md-2,.px-md-2{padding-left:.5rem !important}.pb-md-2,.py-md-2{padding-bottom:.5rem !important}.pl-md-2,.px-md-2{padding-right:.5rem !important}.p-md-3{padding:1rem !important}.pt-md-3,.py-md-3{padding-top:1rem !important}.pr-md-3,.px-md-3{padding-left:1rem !important}.pb-md-3,.py-md-3{padding-bottom:1rem !important}.pl-md-3,.px-md-3{padding-right:1rem !important}.p-md-4{padding:1.5rem !important}.pt-md-4,.py-md-4{padding-top:1.5rem !important}.pr-md-4,.px-md-4{padding-left:1.5rem !important}.pb-md-4,.py-md-4{padding-bottom:1.5rem !important}.pl-md-4,.px-md-4{padding-right:1.5rem !important}.p-md-5{padding:3rem !important}.pt-md-5,.py-md-5{padding-top:3rem !important}.pr-md-5,.px-md-5{padding-left:3rem !important}.pb-md-5,.py-md-5{padding-bottom:3rem !important}.pl-md-5,.px-md-5{padding-right:3rem !important}.m-md-auto{margin:auto !important}.mt-md-auto,.my-md-auto{margin-top:auto !important}.mr-md-auto,.mx-md-auto{margin-left:auto !important}.mb-md-auto,.my-md-auto{margin-bottom:auto !important}.ml-md-auto,.mx-md-auto{margin-right:auto !important}}@media(min-width:992px){.m-lg-0{margin:0 !important}.mt-lg-0,.my-lg-0{margin-top:0 !important}.mr-lg-0,.mx-lg-0{margin-left:0 !important}.mb-lg-0,.my-lg-0{margin-bottom:0 !important}.ml-lg-0,.mx-lg-0{margin-right:0 !important}.m-lg-1{margin:.25rem !important}.mt-lg-1,.my-lg-1{margin-top:.25rem !important}.mr-lg-1,.mx-lg-1{margin-left:.25rem !important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem !important}.ml-lg-1,.mx-lg-1{margin-right:.25rem !important}.m-lg-2{margin:.5rem !important}.mt-lg-2,.my-lg-2{margin-top:.5rem !important}.mr-lg-2,.mx-lg-2{margin-left:.5rem !important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem !important}.ml-lg-2,.mx-lg-2{margin-right:.5rem !important}.m-lg-3{margin:1rem !important}.mt-lg-3,.my-lg-3{margin-top:1rem !important}.mr-lg-3,.mx-lg-3{margin-left:1rem !important}.mb-lg-3,.my-lg-3{margin-bottom:1rem !important}.ml-lg-3,.mx-lg-3{margin-right:1rem !important}.m-lg-4{margin:1.5rem !important}.mt-lg-4,.my-lg-4{margin-top:1.5rem !important}.mr-lg-4,.mx-lg-4{margin-left:1.5rem !important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem !important}.ml-lg-4,.mx-lg-4{margin-right:1.5rem !important}.m-lg-5{margin:3rem !important}.mt-lg-5,.my-lg-5{margin-top:3rem !important}.mr-lg-5,.mx-lg-5{margin-left:3rem !important}.mb-lg-5,.my-lg-5{margin-bottom:3rem !important}.ml-lg-5,.mx-lg-5{margin-right:3rem !important}.p-lg-0{padding:0 !important}.pt-lg-0,.py-lg-0{padding-top:0 !important}.pr-lg-0,.px-lg-0{padding-left:0 !important}.pb-lg-0,.py-lg-0{padding-bottom:0 !important}.pl-lg-0,.px-lg-0{padding-right:0 !important}.p-lg-1{padding:.25rem !important}.pt-lg-1,.py-lg-1{padding-top:.25rem !important}.pr-lg-1,.px-lg-1{padding-left:.25rem !important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem !important}.pl-lg-1,.px-lg-1{padding-right:.25rem !important}.p-lg-2{padding:.5rem !important}.pt-lg-2,.py-lg-2{padding-top:.5rem !important}.pr-lg-2,.px-lg-2{padding-left:.5rem !important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem !important}.pl-lg-2,.px-lg-2{padding-right:.5rem !important}.p-lg-3{padding:1rem !important}.pt-lg-3,.py-lg-3{padding-top:1rem !important}.pr-lg-3,.px-lg-3{padding-left:1rem !important}.pb-lg-3,.py-lg-3{padding-bottom:1rem !important}.pl-lg-3,.px-lg-3{padding-right:1rem !important}.p-lg-4{padding:1.5rem !important}.pt-lg-4,.py-lg-4{padding-top:1.5rem !important}.pr-lg-4,.px-lg-4{padding-left:1.5rem !important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem !important}.pl-lg-4,.px-lg-4{padding-right:1.5rem !important}.p-lg-5{padding:3rem !important}.pt-lg-5,.py-lg-5{padding-top:3rem !important}.pr-lg-5,.px-lg-5{padding-left:3rem !important}.pb-lg-5,.py-lg-5{padding-bottom:3rem !important}.pl-lg-5,.px-lg-5{padding-right:3rem !important}.m-lg-auto{margin:auto !important}.mt-lg-auto,.my-lg-auto{margin-top:auto !important}.mr-lg-auto,.mx-lg-auto{margin-left:auto !important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto !important}.ml-lg-auto,.mx-lg-auto{margin-right:auto !important}}@media(min-width:1200px){.m-xl-0{margin:0 !important}.mt-xl-0,.my-xl-0{margin-top:0 !important}.mr-xl-0,.mx-xl-0{margin-left:0 !important}.mb-xl-0,.my-xl-0{margin-bottom:0 !important}.ml-xl-0,.mx-xl-0{margin-right:0 !important}.m-xl-1{margin:.25rem !important}.mt-xl-1,.my-xl-1{margin-top:.25rem !important}.mr-xl-1,.mx-xl-1{margin-left:.25rem !important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem !important}.ml-xl-1,.mx-xl-1{margin-right:.25rem !important}.m-xl-2{margin:.5rem !important}.mt-xl-2,.my-xl-2{margin-top:.5rem !important}.mr-xl-2,.mx-xl-2{margin-left:.5rem !important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem !important}.ml-xl-2,.mx-xl-2{margin-right:.5rem !important}.m-xl-3{margin:1rem !important}.mt-xl-3,.my-xl-3{margin-top:1rem !important}.mr-xl-3,.mx-xl-3{margin-left:1rem !important}.mb-xl-3,.my-xl-3{margin-bottom:1rem !important}.ml-xl-3,.mx-xl-3{margin-right:1rem !important}.m-xl-4{margin:1.5rem !important}.mt-xl-4,.my-xl-4{margin-top:1.5rem !important}.mr-xl-4,.mx-xl-4{margin-left:1.5rem !important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem !important}.ml-xl-4,.mx-xl-4{margin-right:1.5rem !important}.m-xl-5{margin:3rem !important}.mt-xl-5,.my-xl-5{margin-top:3rem !important}.mr-xl-5,.mx-xl-5{margin-left:3rem !important}.mb-xl-5,.my-xl-5{margin-bottom:3rem !important}.ml-xl-5,.mx-xl-5{margin-right:3rem !important}.p-xl-0{padding:0 !important}.pt-xl-0,.py-xl-0{padding-top:0 !important}.pr-xl-0,.px-xl-0{padding-left:0 !important}.pb-xl-0,.py-xl-0{padding-bottom:0 !important}.pl-xl-0,.px-xl-0{padding-right:0 !important}.p-xl-1{padding:.25rem !important}.pt-xl-1,.py-xl-1{padding-top:.25rem !important}.pr-xl-1,.px-xl-1{padding-left:.25rem !important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem !important}.pl-xl-1,.px-xl-1{padding-right:.25rem !important}.p-xl-2{padding:.5rem !important}.pt-xl-2,.py-xl-2{padding-top:.5rem !important}.pr-xl-2,.px-xl-2{padding-left:.5rem !important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem !important}.pl-xl-2,.px-xl-2{padding-right:.5rem !important}.p-xl-3{padding:1rem !important}.pt-xl-3,.py-xl-3{padding-top:1rem !important}.pr-xl-3,.px-xl-3{padding-left:1rem !important}.pb-xl-3,.py-xl-3{padding-bottom:1rem !important}.pl-xl-3,.px-xl-3{padding-right:1rem !important}.p-xl-4{padding:1.5rem !important}.pt-xl-4,.py-xl-4{padding-top:1.5rem !important}.pr-xl-4,.px-xl-4{padding-left:1.5rem !important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem !important}.pl-xl-4,.px-xl-4{padding-right:1.5rem !important}.p-xl-5{padding:3rem !important}.pt-xl-5,.py-xl-5{padding-top:3rem !important}.pr-xl-5,.px-xl-5{padding-left:3rem !important}.pb-xl-5,.py-xl-5{padding-bottom:3rem !important}.pl-xl-5,.px-xl-5{padding-right:3rem !important}.m-xl-auto{margin:auto !important}.mt-xl-auto,.my-xl-auto{margin-top:auto !important}.mr-xl-auto,.mx-xl-auto{margin-left:auto !important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto !important}.ml-xl-auto,.mx-xl-auto{margin-right:auto !important}}.navbar-nav{padding-right:0}.dropdown-item{text-align:right}.form-check{padding-left:unset;padding-right:1.25rem}.form-check-input{margin-left:unset;margin-right:-1.25rem}.input-group input,.input-group label{border-radius:0}.navbar-brand{margin-left:1rem;margin-right:unset} \ No newline at end of file diff --git a/assets/css/styles.css b/assets/css/styles.css new file mode 100644 index 0000000..b4ef830 --- /dev/null +++ b/assets/css/styles.css @@ -0,0 +1,25 @@ +@import "../libs/bootstrap/css/bootstrap.min.css"; +@import "rtl.min.css"; +@import "fonts.min.css"; + +body { + font-family: Shabnam, sans-serif; + background-color: #f9f9f9 +} + +.ltr { + direction: ltr; + text-align: left; +} + +.table td, .table th { + vertical-align: middle +} + +.card-header .btn { + padding: .05rem .5rem; +} + +.list-group-item:active, .list-group-item:focus{ + background-color: #2e283e; +} \ No newline at end of file diff --git a/assets/css/styles.min.css b/assets/css/styles.min.css new file mode 100644 index 0000000..a147e20 --- /dev/null +++ b/assets/css/styles.min.css @@ -0,0 +1 @@ +@import "../libs/bootstrap/css/bootstrap.min.css";@import "rtl.min.css";@import "fonts.min.css";body{font-family:Shabnam,sans-serif;background-color:#f9f9f9}.ltr{direction:ltr;text-align:left}.table td,.table th{vertical-align:middle}.card-header .btn{padding:.05rem .5rem}.list-group-item:active,.list-group-item:focus{background-color:#2e283e} \ No newline at end of file diff --git a/assets/fonts/shabnam/Shabnam.eot b/assets/fonts/shabnam/Shabnam.eot new file mode 100644 index 0000000..eef68aa Binary files /dev/null and b/assets/fonts/shabnam/Shabnam.eot differ diff --git a/assets/fonts/shabnam/Shabnam.ttf b/assets/fonts/shabnam/Shabnam.ttf new file mode 100644 index 0000000..09cb4ab Binary files /dev/null and b/assets/fonts/shabnam/Shabnam.ttf differ diff --git a/assets/fonts/shabnam/Shabnam.woff b/assets/fonts/shabnam/Shabnam.woff new file mode 100644 index 0000000..7639e2f Binary files /dev/null and b/assets/fonts/shabnam/Shabnam.woff differ diff --git a/assets/fonts/shabnam/Shabnam.woff2 b/assets/fonts/shabnam/Shabnam.woff2 new file mode 100644 index 0000000..89be64f Binary files /dev/null and b/assets/fonts/shabnam/Shabnam.woff2 differ diff --git a/assets/img/no-screenshot.png b/assets/img/no-screenshot.png new file mode 100644 index 0000000..7037301 Binary files /dev/null and b/assets/img/no-screenshot.png differ diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..cb84758 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1 @@ +!function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=11)}([function(e,t,n){"use strict";var r=n(5),i=n(19),o=Object.prototype.toString;function a(e){return"[object Array]"===o.call(e)}function s(e){return null!==e&&"object"==typeof e}function u(e){return"[object Function]"===o.call(e)}function c(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),a(e))for(var n=0,r=e.length;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],function(e){u.headers[e]={}}),r.forEach(["post","put","patch"],function(e){u.headers[e]=r.merge(o)}),e.exports=u}).call(t,n(6))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){for(var n="undefined"!=typeof window&&"undefined"!=typeof document,r=["Edge","Trident","Firefox"],i=0,o=0;o=0){i=1;break}var a=n&&window.Promise?function(e){var t=!1;return function(){t||(t=!0,window.Promise.resolve().then(function(){t=!1,e()}))}}:function(e){var t=!1;return function(){t||(t=!0,setTimeout(function(){t=!1,e()},i))}};function s(e){return e&&"[object Function]"==={}.toString.call(e)}function u(e,t){if(1!==e.nodeType)return[];var n=getComputedStyle(e,null);return t?n[t]:n}function c(e){return"HTML"===e.nodeName?e:e.parentNode||e.host}function l(e){if(!e)return document.body;switch(e.nodeName){case"HTML":case"BODY":return e.ownerDocument.body;case"#document":return e.body}var t=u(e),n=t.overflow,r=t.overflowX,i=t.overflowY;return/(auto|scroll|overlay)/.test(n+i+r)?e:l(c(e))}var f=n&&!(!window.MSInputMethodContext||!document.documentMode),p=n&&/MSIE 10/.test(navigator.userAgent);function d(e){return 11===e?f:10===e?p:f||p}function h(e){if(!e)return document.documentElement;for(var t=d(10)?document.body:null,n=e.offsetParent;n===t&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var r=n&&n.nodeName;return r&&"BODY"!==r&&"HTML"!==r?-1!==["TD","TABLE"].indexOf(n.nodeName)&&"static"===u(n,"position")?h(n):n:e?e.ownerDocument.documentElement:document.documentElement}function v(e){return null!==e.parentNode?v(e.parentNode):e}function g(e,t){if(!(e&&e.nodeType&&t&&t.nodeType))return document.documentElement;var n=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,r=n?e:t,i=n?t:e,o=document.createRange();o.setStart(r,0),o.setEnd(i,0);var a,s,u=o.commonAncestorContainer;if(e!==u&&t!==u||r.contains(i))return"BODY"===(s=(a=u).nodeName)||"HTML"!==s&&h(a.firstElementChild)!==a?h(u):u;var c=v(e);return c.host?g(c.host,t):g(e,v(t).host)}function m(e){var t="top"===(arguments.length>1&&void 0!==arguments[1]?arguments[1]:"top")?"scrollTop":"scrollLeft",n=e.nodeName;if("BODY"===n||"HTML"===n){var r=e.ownerDocument.documentElement;return(e.ownerDocument.scrollingElement||r)[t]}return e[t]}function y(e,t){var n="x"===t?"Left":"Top",r="Left"===n?"Right":"Bottom";return parseFloat(e["border"+n+"Width"],10)+parseFloat(e["border"+r+"Width"],10)}function _(e,t,n,r){return Math.max(t["offset"+e],t["scroll"+e],n["client"+e],n["offset"+e],n["scroll"+e],d(10)?n["offset"+e]+r["margin"+("Height"===e?"Top":"Left")]+r["margin"+("Height"===e?"Bottom":"Right")]:0)}function b(){var e=document.body,t=document.documentElement,n=d(10)&&getComputedStyle(t);return{height:_("Height",e,t,n),width:_("Width",e,t,n)}}var w=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},x=function(){function e(e,t){for(var n=0;n2&&void 0!==arguments[2]&&arguments[2],r=d(10),i="HTML"===t.nodeName,o=A(e),a=A(t),s=l(e),c=u(t),f=parseFloat(c.borderTopWidth,10),p=parseFloat(c.borderLeftWidth,10);n&&"HTML"===t.nodeName&&(a.top=Math.max(a.top,0),a.left=Math.max(a.left,0));var h=T({top:o.top-a.top-f,left:o.left-a.left-p,width:o.width,height:o.height});if(h.marginTop=0,h.marginLeft=0,!r&&i){var v=parseFloat(c.marginTop,10),g=parseFloat(c.marginLeft,10);h.top-=f-v,h.bottom-=f-v,h.left-=p-g,h.right-=p-g,h.marginTop=v,h.marginLeft=g}return(r&&!n?t.contains(s):t===s&&"BODY"!==s.nodeName)&&(h=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=m(t,"top"),i=m(t,"left"),o=n?-1:1;return e.top+=r*o,e.bottom+=r*o,e.left+=i*o,e.right+=i*o,e}(h,t)),h}function k(e){if(!e||!e.parentElement||d())return document.documentElement;for(var t=e.parentElement;t&&"none"===u(t,"transform");)t=t.parentElement;return t||document.documentElement}function O(e,t,n,r){var i=arguments.length>4&&void 0!==arguments[4]&&arguments[4],o={top:0,left:0},a=i?k(e):g(e,t);if("viewport"===r)o=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=e.ownerDocument.documentElement,r=S(e,n),i=Math.max(n.clientWidth,window.innerWidth||0),o=Math.max(n.clientHeight,window.innerHeight||0),a=t?0:m(n),s=t?0:m(n,"left");return T({top:a-r.top+r.marginTop,left:s-r.left+r.marginLeft,width:i,height:o})}(a,i);else{var s=void 0;"scrollParent"===r?"BODY"===(s=l(c(t))).nodeName&&(s=e.ownerDocument.documentElement):s="window"===r?e.ownerDocument.documentElement:r;var f=S(s,a,i);if("HTML"!==s.nodeName||function e(t){var n=t.nodeName;return"BODY"!==n&&"HTML"!==n&&("fixed"===u(t,"position")||e(c(t)))}(a))o=f;else{var p=b(),d=p.height,h=p.width;o.top+=f.top-f.marginTop,o.bottom=d+f.top,o.left+=f.left-f.marginLeft,o.right=h+f.left}}return o.left+=n,o.top+=n,o.right-=n,o.bottom-=n,o}function D(e,t,n,r,i){var o=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0;if(-1===e.indexOf("auto"))return e;var a=O(n,r,o,i),s={top:{width:a.width,height:t.top-a.top},right:{width:a.right-t.right,height:a.height},bottom:{width:a.width,height:a.bottom-t.bottom},left:{width:t.left-a.left,height:a.height}},u=Object.keys(s).map(function(e){return E({key:e},s[e],{area:(t=s[e],t.width*t.height)});var t}).sort(function(e,t){return t.area-e.area}),c=u.filter(function(e){var t=e.width,r=e.height;return t>=n.clientWidth&&r>=n.clientHeight}),l=c.length>0?c[0].key:u[0].key,f=e.split("-")[1];return l+(f?"-"+f:"")}function I(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;return S(n,r?k(t):g(t,n),r)}function N(e){var t=getComputedStyle(e),n=parseFloat(t.marginTop)+parseFloat(t.marginBottom),r=parseFloat(t.marginLeft)+parseFloat(t.marginRight);return{width:e.offsetWidth+r,height:e.offsetHeight+n}}function j(e){var t={left:"right",right:"left",bottom:"top",top:"bottom"};return e.replace(/left|right|bottom|top/g,function(e){return t[e]})}function L(e,t,n){n=n.split("-")[0];var r=N(e),i={width:r.width,height:r.height},o=-1!==["right","left"].indexOf(n),a=o?"top":"left",s=o?"left":"top",u=o?"height":"width",c=o?"width":"height";return i[a]=t[a]+t[u]/2-r[u]/2,i[s]=n===s?t[s]-r[c]:t[j(s)],i}function $(e,t){return Array.prototype.find?e.find(t):e.filter(t)[0]}function P(e,t,n){return(void 0===n?e:e.slice(0,function(e,t,n){if(Array.prototype.findIndex)return e.findIndex(function(e){return e[t]===n});var r=$(e,function(e){return e[t]===n});return e.indexOf(r)}(e,"name",n))).forEach(function(e){e.function&&console.warn("`modifier.function` is deprecated, use `modifier.fn`!");var n=e.function||e.fn;e.enabled&&s(n)&&(t.offsets.popper=T(t.offsets.popper),t.offsets.reference=T(t.offsets.reference),t=n(t,e))}),t}function R(e,t){return e.some(function(e){var n=e.name;return e.enabled&&n===t})}function M(e){for(var t=[!1,"ms","Webkit","Moz","O"],n=e.charAt(0).toUpperCase()+e.slice(1),r=0;r1&&void 0!==arguments[1]&&arguments[1],n=V.indexOf(e),r=V.slice(n+1).concat(V.slice(0,n));return t?r.reverse():r}var Q={FLIP:"flip",CLOCKWISE:"clockwise",COUNTERCLOCKWISE:"counterclockwise"};function Y(e,t,n,r){var i=[0,0],o=-1!==["right","left"].indexOf(r),a=e.split(/(\+|\-)/).map(function(e){return e.trim()}),s=a.indexOf($(a,function(e){return-1!==e.search(/,|\s/)}));a[s]&&-1===a[s].indexOf(",")&&console.warn("Offsets separated by white space(s) are deprecated, use a comma (,) instead.");var u=/\s*,\s*|\s+/,c=-1!==s?[a.slice(0,s).concat([a[s].split(u)[0]]),[a[s].split(u)[1]].concat(a.slice(s+1))]:[a];return(c=c.map(function(e,r){var i=(1===r?!o:o)?"height":"width",a=!1;return e.reduce(function(e,t){return""===e[e.length-1]&&-1!==["+","-"].indexOf(t)?(e[e.length-1]=t,a=!0,e):a?(e[e.length-1]+=t,a=!1,e):e.concat(t)},[]).map(function(e){return function(e,t,n,r){var i=e.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),o=+i[1],a=i[2];if(!o)return e;if(0===a.indexOf("%")){var s=void 0;switch(a){case"%p":s=n;break;case"%":case"%r":default:s=r}return T(s)[t]/100*o}if("vh"===a||"vw"===a)return("vh"===a?Math.max(document.documentElement.clientHeight,window.innerHeight||0):Math.max(document.documentElement.clientWidth,window.innerWidth||0))/100*o;return o}(e,i,t,n)})})).forEach(function(e,t){e.forEach(function(n,r){B(n)&&(i[t]+=n*("-"===e[r-1]?-1:1))})}),i}var X={placement:"bottom",positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(e){var t=e.placement,n=t.split("-")[0],r=t.split("-")[1];if(r){var i=e.offsets,o=i.reference,a=i.popper,s=-1!==["bottom","top"].indexOf(n),u=s?"left":"top",c=s?"width":"height",l={start:C({},u,o[u]),end:C({},u,o[u]+o[c]-a[c])};e.offsets.popper=E({},a,l[r])}return e}},offset:{order:200,enabled:!0,fn:function(e,t){var n=t.offset,r=e.placement,i=e.offsets,o=i.popper,a=i.reference,s=r.split("-")[0],u=void 0;return u=B(+n)?[+n,0]:Y(n,o,a,s),"left"===s?(o.top+=u[0],o.left-=u[1]):"right"===s?(o.top+=u[0],o.left+=u[1]):"top"===s?(o.left+=u[0],o.top-=u[1]):"bottom"===s&&(o.left+=u[0],o.top+=u[1]),e.popper=o,e},offset:0},preventOverflow:{order:300,enabled:!0,fn:function(e,t){var n=t.boundariesElement||h(e.instance.popper);e.instance.reference===n&&(n=h(n));var r=M("transform"),i=e.instance.popper.style,o=i.top,a=i.left,s=i[r];i.top="",i.left="",i[r]="";var u=O(e.instance.popper,e.instance.reference,t.padding,n,e.positionFixed);i.top=o,i.left=a,i[r]=s,t.boundaries=u;var c=t.priority,l=e.offsets.popper,f={primary:function(e){var n=l[e];return l[e]u[e]&&!t.escapeWithReference&&(r=Math.min(l[n],u[e]-("right"===e?l.width:l.height))),C({},n,r)}};return c.forEach(function(e){var t=-1!==["left","top"].indexOf(e)?"primary":"secondary";l=E({},l,f[t](e))}),e.offsets.popper=l,e},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,n=t.popper,r=t.reference,i=e.placement.split("-")[0],o=Math.floor,a=-1!==["top","bottom"].indexOf(i),s=a?"right":"bottom",u=a?"left":"top",c=a?"width":"height";return n[s]o(r[s])&&(e.offsets.popper[u]=o(r[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,t){var n;if(!U(e.instance.modifiers,"arrow","keepTogether"))return e;var r=t.element;if("string"==typeof r){if(!(r=e.instance.popper.querySelector(r)))return e}else if(!e.instance.popper.contains(r))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),e;var i=e.placement.split("-")[0],o=e.offsets,a=o.popper,s=o.reference,c=-1!==["left","right"].indexOf(i),l=c?"height":"width",f=c?"Top":"Left",p=f.toLowerCase(),d=c?"left":"top",h=c?"bottom":"right",v=N(r)[l];s[h]-va[h]&&(e.offsets.popper[p]+=s[p]+v-a[h]),e.offsets.popper=T(e.offsets.popper);var g=s[p]+s[l]/2-v/2,m=u(e.instance.popper),y=parseFloat(m["margin"+f],10),_=parseFloat(m["border"+f+"Width"],10),b=g-e.offsets.popper[p]-y-_;return b=Math.max(Math.min(a[l]-v,b),0),e.arrowElement=r,e.offsets.arrow=(C(n={},p,Math.round(b)),C(n,d,""),n),e},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(e,t){if(R(e.instance.modifiers,"inner"))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var n=O(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),r=e.placement.split("-")[0],i=j(r),o=e.placement.split("-")[1]||"",a=[];switch(t.behavior){case Q.FLIP:a=[r,i];break;case Q.CLOCKWISE:a=K(r);break;case Q.COUNTERCLOCKWISE:a=K(r,!0);break;default:a=t.behavior}return a.forEach(function(s,u){if(r!==s||a.length===u+1)return e;r=e.placement.split("-")[0],i=j(r);var c=e.offsets.popper,l=e.offsets.reference,f=Math.floor,p="left"===r&&f(c.right)>f(l.left)||"right"===r&&f(c.left)f(l.top)||"bottom"===r&&f(c.top)f(n.right),v=f(c.top)f(n.bottom),m="left"===r&&d||"right"===r&&h||"top"===r&&v||"bottom"===r&&g,y=-1!==["top","bottom"].indexOf(r),_=!!t.flipVariations&&(y&&"start"===o&&d||y&&"end"===o&&h||!y&&"start"===o&&v||!y&&"end"===o&&g);(p||m||_)&&(e.flipped=!0,(p||m)&&(r=a[u+1]),_&&(o=function(e){return"end"===e?"start":"start"===e?"end":e}(o)),e.placement=r+(o?"-"+o:""),e.offsets.popper=E({},e.offsets.popper,L(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,"flip"))}),e},behavior:"flip",padding:5,boundariesElement:"viewport"},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,n=t.split("-")[0],r=e.offsets,i=r.popper,o=r.reference,a=-1!==["left","right"].indexOf(n),s=-1===["top","left"].indexOf(n);return i[a?"left":"top"]=o[n]-(s?i[a?"width":"height"]:0),e.placement=j(t),e.offsets.popper=T(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!U(e.instance.modifiers,"hide","preventOverflow"))return e;var t=e.offsets.reference,n=$(e.instance.modifiers,function(e){return"preventOverflow"===e.name}).boundaries;if(t.bottomn.right||t.top>n.bottom||t.right2&&void 0!==arguments[2]?arguments[2]:{};w(this,e),this.scheduleUpdate=function(){return requestAnimationFrame(r.update)},this.update=a(this.update.bind(this)),this.options=E({},e.Defaults,i),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=t&&t.jquery?t[0]:t,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(E({},e.Defaults.modifiers,i.modifiers)).forEach(function(t){r.options.modifiers[t]=E({},e.Defaults.modifiers[t]||{},i.modifiers?i.modifiers[t]:{})}),this.modifiers=Object.keys(this.options.modifiers).map(function(e){return E({name:e},r.options.modifiers[e])}).sort(function(e,t){return e.order-t.order}),this.modifiers.forEach(function(e){e.enabled&&s(e.onLoad)&&e.onLoad(r.reference,r.popper,r.options,e,r.state)}),this.update();var o=this.options.eventsEnabled;o&&this.enableEventListeners(),this.state.eventsEnabled=o}return x(e,[{key:"update",value:function(){return function(){if(!this.state.isDestroyed){var e={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};e.offsets.reference=I(this.state,this.popper,this.reference,this.options.positionFixed),e.placement=D(this.options.placement,e.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),e.originalPlacement=e.placement,e.positionFixed=this.options.positionFixed,e.offsets.popper=L(this.popper,e.offsets.reference,e.placement),e.offsets.popper.position=this.options.positionFixed?"fixed":"absolute",e=P(this.modifiers,e),this.state.isCreated?this.options.onUpdate(e):(this.state.isCreated=!0,this.options.onCreate(e))}}.call(this)}},{key:"destroy",value:function(){return function(){return this.state.isDestroyed=!0,R(this.modifiers,"applyStyle")&&(this.popper.removeAttribute("x-placement"),this.popper.style.position="",this.popper.style.top="",this.popper.style.left="",this.popper.style.right="",this.popper.style.bottom="",this.popper.style.willChange="",this.popper.style[M("transform")]=""),this.disableEventListeners(),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}.call(this)}},{key:"enableEventListeners",value:function(){return function(){this.state.eventsEnabled||(this.state=F(this.reference,this.options,this.state,this.scheduleUpdate))}.call(this)}},{key:"disableEventListeners",value:function(){return q.call(this)}}]),e}();G.Utils=("undefined"!=typeof window?window:e).PopperUtils,G.placements=z,G.Defaults=X,t.default=G}.call(t,n(1))},function(e,t,n){var r;!function(t,n){"use strict";"object"==typeof e&&"object"==typeof e.exports?e.exports=t.document?n(t,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return n(e)}:n(t)}("undefined"!=typeof window?window:this,function(n,i){"use strict";var o=[],a=n.document,s=Object.getPrototypeOf,u=o.slice,c=o.concat,l=o.push,f=o.indexOf,p={},d=p.toString,h=p.hasOwnProperty,v=h.toString,g=v.call(Object),m={},y=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},_=function(e){return null!=e&&e===e.window},b={type:!0,src:!0,noModule:!0};function w(e,t,n){var r,i=(t=t||a).createElement("script");if(i.text=e,n)for(r in b)n[r]&&(i[r]=n[r]);t.head.appendChild(i).parentNode.removeChild(i)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?p[d.call(e)]||"object":typeof e}var C=function(e,t){return new C.fn.init(e,t)},E=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function T(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!y(e)&&!_(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}C.fn=C.prototype={jquery:"3.3.1",constructor:C,length:0,toArray:function(){return u.call(this)},get:function(e){return null==e?u.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=C.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return C.each(this,e)},map:function(e){return this.pushStack(C.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(u.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n+~]|"+P+")"+P+"*"),U=new RegExp("="+P+"*([^\\]'\"]*?)"+P+"*\\]","g"),z=new RegExp(H),V=new RegExp("^"+R+"$"),K={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+H),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:new RegExp("^(?:"+$+")$","i"),needsContext:new RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},Q=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,X=/^[^{]+\{\s*\[native \w/,G=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,J=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=ye(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{N.apply(O=j.call(w.childNodes),w.childNodes),O[w.childNodes.length].nodeType}catch(e){N={apply:O.length?function(e,t){I.apply(e,j.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}function oe(e,t,r,i){var o,s,c,l,f,h,m,y=t&&t.ownerDocument,x=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==x&&9!==x&&11!==x)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,v)){if(11!==x&&(f=G.exec(e)))if(o=f[1]){if(9===x){if(!(c=t.getElementById(o)))return r;if(c.id===o)return r.push(c),r}else if(y&&(c=y.getElementById(o))&&_(t,c)&&c.id===o)return r.push(c),r}else{if(f[2])return N.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return N.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!A[e+" "]&&(!g||!g.test(e))){if(1!==x)y=t,m=e;else if("object"!==t.nodeName.toLowerCase()){for((l=t.getAttribute("id"))?l=l.replace(te,ne):t.setAttribute("id",l=b),s=(h=a(e)).length;s--;)h[s]="#"+l+" "+me(h[s]);m=h.join(","),y=J.test(e)&&ve(t.parentNode)||t}if(m)try{return N.apply(r,y.querySelectorAll(m)),r}catch(e){}finally{l===b&&t.removeAttribute("id")}}}return u(e.replace(q,"$1"),t,r,i)}function ae(){var e=[];return function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ce(e,t){for(var n=e.split("|"),i=n.length;i--;)r.attrHandle[n[i]]=t}function le(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ve(e){return e&&void 0!==e.getElementsByTagName&&e}for(t in n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(h=(d=a).documentElement,v=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=X.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if(void 0!==t.getElementById&&v){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if(void 0!==t.getElementById&&v){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];for(i=t.getElementsByName(e),r=0;o=i[r++];)if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&v)return t.getElementsByClassName(e)},m=[],g=[],(n.qsa=X.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+$+")"),e.querySelectorAll("[id~="+b+"-]").length||g.push("~="),e.querySelectorAll(":checked").length||g.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||g.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&g.push("name"+P+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&g.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(n.matchesSelector=X.test(y=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=y.call(e,"*"),y.call(e,"[s!='']:x"),m.push("!=",H)}),g=g.length&&new RegExp(g.join("|")),m=m.length&&new RegExp(m.join("|")),t=X.test(h.compareDocumentPosition),_=t||X.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},S=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&_(w,e)?-1:t===d||t.ownerDocument===w&&_(w,t)?1:l?L(l,e)-L(l,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:l?L(l,e)-L(l,t):0;if(i===o)return le(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)s.unshift(n);for(;a[r]===s[r];)r++;return r?le(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(U,"='$1']"),n.matchesSelector&&v&&!A[t+" "]&&(!m||!m.test(t))&&(!g||!g.test(t)))try{var r=y.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),_(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&k.call(r.attrHandle,t.toLowerCase())?i(e,t,!v):void 0;return void 0!==o?o:n.attributes||!v?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),f){for(;t=e[o++];)t===e[o]&&(i=r.push(o));for(;i--;)e.splice(r[i],1)}return l=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r++];)n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:K,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return K.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&z.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace(F," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var c,l,f,p,d,h,v=o!==a?"nextSibling":"previousSibling",g=t.parentNode,m=s&&t.nodeName.toLowerCase(),y=!u&&!s,_=!1;if(g){if(o){for(;v;){for(p=t;p=p[v];)if(s?p.nodeName.toLowerCase()===m:1===p.nodeType)return!1;h=v="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?g.firstChild:g.lastChild],a&&y){for(_=(d=(c=(l=(f=(p=g)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===x&&c[1])&&c[2],p=d&&g.childNodes[d];p=++d&&p&&p[v]||(_=d=0)||h.pop();)if(1===p.nodeType&&++_&&p===t){l[e]=[x,d,_];break}}else if(y&&(_=d=(c=(l=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===x&&c[1]),!1===_)for(;(p=++d&&p&&p[v]||(_=d=0)||h.pop())&&((s?p.nodeName.toLowerCase()!==m:1!==p.nodeType)||!++_||(y&&((l=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[x,_]),p!==t)););return(_-=i)===r||_%r==0&&_/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){for(var r,o=i(e,t),a=o.length;a--;)e[r=L(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(q,"$1"));return r[b]?se(function(e,t,n,i){for(var o,a=r(e,null,i,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return V.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=v?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,c=null!=t;s-1&&(o[c]=!(a[c]=f))}}else m=be(m===a?m.splice(h,m.length):m),i?i(null,a,m,u):N.apply(a,m)})}function xe(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,l=ye(function(e){return e===t},s,!0),f=ye(function(e){return L(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==c)||((t=n).nodeType?l(e,n,r):f(e,n,r));return t=null,i}];u1&&_e(p),u>1&&me(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(q,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,l){var f,h,g,m=0,y="0",_=o&&[],b=[],w=c,C=o||i&&r.find.TAG("*",l),E=x+=null==w?1:Math.random()||.1,T=C.length;for(l&&(c=a===d||a||l);y!==T&&null!=(f=C[y]);y++){if(i&&f){for(h=0,a||f.ownerDocument===d||(p(f),s=!v);g=e[h++];)if(g(f,a||d,s)){u.push(f);break}l&&(x=E)}n&&((f=!g&&f)&&m--,o&&_.push(f))}if(m+=y,n&&y!==m){for(h=0;g=t[h++];)g(_,b,a,s);if(o){if(m>0)for(;y--;)_[y]||b[y]||(b[y]=D.call(u));b=be(b)}N.apply(u,b),l&&!o&&b.length>0&&m+t.length>1&&oe.uniqueSort(u)}return l&&(x=E,c=w),_};return n?se(o):o}(o,i))).selector=e}return s},u=oe.select=function(e,t,n,i){var o,u,c,l,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(c=u[0]).type&&9===t.nodeType&&v&&r.relative[u[1].type]){if(!(t=(r.find.ID(c.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}for(o=K.needsContext.test(e)?0:u.length;o--&&(c=u[o],!r.relative[l=c.type]);)if((f=r.find[l])&&(i=f(c.matches[0].replace(Z,ee),J.test(u[0].type)&&ve(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&me(u)))return N.apply(n,i),n;break}}return(p||s(e,d))(i,t,!v,n,!t||J.test(e)&&ve(t.parentNode)||t),n},n.sortStable=b.split("").sort(S).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ce("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ce("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||ce($,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(n);C.find=A,C.expr=A.selectors,C.expr[":"]=C.expr.pseudos,C.uniqueSort=C.unique=A.uniqueSort,C.text=A.getText,C.isXMLDoc=A.isXML,C.contains=A.contains,C.escapeSelector=A.escape;var S=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&C(e).is(n))break;r.push(e)}return r},k=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},O=C.expr.match.needsContext;function D(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var I=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function N(e,t,n){return y(t)?C.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?C.grep(e,function(e){return e===t!==n}):"string"!=typeof t?C.grep(e,function(e){return f.call(t,e)>-1!==n}):C.filter(t,e,n)}C.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?C.find.matchesSelector(r,e)?[r]:[]:C.find.matches(e,C.grep(t,function(e){return 1===e.nodeType}))},C.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(C(e).filter(function(){for(t=0;t1?C.uniqueSort(n):n},filter:function(e){return this.pushStack(N(this,e||[],!1))},not:function(e){return this.pushStack(N(this,e||[],!0))},is:function(e){return!!N(this,"string"==typeof e&&O.test(e)?C(e):e||[],!1).length}});var j,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(C.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof C?t[0]:t,C.merge(this,C.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:a,!0)),I.test(r[1])&&C.isPlainObject(t))for(r in t)y(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=a.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):y(e)?void 0!==n.ready?n.ready(e):e(C):C.makeArray(e,this)}).prototype=C.fn,j=C(a);var $=/^(?:parents|prev(?:Until|All))/,P={children:!0,contents:!0,next:!0,prev:!0};function R(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}C.fn.extend({has:function(e){var t=C(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&C.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?C.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?f.call(C(e),this[0]):f.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(C.uniqueSort(C.merge(this.get(),C(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),C.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return S(e,"parentNode")},parentsUntil:function(e,t,n){return S(e,"parentNode",n)},next:function(e){return R(e,"nextSibling")},prev:function(e){return R(e,"previousSibling")},nextAll:function(e){return S(e,"nextSibling")},prevAll:function(e){return S(e,"previousSibling")},nextUntil:function(e,t,n){return S(e,"nextSibling",n)},prevUntil:function(e,t,n){return S(e,"previousSibling",n)},siblings:function(e){return k((e.parentNode||{}).firstChild,e)},children:function(e){return k(e.firstChild)},contents:function(e){return D(e,"iframe")?e.contentDocument:(D(e,"template")&&(e=e.content||e),C.merge([],e.childNodes))}},function(e,t){C.fn[e]=function(n,r){var i=C.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=C.filter(r,i)),this.length>1&&(P[e]||C.uniqueSort(i),$.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function H(e){return e}function F(e){throw e}function q(e,t,n,r){var i;try{e&&y(i=e.promise)?i.call(e).done(t).fail(n):e&&y(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}C.Callbacks=function(e){e="string"==typeof e?function(e){var t={};return C.each(e.match(M)||[],function(e,n){t[n]=!0}),t}(e):C.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1)for(n=a.shift();++s-1;)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?C.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},C.extend({Deferred:function(e){var t=[["notify","progress",C.Callbacks("memory"),C.Callbacks("memory"),2],["resolve","done",C.Callbacks("once memory"),C.Callbacks("once memory"),0,"resolved"],["reject","fail",C.Callbacks("once memory"),C.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments;return C.Deferred(function(n){C.each(t,function(t,r){var i=y(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&y(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(e,r,i){var o=0;function a(e,t,r,i){return function(){var s=this,u=arguments,c=function(){var n,c;if(!(e=o&&(r!==F&&(s=void 0,u=[n]),t.rejectWith(s,u))}};e?l():(C.Deferred.getStackHook&&(l.stackTrace=C.Deferred.getStackHook()),n.setTimeout(l))}}return C.Deferred(function(n){t[0][3].add(a(0,n,y(i)?i:H,n.notifyWith)),t[1][3].add(a(0,n,y(e)?e:H)),t[2][3].add(a(0,n,y(r)?r:F))}).promise()},promise:function(e){return null!=e?C.extend(e,i):i}},o={};return C.each(t,function(e,n){var a=n[2],s=n[5];i[n[1]]=a.add,s&&a.add(function(){r=s},t[3-e][2].disable,t[3-e][3].disable,t[0][2].lock,t[0][3].lock),a.add(n[3].fire),o[n[0]]=function(){return o[n[0]+"With"](this===o?void 0:this,arguments),this},o[n[0]+"With"]=a.fireWith}),i.promise(o),e&&e.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=u.call(arguments),o=C.Deferred(),a=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?u.call(arguments):n,--t||o.resolveWith(r,i)}};if(t<=1&&(q(e,o.done(a(n)).resolve,o.reject,!t),"pending"===o.state()||y(i[n]&&i[n].then)))return o.then();for(;n--;)q(i[n],a(n),o.reject);return o.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;C.Deferred.exceptionHook=function(e,t){n.console&&n.console.warn&&e&&B.test(e.name)&&n.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},C.readyException=function(e){n.setTimeout(function(){throw e})};var W=C.Deferred();function U(){a.removeEventListener("DOMContentLoaded",U),n.removeEventListener("load",U),C.ready()}C.fn.ready=function(e){return W.then(e).catch(function(e){C.readyException(e)}),this},C.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--C.readyWait:C.isReady)||(C.isReady=!0,!0!==e&&--C.readyWait>0||W.resolveWith(a,[C]))}}),C.ready.then=W.then,"complete"===a.readyState||"loading"!==a.readyState&&!a.documentElement.doScroll?n.setTimeout(C.ready):(a.addEventListener("DOMContentLoaded",U),n.addEventListener("load",U));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,c=null==n;if("object"===x(n))for(s in i=!0,n)z(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,y(r)||(a=!0),c&&(a?(t.call(e,r),t=null):(c=t,t=function(e,t,n){return c.call(C(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){Z.remove(this,e)})}}),C.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,C.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=C.queue(e,t),r=n.length,i=n.shift(),o=C._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){C.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:C.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),C.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ve={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ge(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&D(e,t)?C.merge([e],n):n}function me(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(c=C.contains(o.ownerDocument,o),a=ge(f.appendChild(o),"script"),c&&me(a),n)for(l=0;o=a[l++];)he.test(o.type||"")&&n.push(o);return f}ye=a.createDocumentFragment().appendChild(a.createElement("div")),(_e=a.createElement("input")).setAttribute("type","radio"),_e.setAttribute("checked","checked"),_e.setAttribute("name","t"),ye.appendChild(_e),m.checkClone=ye.cloneNode(!0).cloneNode(!0).lastChild.checked,ye.innerHTML="",m.noCloneChecked=!!ye.cloneNode(!0).lastChild.defaultValue;var xe=a.documentElement,Ce=/^key/,Ee=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ae(){return!0}function Se(){return!1}function ke(){try{return a.activeElement}catch(e){}}function Oe(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Oe(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return C().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=C.guid++)),e.each(function(){C.event.add(this,t,i,r,n)})}C.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,c,l,f,p,d,h,v,g=J.get(e);if(g)for(n.handler&&(n=(o=n).handler,i=o.selector),i&&C.find.matchesSelector(xe,i),n.guid||(n.guid=C.guid++),(u=g.events)||(u=g.events={}),(a=g.handle)||(a=g.handle=function(t){return void 0!==C&&C.event.triggered!==t.type?C.event.dispatch.apply(e,arguments):void 0}),c=(t=(t||"").match(M)||[""]).length;c--;)d=v=(s=Te.exec(t[c])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=C.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=C.event.special[d]||{},l=C.extend({type:d,origType:v,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&C.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,l),l.handler.guid||(l.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,l):p.push(l),C.event.global[d]=!0)},remove:function(e,t,n,r,i){var o,a,s,u,c,l,f,p,d,h,v,g=J.hasData(e)&&J.get(e);if(g&&(u=g.events)){for(c=(t=(t||"").match(M)||[""]).length;c--;)if(d=v=(s=Te.exec(t[c])||[])[1],h=(s[2]||"").split(".").sort(),d){for(f=C.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)l=p[o],!i&&v!==l.origType||n&&n.guid!==l.guid||s&&!s.test(l.namespace)||r&&r!==l.selector&&("**"!==r||!l.selector)||(p.splice(o,1),l.selector&&p.delegateCount--,f.remove&&f.remove.call(e,l));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,g.handle)||C.removeEvent(e,d,g.handle),delete u[d])}else for(d in u)C.event.remove(e,d+t[c],n,r,!0);C.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=C.event.fix(e),u=new Array(arguments.length),c=(J.get(this,"events")||{})[s.type]||[],l=C.event.special[s.type]||{};for(u[0]=s,t=1;t=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==e.type||!0!==c.disabled)){for(o=[],a={},n=0;n-1:C.find(i,this,null,[c]).length),a[i]&&o.push(r);o.length&&s.push({elem:c,handlers:o})}return c=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ie=/\s*$/g;function Le(e,t){return D(e,"table")&&D(11!==t.nodeType?t:t.firstChild,"tr")&&C(e).children("tbody")[0]||e}function $e(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Pe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Re(e,t){var n,r,i,o,a,s,u,c;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),c=o.events))for(i in delete a.handle,a.events={},c)for(n=0,r=c[i].length;n1&&"string"==typeof h&&!m.checkClone&&Ne.test(h))return e.each(function(i){var o=e.eq(i);v&&(t[0]=h.call(this,i,o.html())),Me(o,t,n,r)});if(p&&(o=(i=we(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=o),o||r)){for(s=(a=C.map(ge(i,"script"),$e)).length;f")},clone:function(e,t,n){var r,i,o,a,s,u,c,l=e.cloneNode(!0),f=C.contains(e.ownerDocument,e);if(!(m.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||C.isXMLDoc(e)))for(a=ge(l),r=0,i=(o=ge(e)).length;r0&&me(a,!f&&ge(e,"script")),l},cleanData:function(e){for(var t,n,r,i=C.event.special,o=0;void 0!==(n=e[o]);o++)if(X(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?C.event.remove(n,r):C.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[Z.expando]&&(n[Z.expando]=void 0)}}}),C.fn.extend({detach:function(e){return He(this,e,!0)},remove:function(e){return He(this,e)},text:function(e){return z(this,function(e){return void 0===e?C.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Me(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Me(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Me(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Me(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(C.cleanData(ge(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return C.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ie.test(e)&&!ve[(de.exec(e)||["",""])[1].toLowerCase()]){e=C.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=qe(e),i=We(e,t,r),o="border-box"===C.css(e,"boxSizing",!1,r),a=o;if(Fe.test(i)){if(!n)return i;i="auto"}return a=a&&(m.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===C.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}C.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=We(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=Y(t),u=Ve.test(t),c=e.style;if(u||(t=Ge(s)),a=C.cssHooks[t]||C.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:c[t];"string"===(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n==n&&("number"===o&&(n+=i&&i[3]||(C.cssNumber[s]?"":"px")),m.clearCloneStyle||""!==n||0!==t.indexOf("background")||(c[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?c.setProperty(t,n):c[t]=n))}},css:function(e,t,n,r){var i,o,a,s=Y(t);return Ve.test(t)||(t=Ge(s)),(a=C.cssHooks[t]||C.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=We(e,t,r)),"normal"===i&&t in Qe&&(i=Qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),C.each(["height","width"],function(e,t){C.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(C.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ke,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=qe(e),a="border-box"===C.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&m.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=C.css(e,t)),Je(0,n,s)}}}),C.cssHooks.marginLeft=Ue(m.reliableMarginLeft,function(e,t){if(t)return(parseFloat(We(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),C.each({margin:"",padding:"",border:"Width"},function(e,t){C.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(C.cssHooks[e+t].set=Je)}),C.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=qe(e),i=t.length;a1)}}),C.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||C.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(C.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=C.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=C.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){C.fx.step[e.prop]?C.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[C.cssProps[e.prop]]&&!C.cssHooks[e.prop]?e.elem[e.prop]=e.now:C.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},C.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},C.fx=tt.prototype.init,C.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===a.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(at):n.setTimeout(at,C.fx.interval),C.fx.tick())}function st(){return n.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ct(e,t,n){for(var r,i=(lt.tweeners[t]||[]).concat(lt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){C.removeAttr(this,e)})}}),C.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?C.prop(e,t,n):(1===o&&C.isXMLDoc(e)||(i=C.attrHooks[t.toLowerCase()]||(C.expr.match.bool.test(t)?ft:void 0)),void 0!==n?null===n?void C.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=C.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!m.radioValue&&"radio"===t&&D(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),ft={set:function(e,t,n){return!1===t?C.removeAttr(e,n):e.setAttribute(n,n),n}},C.each(C.expr.match.bool.source.match(/\w+/g),function(e,t){var n=pt[t]||C.find.attr;pt[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=pt[a],pt[a]=i,i=null!=n(e,t,r)?a:null,pt[a]=o),i}});var dt=/^(?:input|select|textarea|button)$/i,ht=/^(?:a|area)$/i;function vt(e){return(e.match(M)||[]).join(" ")}function gt(e){return e.getAttribute&&e.getAttribute("class")||""}function mt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(M)||[]}C.fn.extend({prop:function(e,t){return z(this,C.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[C.propFix[e]||e]})}}),C.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&C.isXMLDoc(e)||(t=C.propFix[t]||t,i=C.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=C.find.attr(e,"tabindex");return t?parseInt(t,10):dt.test(e.nodeName)||ht.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),m.optSelected||(C.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),C.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){C.propFix[this.toLowerCase()]=this}),C.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(y(e))return this.each(function(t){C(this).addClass(e.call(this,t,gt(this)))});if((t=mt(e)).length)for(;n=this[u++];)if(i=gt(n),r=1===n.nodeType&&" "+vt(i)+" "){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(y(e))return this.each(function(t){C(this).removeClass(e.call(this,t,gt(this)))});if(!arguments.length)return this.attr("class","");if((t=mt(e)).length)for(;n=this[u++];)if(i=gt(n),r=1===n.nodeType&&" "+vt(i)+" "){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):y(e)?this.each(function(n){C(this).toggleClass(e.call(this,n,gt(this),t),t)}):this.each(function(){var t,i,o,a;if(r)for(i=0,o=C(this),a=mt(e);t=a[i++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&"boolean"!==n||((t=gt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+vt(gt(n))+" ").indexOf(t)>-1)return!0;return!1}});var yt=/\r/g;C.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r=y(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,C(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=C.map(i,function(e){return null==e?"":e+""})),(t=C.valHooks[this.type]||C.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=C.valHooks[i.type]||C.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(yt,""):null==n?"":n:void 0}}),C.extend({valHooks:{option:{get:function(e){var t=C.find.attr(e,"value");return null!=t?t:vt(C.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),C.each(["radio","checkbox"],function(){C.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=C.inArray(C(e).val(),t)>-1}},m.checkOn||(C.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),m.focusin="onfocusin"in n;var _t=/^(?:focusinfocus|focusoutblur)$/,bt=function(e){e.stopPropagation()};C.extend(C.event,{trigger:function(e,t,r,i){var o,s,u,c,l,f,p,d,v=[r||a],g=h.call(e,"type")?e.type:e,m=h.call(e,"namespace")?e.namespace.split("."):[];if(s=d=u=r=r||a,3!==r.nodeType&&8!==r.nodeType&&!_t.test(g+C.event.triggered)&&(g.indexOf(".")>-1&&(g=(m=g.split(".")).shift(),m.sort()),l=g.indexOf(":")<0&&"on"+g,(e=e[C.expando]?e:new C.Event(g,"object"==typeof e&&e)).isTrigger=i?2:3,e.namespace=m.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:C.makeArray(t,[e]),p=C.event.special[g]||{},i||!p.trigger||!1!==p.trigger.apply(r,t))){if(!i&&!p.noBubble&&!_(r)){for(c=p.delegateType||g,_t.test(c+g)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(r.ownerDocument||a)&&v.push(u.defaultView||u.parentWindow||n)}for(o=0;(s=v[o++])&&!e.isPropagationStopped();)d=s,e.type=o>1?c:p.bindType||g,(f=(J.get(s,"events")||{})[e.type]&&J.get(s,"handle"))&&f.apply(s,t),(f=l&&s[l])&&f.apply&&X(s)&&(e.result=f.apply(s,t),!1===e.result&&e.preventDefault());return e.type=g,i||e.isDefaultPrevented()||p._default&&!1!==p._default.apply(v.pop(),t)||!X(r)||l&&y(r[g])&&!_(r)&&((u=r[l])&&(r[l]=null),C.event.triggered=g,e.isPropagationStopped()&&d.addEventListener(g,bt),r[g](),e.isPropagationStopped()&&d.removeEventListener(g,bt),C.event.triggered=void 0,u&&(r[l]=u)),e.result}},simulate:function(e,t,n){var r=C.extend(new C.Event,n,{type:e,isSimulated:!0});C.event.trigger(r,null,t)}}),C.fn.extend({trigger:function(e,t){return this.each(function(){C.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return C.event.trigger(e,t,n,!0)}}),m.focusin||C.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){C.event.simulate(t,e.target,C.event.fix(e))};C.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var wt=n.location,xt=Date.now(),Ct=/\?/;C.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||C.error("Invalid XML: "+e),t};var Et=/\[\]$/,Tt=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,St=/^(?:input|select|textarea|keygen)/i;function kt(e,t,n,r){var i;if(Array.isArray(t))C.each(t,function(t,i){n||Et.test(e)?r(e,i):kt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)kt(e+"["+i+"]",t[i],n,r)}C.param=function(e,t){var n,r=[],i=function(e,t){var n=y(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!C.isPlainObject(e))C.each(e,function(){i(this.name,this.value)});else for(n in e)kt(n,e[n],t,i);return r.join("&")},C.fn.extend({serialize:function(){return C.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=C.prop(this,"elements");return e?C.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!C(this).is(":disabled")&&St.test(this.nodeName)&&!At.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=C(this).val();return null==n?null:Array.isArray(n)?C.map(n,function(e){return{name:t.name,value:e.replace(Tt,"\r\n")}}):{name:t.name,value:n.replace(Tt,"\r\n")}}).get()}});var Ot=/%20/g,Dt=/#.*$/,It=/([?&])_=[^&]*/,Nt=/^(.*?):[ \t]*([^\r\n]*)$/gm,jt=/^(?:GET|HEAD)$/,Lt=/^\/\//,$t={},Pt={},Rt="*/".concat("*"),Mt=a.createElement("a");function Ht(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(y(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function Ft(e,t,n,r){var i={},o=e===Pt;function a(s){var u;return i[s]=!0,C.each(e[s]||[],function(e,s){var c=s(t,n,r);return"string"!=typeof c||o||i[c]?o?!(u=c):void 0:(t.dataTypes.unshift(c),a(c),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function qt(e,t){var n,r,i=C.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&C.extend(!0,e,r),e}Mt.href=wt.href,C.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:wt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(wt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Rt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":C.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?qt(qt(e,C.ajaxSettings),t):qt(C.ajaxSettings,e)},ajaxPrefilter:Ht($t),ajaxTransport:Ht(Pt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var r,i,o,s,u,c,l,f,p,d,h=C.ajaxSetup({},t),v=h.context||h,g=h.context&&(v.nodeType||v.jquery)?C(v):C.event,m=C.Deferred(),y=C.Callbacks("once memory"),_=h.statusCode||{},b={},w={},x="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(l){if(!s)for(s={};t=Nt.exec(o);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return l?o:null},setRequestHeader:function(e,t){return null==l&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==l&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(l)E.always(e[E.status]);else for(t in e)_[t]=[_[t],e[t]];return this},abort:function(e){var t=e||x;return r&&r.abort(t),T(0,t),this}};if(m.promise(E),h.url=((e||h.url||wt.href)+"").replace(Lt,wt.protocol+"//"),h.type=t.method||t.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){c=a.createElement("a");try{c.href=h.url,c.href=c.href,h.crossDomain=Mt.protocol+"//"+Mt.host!=c.protocol+"//"+c.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=C.param(h.data,h.traditional)),Ft($t,h,t,E),l)return E;for(p in(f=C.event&&h.global)&&0==C.active++&&C.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!jt.test(h.type),i=h.url.replace(Dt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(Ot,"+")):(d=h.url.slice(i.length),h.data&&(h.processData||"string"==typeof h.data)&&(i+=(Ct.test(i)?"&":"?")+h.data,delete h.data),!1===h.cache&&(i=i.replace(It,"$1"),d=(Ct.test(i)?"&":"?")+"_="+xt+++d),h.url=i+d),h.ifModified&&(C.lastModified[i]&&E.setRequestHeader("If-Modified-Since",C.lastModified[i]),C.etag[i]&&E.setRequestHeader("If-None-Match",C.etag[i])),(h.data&&h.hasContent&&!1!==h.contentType||t.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+Rt+"; q=0.01":""):h.accepts["*"]),h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(v,E,h)||l))return E.abort();if(x="abort",y.add(h.complete),E.done(h.success),E.fail(h.error),r=Ft(Pt,h,t,E)){if(E.readyState=1,f&&g.trigger("ajaxSend",[E,h]),l)return E;h.async&&h.timeout>0&&(u=n.setTimeout(function(){E.abort("timeout")},h.timeout));try{l=!1,r.send(b,T)}catch(e){if(l)throw e;T(-1,e)}}else T(-1,"No Transport");function T(e,t,a,s){var c,p,d,b,w,x=t;l||(l=!0,u&&n.clearTimeout(u),r=void 0,o=s||"",E.readyState=e>0?4:0,c=e>=200&&e<300||304===e,a&&(b=function(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(h,E,a)),b=function(e,t,n,r){var i,o,a,s,u,c={},l=e.dataTypes.slice();if(l[1])for(a in e.converters)c[a.toLowerCase()]=e.converters[a];for(o=l.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=l.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=c[u+" "+o]||c["* "+o]))for(i in c)if((s=i.split(" "))[1]===o&&(a=c[u+" "+s[0]]||c["* "+s[0]])){!0===a?a=c[i]:!0!==c[i]&&(o=s[0],l.unshift(s[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(h,b,E,c),c?(h.ifModified&&((w=E.getResponseHeader("Last-Modified"))&&(C.lastModified[i]=w),(w=E.getResponseHeader("etag"))&&(C.etag[i]=w)),204===e||"HEAD"===h.type?x="nocontent":304===e?x="notmodified":(x=b.state,p=b.data,c=!(d=b.error))):(d=x,!e&&x||(x="error",e<0&&(e=0))),E.status=e,E.statusText=(t||x)+"",c?m.resolveWith(v,[p,x,E]):m.rejectWith(v,[E,x,d]),E.statusCode(_),_=void 0,f&&g.trigger(c?"ajaxSuccess":"ajaxError",[E,h,c?p:d]),y.fireWith(v,[E,x]),f&&(g.trigger("ajaxComplete",[E,h]),--C.active||C.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return C.get(e,t,n,"json")},getScript:function(e,t){return C.get(e,void 0,t,"script")}}),C.each(["get","post"],function(e,t){C[t]=function(e,n,r,i){return y(n)&&(i=i||r,r=n,n=void 0),C.ajax(C.extend({url:e,type:t,dataType:i,data:n,success:r},C.isPlainObject(e)&&e))}}),C._evalUrl=function(e){return C.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},C.fn.extend({wrapAll:function(e){var t;return this[0]&&(y(e)&&(e=e.call(this[0])),t=C(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return y(e)?this.each(function(t){C(this).wrapInner(e.call(this,t))}):this.each(function(){var t=C(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=y(e);return this.each(function(n){C(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){C(this).replaceWith(this.childNodes)}),this}}),C.expr.pseudos.hidden=function(e){return!C.expr.pseudos.visible(e)},C.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},C.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Bt={0:200,1223:204},Wt=C.ajaxSettings.xhr();m.cors=!!Wt&&"withCredentials"in Wt,m.ajax=Wt=!!Wt,C.ajaxTransport(function(e){var t,r;if(m.cors||Wt&&!e.crossDomain)return{send:function(i,o){var a,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(a in e.xhrFields)s[a]=e.xhrFields[a];for(a in e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)s.setRequestHeader(a,i[a]);t=function(e){return function(){t&&(t=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Bt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=t(),r=s.onerror=s.ontimeout=t("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&n.setTimeout(function(){t&&r()})},t=t("abort");try{s.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}}),C.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),C.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return C.globalEval(e),e}}}),C.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),C.ajaxTransport("script",function(e){var t,n;if(e.crossDomain)return{send:function(r,i){t=C("#is', '', $string))); +} + +function jalali_to_georgian($date) +{ + try { + $dateArray = explode('-', $date); + $dateTemp = Morilog\Jalali\jDateTime::toGregorian(persian_number_to_latin($dateArray[0]), persian_number_to_latin($dateArray[1]), persian_number_to_latin($dateArray[2])); + $dateTemp[1] = $dateTemp[1] < 10 ? '0' . $dateTemp[1] : $dateTemp[1]; + $dateTemp[2] = $dateTemp[2] < 10 ? '0' . $dateTemp[2] : $dateTemp[2]; + $date = $dateTemp[0] . '-' . $dateTemp[1] . '-' . $dateTemp[2]; + + return is_valid_date($date) ? $date : null; + } catch (\Exception $e) { + return null; + } +} + +function is_valid_date($date, $format = 'Y-m-d') +{ + $d = DateTime::createFromFormat($format, $date); + + return $d && $d->format($format) === $date; +} + +function site_config($key) +{ + $config = App\Config::where('key', '=', $key)->first(); + if ($config) { + return $config->value; + } + + return ''; +} + + +function handle_exception(\Exception $e) +{ + if (app('site_configs')['APP_ENV'] == 'local') { + return redirect()->back() + ->with('alert', 'danger') + ->with('message', $e->getMessage()); + } + + return redirect()->back() + ->with('alert', 'danger') + ->with('message', 'Error'); +} + +function custom_money_format($money) +{ + return number_format($money, 0, '', ','); +} + +function mask_card_number($cardNumber) +{ + $cardNumber = str_replace('-', '', $cardNumber); + + return substr($cardNumber, 0, 6) . '******' . substr($cardNumber, -4, 4); +} + +function create_date_range($strDateFrom, $strDateTo) +{ + $aryRange = []; + $iDateFrom = mktime(1, 0, 0, substr($strDateFrom, 5, 2), substr($strDateFrom, 8, 2), substr($strDateFrom, 0, 4)); + $iDateTo = mktime(1, 0, 0, substr($strDateTo, 5, 2), substr($strDateTo, 8, 2), substr($strDateTo, 0, 4)); + if ($iDateTo >= $iDateFrom) { + array_push($aryRange, date('Y-m-d', $iDateFrom)); // first entry + while ($iDateFrom < $iDateTo) { + $iDateFrom += 86400; // add 24 hours + array_push($aryRange, date('Y-m-d', $iDateFrom)); + } + } + + return $aryRange; +} + + +function theme_asset($url, $secure = null) +{ + return asset('themes/' . site_config('theme') . '/' . $url, $secure); +} + +function lang($key) +{ + if (\Lang::has('fp::' . $key)) { + return trans('fp::' . $key); + } + + return trans($key); +} + +function date_diff_in_minutes(\Carbon\Carbon $start, \Carbon\Carbon $finish) +{ + $totalDuration = $start->diffInMinutes($finish); + + return $totalDuration; +} + +function date_diff_in_days(\Carbon\Carbon $start, \Carbon\Carbon $finish) +{ + $totalDuration = $start->diffInDays($finish); + + return $totalDuration; +} diff --git a/core/app/Transaction.php b/core/app/Transaction.php new file mode 100644 index 0000000..5829336 --- /dev/null +++ b/core/app/Transaction.php @@ -0,0 +1,98 @@ + 'json', + 'details' => 'json', + ]; + + /** + * Status enums + * + * @var array + */ + public static $status = [ + 'not_paid' => 0, + 'paid' => 1, + ]; + + /** + * Status enums + * + * @var array + */ + public static $type = [ + 'form' => 1, + 'factor' => 2, + 'file' => 3, + ]; + + /** + * Status enums + * + * @var array + */ + public static $typeLabels = [ + 1 => 'فرم پرداخت', + 2 => 'فاکتور پرداخت', + 3 => 'فروش فایل', + ]; + + public function form() + { + if ($this->type == self::$type['form']) { + return Form::find($this->details['form_id']); + } + + return null; + } + + public function factor() + { + if ($this->type == self::$type['factor']) { + return Factor::find($this->details['factor_id']); + } + + return null; + } + + public function file() + { + if ($this->type == self::$type['file']) { + return File::find($this->details['file_id']); + } + + return null; + } +} diff --git a/core/app/User.php b/core/app/User.php new file mode 100644 index 0000000..0f5159c --- /dev/null +++ b/core/app/User.php @@ -0,0 +1,37 @@ +make(Illuminate\Contracts\Console\Kernel::class); + +$status = $kernel->handle( + $input = new Symfony\Component\Console\Input\ArgvInput, + new Symfony\Component\Console\Output\ConsoleOutput +); + +/* +|-------------------------------------------------------------------------- +| Shutdown The Application +|-------------------------------------------------------------------------- +| +| Once Artisan has finished running. We will fire off the shutdown events +| so that any final work may be done by the application before we shut +| down the process. This is the last thing to happen to the request. +| +*/ + +$kernel->terminate($input, $status); + +exit($status); diff --git a/core/bootstrap/app.php b/core/bootstrap/app.php new file mode 100644 index 0000000..022fd9f --- /dev/null +++ b/core/bootstrap/app.php @@ -0,0 +1,59 @@ +singleton('site_configs', function () { + return require(__DIR__ . '/../config.php'); +}); + +$app->singleton( + Illuminate\Contracts\Http\Kernel::class, + App\Http\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Console\Kernel::class, + App\Console\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Debug\ExceptionHandler::class, + App\Exceptions\Handler::class +); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/core/bootstrap/autoload.php b/core/bootstrap/autoload.php new file mode 100644 index 0000000..3830137 --- /dev/null +++ b/core/bootstrap/autoload.php @@ -0,0 +1,34 @@ +=5.5.9", + "laravel/framework": "5.1.*", + "morilog/jalali": "^2.3", + "ext-curl": "*", + "ext-json": "*", + "doctrine/inflector": "1.1.0" + }, + "require-dev": { + "fzaninotto/faker": "~1.4", + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~4.0", + "phpspec/phpspec": "~2.1" + }, + "autoload": { + "classmap": [ + "database" + ], + "psr-4": { + "App\\": "app/" + }, + "files": [ + "app/Support/helpers.php" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/TestCase.php" + ] + }, + "scripts": { + "post-create-project-cmd": [ + "php artisan key:generate" + ], + "post-install-cmd": [ + "Illuminate\\Foundation\\ComposerScripts::postInstall", + "php artisan optimize" + ], + "post-update-cmd": [ + "Illuminate\\Foundation\\ComposerScripts::postUpdate", + "php artisan optimize" + ] + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/core/composer.lock b/core/composer.lock new file mode 100644 index 0000000..543c90f --- /dev/null +++ b/core/composer.lock @@ -0,0 +1,3546 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "dcb372f20f60aa27928ab5d1d970875c", + "packages": [ + { + "name": "classpreloader/classpreloader", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/ClassPreloader/ClassPreloader.git", + "reference": "4729e438e0ada350f91148e7d4bb9809342575ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/4729e438e0ada350f91148e7d4bb9809342575ff", + "reference": "4729e438e0ada350f91148e7d4bb9809342575ff", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.0|^2.0|^3.0", + "php": ">=5.5.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "ClassPreloader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", + "keywords": [ + "autoload", + "class", + "preload" + ], + "time": "2017-12-10T11:40:39+00:00" + }, + { + "name": "danielstjules/stringy", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/danielstjules/Stringy.git", + "reference": "4749c205db47ee5b32e8d1adf6d9aff8db6caf3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/danielstjules/Stringy/zipball/4749c205db47ee5b32e8d1adf6d9aff8db6caf3b", + "reference": "4749c205db47ee5b32e8d1adf6d9aff8db6caf3b", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Stringy\\": "src/" + }, + "files": [ + "src/Create.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel St. Jules", + "email": "danielst.jules@gmail.com", + "homepage": "http://www.danielstjules.com" + } + ], + "description": "A string manipulation library with multibyte support", + "homepage": "https://github.com/danielstjules/Stringy", + "keywords": [ + "UTF", + "helpers", + "manipulation", + "methods", + "multibyte", + "string", + "utf-8", + "utility", + "utils" + ], + "time": "2015-07-23T00:54:12+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + }, + "type": "project", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "time": "2014-10-24T07:27:01+00:00" + }, + { + "name": "doctrine/inflector", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2015-11-06T14:35:42+00:00" + }, + { + "name": "jakub-onderka/php-console-color", + "version": "v0.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "1.0", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "~4.3", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "JakubOnderka\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "time": "2018-09-29T17:23:10+00:00" + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.3.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "shasum": "" + }, + "require": { + "jakub-onderka/php-console-color": "~0.1", + "php": ">=5.3.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~0.5", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleHighlighter": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "time": "2015-04-20T18:58:01+00:00" + }, + { + "name": "jeremeamia/superclosure", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/jeremeamia/super_closure.git", + "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/5707d5821b30b9a07acfb4d76949784aaa0e9ce9", + "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.2|^2.0|^3.0|^4.0", + "php": ">=5.4", + "symfony/polyfill-php56": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "SuperClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia", + "role": "Developer" + } + ], + "description": "Serialize Closure objects, including their context and binding", + "homepage": "https://github.com/jeremeamia/super_closure", + "keywords": [ + "closure", + "function", + "lambda", + "parser", + "serializable", + "serialize", + "tokenizer" + ], + "time": "2018-03-21T22:21:57+00:00" + }, + { + "name": "kylekatarnls/update-helper", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/kylekatarnls/update-helper.git", + "reference": "b34a46d7f5ec1795b4a15ac9d46b884377262df9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kylekatarnls/update-helper/zipball/b34a46d7f5ec1795b4a15ac9d46b884377262df9", + "reference": "b34a46d7f5ec1795b4a15ac9d46b884377262df9", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0", + "php": ">=5.3.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "dev-master", + "composer/composer": "^2.0.x-dev", + "phpunit/phpunit": ">=4.8.35 <6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "UpdateHelper\\ComposerPlugin" + }, + "autoload": { + "psr-0": { + "UpdateHelper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Update helper", + "time": "2019-06-05T08:34:23+00:00" + }, + { + "name": "laravel/framework", + "version": "v5.1.46", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "7f2f892e62163138121e8210b92b21394fda8d1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/7f2f892e62163138121e8210b92b21394fda8d1c", + "reference": "7f2f892e62163138121e8210b92b21394fda8d1c", + "shasum": "" + }, + "require": { + "classpreloader/classpreloader": "~2.0|~3.0", + "danielstjules/stringy": "~1.8", + "doctrine/inflector": "~1.0", + "ext-mbstring": "*", + "ext-openssl": "*", + "jeremeamia/superclosure": "~2.0", + "league/flysystem": "~1.0", + "monolog/monolog": "~1.11", + "mtdowling/cron-expression": "~1.0", + "nesbot/carbon": "~1.19", + "paragonie/random_compat": "~1.4", + "php": ">=5.5.9", + "psy/psysh": "0.7.*", + "swiftmailer/swiftmailer": "~5.1", + "symfony/console": "2.7.*", + "symfony/css-selector": "2.7.*|2.8.*", + "symfony/debug": "2.7.*", + "symfony/dom-crawler": "2.7.*", + "symfony/finder": "2.7.*", + "symfony/http-foundation": "2.7.*", + "symfony/http-kernel": "2.7.*", + "symfony/process": "2.7.*", + "symfony/routing": "2.7.*", + "symfony/translation": "2.7.*", + "symfony/var-dumper": "2.7.*", + "vlucas/phpdotenv": "~1.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "~3.0", + "iron-io/iron_mq": "~2.0", + "mockery/mockery": "~0.9.4", + "pda/pheanstalk": "~3.0", + "phpunit/phpunit": "~4.0", + "predis/predis": "~1.0" + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).", + "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~5.3|~6.0).", + "iron-io/iron_mq": "Required to use the iron queue driver (~2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", + "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/Illuminate/Queue/IlluminateQueueClosure.php" + ], + "files": [ + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "http://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "time": "2017-03-24T16:31:45+00:00" + }, + { + "name": "league/flysystem", + "version": "1.0.53", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "08e12b7628f035600634a5e76d95b5eb66cea674" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/08e12b7628f035600634a5e76d95b5eb66cea674", + "reference": "08e12b7628f035600634a5e76d95b5eb66cea674", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.10" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2019-06-18T20:09:29+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.24.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2018-11-05T09:00:11+00:00" + }, + { + "name": "morilog/jalali", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/morilog/jalali.git", + "reference": "6a2a692fde8bb4695acf45a654bad609e402040a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/morilog/jalali/zipball/6a2a692fde8bb4695acf45a654bad609e402040a", + "reference": "6a2a692fde8bb4695acf45a654bad609e402040a", + "shasum": "" + }, + "require": { + "illuminate/support": "^5.0", + "nesbot/carbon": "^1.21", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Morilog\\Jalali\\JalaliServiceProvider" + ], + "aliases": { + "jDate": "Morilog\\Jalali\\Facades\\jDate" + } + } + }, + "autoload": { + "psr-4": { + "Morilog\\Jalali\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Milad Rey", + "email": "miladr@gmail.com" + }, + { + "name": "Morteza Parvini", + "email": "m.parvini@outlook.com" + } + ], + "description": "eThis Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in Laravel 5 applications, based on Jalali (Shamsi) DateTime class.", + "keywords": [ + "Jalali", + "date", + "datetime", + "laravel", + "morilog" + ], + "time": "2017-09-06T19:39:20+00:00" + }, + { + "name": "mtdowling/cron-expression", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/mtdowling/cron-expression.git", + "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/9504fa9ea681b586028adaaa0877db4aecf32bad", + "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "time": "2017-01-23T04:29:33+00:00" + }, + { + "name": "nesbot/carbon", + "version": "1.39.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0", + "reference": "dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0", + "shasum": "" + }, + "require": { + "kylekatarnls/update-helper": "^1.1", + "php": ">=5.3.9", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" + }, + "require-dev": { + "composer/composer": "^1.2", + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "bin": [ + "bin/upgrade-carbon" + ], + "type": "library", + "extra": { + "update-helper": "Carbon\\Upgrade", + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2019-06-11T09:07:59+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4dd659edadffdc2143e4753df655d866dbfeedf0", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2016-09-16T12:04:44+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v1.4.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/9b3899e3c3ddde89016f576edb8c489708ad64cd", + "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2018-04-04T21:48:54+00:00" + }, + { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280", + "shasum": "" + }, + "require": { + "dnoegel/php-xdg-base-dir": "0.1", + "jakub-onderka/php-console-highlighter": "0.3.*", + "nikic/php-parser": "^1.2.1|~2.0", + "php": ">=5.3.9", + "symfony/console": "~2.3.10|^2.4.2|~3.0", + "symfony/var-dumper": "~2.7|~3.0" + }, + "require-dev": { + "fabpot/php-cs-fixer": "~1.5", + "phpunit/phpunit": "~3.7|~4.0|~5.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/finder": "~2.1|~3.0" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.8.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psy/functions.php" + ], + "psr-4": { + "Psy\\": "src/Psy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "time": "2016-03-09T05:03:14+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.12", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "181b89f18a90f8925ef805f950d47a7190e9b950" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950", + "reference": "181b89f18a90f8925ef805f950d47a7190e9b950", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "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": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2018-07-31T09:26:32+00:00" + }, + { + "name": "symfony/console", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "574cb4cfaa01ba115fc2fc0c2355b2c5472a4804" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/574cb4cfaa01ba115fc2fc0c2355b2c5472a4804", + "reference": "574cb4cfaa01ba115fc2fc0c2355b2c5472a4804", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/process": "~2.1" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-05-13T15:44:36+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v2.8.50", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "7b1692e418d7ccac24c373528453bc90e42797de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/7b1692e418d7ccac24c373528453bc90e42797de", + "reference": "7b1692e418d7ccac24c373528453bc90e42797de", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/debug", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "4a7330f29b3d215f8bacf076689f9d1c3d568681" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/4a7330f29b3d215f8bacf076689f9d1c3d568681", + "reference": "4a7330f29b3d215f8bacf076689f9d1c3d568681", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2018-08-03T11:24:48+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "d905e1c5885735ee66af60c205429b9941f24752" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d905e1c5885735ee66af60c205429b9941f24752", + "reference": "d905e1c5885735ee66af60c205429b9941f24752", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/css-selector": "~2.3" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2018-05-01T22:30:49+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.50", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-11-21T14:20:20+00:00" + }, + { + "name": "symfony/finder", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "34226a3aa279f1e356ad56181b91acfdc9a2525c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/34226a3aa279f1e356ad56181b91acfdc9a2525c", + "reference": "34226a3aa279f1e356ad56181b91acfdc9a2525c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2018-05-14T06:36:14+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "b67e5cbd2bf837fb3681f2c4965826d6c6758532" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b67e5cbd2bf837fb3681f2c4965826d6c6758532", + "reference": "b67e5cbd2bf837fb3681f2c4965826d6c6758532", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2019-04-16T09:58:21+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v2.7.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "435064b3b143f79469206915137c21e88b56bfb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/435064b3b143f79469206915137c21e88b56bfb9", + "reference": "435064b3b143f79469206915137c21e88b56bfb9", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0", + "symfony/debug": "^2.6.2", + "symfony/event-dispatcher": "^2.6.7", + "symfony/http-foundation": "~2.7.36|^2.8.29", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/config": "<2.7", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "symfony/browser-kit": "~2.3", + "symfony/class-loader": "~2.1", + "symfony/config": "~2.7", + "symfony/console": "~2.3", + "symfony/css-selector": "^2.0.5", + "symfony/dependency-injection": "~2.2", + "symfony/dom-crawler": "^2.0.5", + "symfony/expression-language": "~2.4", + "symfony/finder": "^2.0.5", + "symfony/process": "^2.0.5", + "symfony/routing": "~2.2", + "symfony/stopwatch": "~2.3", + "symfony/templating": "~2.2", + "symfony/translation": "^2.0.5", + "symfony/var-dumper": "~2.6" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2019-04-17T16:37:53+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/f4dddbc5c3471e1b700a147a20ae17cdb72dbe42", + "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "b46c6cae28a3106735323f00a0c38eccf2328897" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/b46c6cae28a3106735323f00a0c38eccf2328897", + "reference": "b46c6cae28a3106735323f00a0c38eccf2328897", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2019-02-08T14:16:39+00:00" + }, + { + "name": "symfony/process", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "eda637e05670e2afeec3842dcd646dce94262f6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/eda637e05670e2afeec3842dcd646dce94262f6b", + "reference": "eda637e05670e2afeec3842dcd646dce94262f6b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2018-08-03T11:24:48+00:00" + }, + { + "name": "symfony/routing", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "33bd5882f201f9a3b7dd9640b95710b71304c4fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/33bd5882f201f9a3b7dd9640b95710b71304c4fb", + "reference": "33bd5882f201f9a3b7dd9640b95710b71304c4fb", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.7", + "symfony/expression-language": "~2.4", + "symfony/http-foundation": "~2.3", + "symfony/yaml": "^2.0.5" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2018-02-28T09:36:59+00:00" + }, + { + "name": "symfony/translation", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "1959c78c5a32539ef221b3e18a961a96d949118f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/1959c78c5a32539ef221b3e18a961a96d949118f", + "reference": "1959c78c5a32539ef221b3e18a961a96d949118f", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.7", + "symfony/intl": "~2.7.25|^2.8.18", + "symfony/yaml": "~2.2" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2018-05-17T10:34:06+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "6f9271e94369db05807b261fcfefe4cd1aafd390" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6f9271e94369db05807b261fcfefe4cd1aafd390", + "reference": "6f9271e94369db05807b261fcfefe4cd1aafd390", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "suggest": { + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2018-04-22T05:56:10+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa", + "reference": "0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Dotenv": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "homepage": "http://github.com/vlucas/phpdotenv", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2015-05-30T15:59:26+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "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" + ], + "time": "2018-07-12T10:23:15+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "hamcrest" + ], + "files": [ + "hamcrest/Hamcrest.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2015-05-11T14:41:42+00:00" + }, + { + "name": "mockery/mockery", + "version": "0.9.11", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "be9bf28d8e57d67883cba9fcadfcff8caab667f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/be9bf28d8e57d67883cba9fcadfcff8caab667f8", + "reference": "be9bf28d8e57d67883cba9fcadfcff8caab667f8", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~1.1", + "lib-pcre": ">=7.0", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/padraic/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2019-02-12T16:07:13+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-04-30T17:48:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/php-diff", + "version": "v1.0.2", + "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": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Diff": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Chris Boulton", + "homepage": "http://github.com/chrisboulton" + } + ], + "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", + "time": "2013-11-01T13:02:21+00:00" + }, + { + "name": "phpspec/phpspec", + "version": "2.5.8", + "source": { + "type": "git", + "url": "https://github.com/phpspec/phpspec.git", + "reference": "d8a153dcb52f929b448c0bf2cc19c7388951adb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/d8a153dcb52f929b448c0bf2cc19c7388951adb1", + "reference": "d8a153dcb52f929b448c0bf2cc19c7388951adb1", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.1", + "ext-tokenizer": "*", + "php": ">=5.3.3", + "phpspec/php-diff": "~1.0.0", + "phpspec/prophecy": "~1.4", + "sebastian/exporter": "~1.0|~2.0|^3.0", + "symfony/console": "~2.3|~3.0,!=3.2.8", + "symfony/event-dispatcher": "~2.1|~3.0", + "symfony/finder": "~2.1|~3.0", + "symfony/process": "^2.6|~3.0", + "symfony/yaml": "~2.1|~3.0" + }, + "require-dev": { + "behat/behat": "^3.0.11,!=3.3.1", + "ciaranmcnulty/versionbasedtestskipper": "^0.2.1", + "phpunit/phpunit": "~4.4", + "symfony/filesystem": "~2.1|~3.0" + }, + "suggest": { + "phpspec/nyan-formatters": "~1.0 – Adds Nyan formatters" + }, + "bin": [ + "bin/phpspec" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "PhpSpec": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "homepage": "http://marcelloduarte.net/" + } + ], + "description": "Specification-oriented BDD framework for PHP 5.3+", + "homepage": "http://phpspec.net/", + "keywords": [ + "BDD", + "SpecBDD", + "TDD", + "spec", + "specification", + "testing", + "tests" + ], + "time": "2017-07-29T17:19:38+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-06-13T12:50:23+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21T13:59:46+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.3.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "af615970e265543a26ee712c958404eb9b7ac93d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/af615970e265543a26ee712c958404eb9b7ac93d", + "reference": "af615970e265543a26ee712c958404eb9b7ac93d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-01-20T15:04:53+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-12-25T11:19:39+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.5.9", + "ext-curl": "*", + "ext-json": "*" + }, + "platform-dev": [] +} diff --git a/core/config-sample.php b/core/config-sample.php new file mode 100644 index 0000000..23a0a5b --- /dev/null +++ b/core/config-sample.php @@ -0,0 +1,15 @@ + 'production', + 'APP_DEBUG' => false, + 'APP_KEY' => 'MmyMcunZBJZuI9koOgQLbykzLf44NEQs', + 'APP_URL' => '', + 'DB_CONNECTION' => 'mysql', + 'DB_HOST' => '', + 'DB_DATABASE' => '', + 'DB_USERNAME' => '', + 'DB_PASSWORD' => '', + 'CACHE_DRIVER' => 'file', + 'SESSION_DRIVER' => 'file' +]; diff --git a/core/config.php b/core/config.php new file mode 100644 index 0000000..e850f9a --- /dev/null +++ b/core/config.php @@ -0,0 +1,15 @@ + "production", + "APP_DEBUG" => "", + "APP_KEY" => "MmyMcunZBJZuI9koOgQLbykzLf44NEQs", + "APP_URL" => "localhost", + "DB_CONNECTION" => "mysql", + "DB_HOST" => "localhost", + "DB_DATABASE" => "", + "DB_USERNAME" => "", + "DB_PASSWORD" => "", + "CACHE_DRIVER" => "file", + "SESSION_DRIVER" => "file", +) +; \ No newline at end of file diff --git a/core/config/app.php b/core/config/app.php new file mode 100644 index 0000000..96fa135 --- /dev/null +++ b/core/config/app.php @@ -0,0 +1,202 @@ + '2022.02.13', + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => app('site_configs')['APP_DEBUG'], + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => app('site_configs')['APP_URL'], + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => 'Asia/Tehran', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => 'fa', + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ + + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => app('site_configs')['APP_KEY'], + + 'cipher' => 'AES-256-CBC', + + /* + |-------------------------------------------------------------------------- + | Logging Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure the log settings for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Settings: "single", "daily", "syslog", "errorlog" + | + */ + + 'log' => 'single', + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => [ + + /* + * Laravel Framework Service Providers... + */ + Illuminate\Foundation\Providers\ArtisanServiceProvider::class, + Illuminate\Auth\AuthServiceProvider::class, + Illuminate\Broadcasting\BroadcastServiceProvider::class, + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Routing\ControllerServiceProvider::class, + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, + Illuminate\View\ViewServiceProvider::class, + + /* + * Application Service Providers... + */ + App\Providers\AppServiceProvider::class, + App\Providers\AuthServiceProvider::class, + App\Providers\EventServiceProvider::class, + App\Providers\RouteServiceProvider::class, + + App\Providers\UpdateServiceProvider::class, + App\Providers\ThemeServiceProvider::class, + ], + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => [ + + 'App' => Illuminate\Support\Facades\App::class, + 'Artisan' => Illuminate\Support\Facades\Artisan::class, + 'Auth' => Illuminate\Support\Facades\Auth::class, + 'Blade' => Illuminate\Support\Facades\Blade::class, + 'Bus' => Illuminate\Support\Facades\Bus::class, + 'Cache' => Illuminate\Support\Facades\Cache::class, + 'Config' => Illuminate\Support\Facades\Config::class, + 'Cookie' => Illuminate\Support\Facades\Cookie::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, + 'DB' => Illuminate\Support\Facades\DB::class, + 'Eloquent' => Illuminate\Database\Eloquent\Model::class, + 'Event' => Illuminate\Support\Facades\Event::class, + 'File' => Illuminate\Support\Facades\File::class, + 'Gate' => Illuminate\Support\Facades\Gate::class, + 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Input' => Illuminate\Support\Facades\Input::class, + 'Lang' => Illuminate\Support\Facades\Lang::class, + 'Log' => Illuminate\Support\Facades\Log::class, + 'Mail' => Illuminate\Support\Facades\Mail::class, + 'Password' => Illuminate\Support\Facades\Password::class, + 'Queue' => Illuminate\Support\Facades\Queue::class, + 'Redirect' => Illuminate\Support\Facades\Redirect::class, + 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Request' => Illuminate\Support\Facades\Request::class, + 'Response' => Illuminate\Support\Facades\Response::class, + 'Route' => Illuminate\Support\Facades\Route::class, + 'Schema' => Illuminate\Support\Facades\Schema::class, + 'Session' => Illuminate\Support\Facades\Session::class, + 'Storage' => Illuminate\Support\Facades\Storage::class, + 'URL' => Illuminate\Support\Facades\URL::class, + 'Validator' => Illuminate\Support\Facades\Validator::class, + 'View' => Illuminate\Support\Facades\View::class, + + ], + +]; diff --git a/core/config/auth.php b/core/config/auth.php new file mode 100644 index 0000000..1853d6c --- /dev/null +++ b/core/config/auth.php @@ -0,0 +1,67 @@ + 'eloquent', + + /* + |-------------------------------------------------------------------------- + | Authentication Model + |-------------------------------------------------------------------------- + | + | When using the "Eloquent" authentication driver, we need to know which + | Eloquent model should be used to retrieve your users. Of course, it + | is often just the "User" model but you may use whatever you like. + | + */ + + 'model' => App\User::class, + + /* + |-------------------------------------------------------------------------- + | Authentication Table + |-------------------------------------------------------------------------- + | + | When using the "Database" authentication driver, we need to know which + | table should be used to retrieve your users. We have chosen a basic + | default value but you may easily change it to any table you like. + | + */ + + 'table' => 'users', + + /* + |-------------------------------------------------------------------------- + | Password Reset Settings + |-------------------------------------------------------------------------- + | + | Here you may set the options for resetting passwords including the view + | that is your password reset e-mail. You can also set the name of the + | table that maintains all of the reset tokens for your application. + | + | The expire time is the number of minutes that the reset token should be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + */ + + 'password' => [ + 'email' => 'emails.password', + 'table' => 'fp_password_resets', + 'expire' => 60, + ], + +]; diff --git a/core/config/cache.php b/core/config/cache.php new file mode 100644 index 0000000..bf0e2bb --- /dev/null +++ b/core/config/cache.php @@ -0,0 +1,79 @@ + 'file', + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + */ + + 'stores' => [ + + 'apc' => [ + 'driver' => 'apc', + ], + + 'array' => [ + 'driver' => 'array', + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'cache', + 'connection' => null, + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'servers' => [ + [ + 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing a RAM based store such as APC or Memcached, there might + | be other applications utilizing the same cache. So, we'll specify a + | value to get prefixed to all our keys so we can avoid collisions. + | + */ + + 'prefix' => 'sadadpayment', + +]; diff --git a/core/config/database.php b/core/config/database.php new file mode 100644 index 0000000..041f0d1 --- /dev/null +++ b/core/config/database.php @@ -0,0 +1,127 @@ + PDO::FETCH_CLASS, + + /* + |-------------------------------------------------------------------------- + | Default Database Connection Name + |-------------------------------------------------------------------------- + | + | Here you may specify which of the database connections below you wish + | to use as your default connection for all database work. Of course + | you may use many connections at once using the Database library. + | + */ + + 'default' => app('site_configs')['DB_CONNECTION'], + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => database_path('database.sqlite'), + 'prefix' => '', + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'host' => app('site_configs')['DB_HOST'], + 'database' => app('site_configs')['DB_DATABASE'], + 'username' => app('site_configs')['DB_USERNAME'], + 'password' => app('site_configs')['DB_PASSWORD'], + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => false, + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'host' => app('site_configs')['DB_HOST'], + 'database' => app('site_configs')['DB_DATABASE'], + 'username' => app('site_configs')['DB_USERNAME'], + 'password' => app('site_configs')['DB_PASSWORD'], + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'host' => app('site_configs')['DB_HOST'], + 'database' => app('site_configs')['DB_DATABASE'], + 'username' => app('site_configs')['DB_USERNAME'], + 'password' => app('site_configs')['DB_PASSWORD'], + 'charset' => 'utf8', + 'prefix' => '', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run in the database. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer set of commands than a typical key-value systems + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'cluster' => false, + + 'default' => [ + 'host' => 'localhost', + 'password' => null, + 'port' => 6379, + 'database' => 0, + ], + + ], + +]; diff --git a/core/config/filesystems.php b/core/config/filesystems.php new file mode 100644 index 0000000..404f951 --- /dev/null +++ b/core/config/filesystems.php @@ -0,0 +1,74 @@ + 'local', + + /* + |-------------------------------------------------------------------------- + | Default Cloud Filesystem Disk + |-------------------------------------------------------------------------- + | + | Many applications store files both locally and in the cloud. For this + | reason, you may specify a default "cloud" driver here. This driver + | will be bound as the Cloud disk implementation in the container. + | + */ + + 'cloud' => 's3', + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Here you may configure as many filesystem "disks" as you wish, and you + | may even configure multiple disks of the same driver. Defaults have + | been setup for each driver as an example of the required options. + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + ], + + 'forms' => [ + 'driver' => 'local', + 'root' => base_path('../storage/forms'), + 'url' => 'storage/forms', + 'visibility' => 'public', + ], + + 'files-image' => [ + 'driver' => 'local', + 'root' => base_path('../storage/files-image'), + 'url' => 'storage/files-image', + 'visibility' => 'public', + ], + + 'files' => [ + 'driver' => 'local', + 'root' => storage_path('app/files'), + 'url' => 'storage/app/files', + 'visibility' => 'public', + ], + + ], + +]; diff --git a/core/config/mail.php b/core/config/mail.php new file mode 100644 index 0000000..59ced33 --- /dev/null +++ b/core/config/mail.php @@ -0,0 +1,122 @@ + 'smtp', + + /* + |-------------------------------------------------------------------------- + | SMTP Host Address + |-------------------------------------------------------------------------- + | + | Here you may provide the host address of the SMTP server used by your + | applications. A default option is provided that is compatible with + | the Mailgun mail service which will provide reliable deliveries. + | + */ + + 'host' => 'localhost', + + /* + |-------------------------------------------------------------------------- + | SMTP Host Port + |-------------------------------------------------------------------------- + | + | This is the SMTP port used by your application to deliver e-mails to + | users of the application. Like the host we have set this value to + | stay compatible with the Mailgun e-mail application by default. + | + */ + + 'port' => 25, + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. + | + */ + + 'from' => [ + 'address' => 'noreply@' . parse_url(app('site_configs')['APP_URL'])['host'], + 'name' => parse_url(app('site_configs')['APP_URL'])['host'], + ], + + /* + |-------------------------------------------------------------------------- + | E-Mail Encryption Protocol + |-------------------------------------------------------------------------- + | + | Here you may specify the encryption protocol that should be used when + | the application send e-mail messages. A sensible default using the + | transport layer security protocol should provide great security. + | + */ + + 'encryption' => null, + + /* + |-------------------------------------------------------------------------- + | SMTP Server Username + |-------------------------------------------------------------------------- + | + | If your SMTP server requires a username for authentication, you should + | set it here. This will get used to authenticate with your server on + | connection. You may also set the "password" value below this one. + | + */ + + 'username' => '', + + 'password' => '', + + /* + |-------------------------------------------------------------------------- + | Sendmail System Path + |-------------------------------------------------------------------------- + | + | When using the "sendmail" driver to send e-mails, we will need to know + | the path to where Sendmail lives on this server. A default path has + | been provided here, which will work well on most of your systems. + | + */ + + 'sendmail' => '/usr/sbin/sendmail -bs', + + /* + |-------------------------------------------------------------------------- + | Markdown Mail Settings + |-------------------------------------------------------------------------- + | + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! + | + */ + + 'markdown' => [ + 'theme' => 'default', + + 'paths' => [ + ], + ], + +]; diff --git a/core/config/queue.php b/core/config/queue.php new file mode 100644 index 0000000..aac5372 --- /dev/null +++ b/core/config/queue.php @@ -0,0 +1,54 @@ + 'sync', + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + */ + + 'connections' => [ + 'sync' => [ + 'driver' => 'sync', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control which database and table are used to store the jobs that + | have failed. You may change them to any database / table you wish. + | + */ + + 'failed' => [ + 'database' => 'mysql', + 'table' => 'failed_jobs', + ], + +]; diff --git a/core/config/session.php b/core/config/session.php new file mode 100644 index 0000000..87b2fa6 --- /dev/null +++ b/core/config/session.php @@ -0,0 +1,153 @@ + 'file', + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to immediately expire on the browser closing, set that option. + | + */ + + 'lifetime' => 120, + + 'expire_on_close' => false, + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it is stored. All encryption will be run + | automatically by Laravel and you can use the Session like normal. + | + */ + + 'encrypt' => false, + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When using the native session driver, we need a location where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => null, + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => 'sessions', + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + 'cookie' => 'sadadpayment_session', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application but you are free to change this when necessary. + | + */ + + 'path' => '/', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | Here you may change the domain of the cookie used to identify a session + | in your application. This will determine which domains the cookie is + | available to in your application. A sensible default has been set. + | + */ + + 'domain' => null, + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you if it can not be done securely. + | + */ + + 'secure' => false, + +]; diff --git a/core/config/view.php b/core/config/view.php new file mode 100644 index 0000000..e193ab6 --- /dev/null +++ b/core/config/view.php @@ -0,0 +1,33 @@ + [ + realpath(base_path('resources/views')), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => realpath(storage_path('framework/views')), + +]; diff --git a/core/configdd.php b/core/configdd.php new file mode 100644 index 0000000..a61eee4 --- /dev/null +++ b/core/configdd.php @@ -0,0 +1,15 @@ + "development", + "APP_DEBUG" => "true", + "APP_KEY" => "MmyMcunZBJZuI9koOgQLbykzLf44NEQs", + "APP_URL" => "https://eshop.faradadeh.com", + "DB_CONNECTION" => "mysql", + "DB_HOST" => "localhost", + "DB_DATABASE" => "eshop_sadad", + "DB_USERNAME" => "eshop_sadad", + "DB_PASSWORD" => "ZT@}^PuS,^uW", + "CACHE_DRIVER" => "file", + "SESSION_DRIVER" => "file", +) +; \ No newline at end of file diff --git a/core/database/.gitignore b/core/database/.gitignore new file mode 100644 index 0000000..9b1dffd --- /dev/null +++ b/core/database/.gitignore @@ -0,0 +1 @@ +*.sqlite diff --git a/core/database/migrations/.gitkeep b/core/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/core/database/migrations/2014_10_12_000000_create_users_table.php b/core/database/migrations/2014_10_12_000000_create_users_table.php new file mode 100644 index 0000000..ca16ecb --- /dev/null +++ b/core/database/migrations/2014_10_12_000000_create_users_table.php @@ -0,0 +1,35 @@ +bigIncrements('id'); + $table->string('name'); + $table->string('email')->unique(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('users'); + } +} diff --git a/core/database/migrations/2014_10_12_100000_create_password_resets_table.php b/core/database/migrations/2014_10_12_100000_create_password_resets_table.php new file mode 100644 index 0000000..0d5cb84 --- /dev/null +++ b/core/database/migrations/2014_10_12_100000_create_password_resets_table.php @@ -0,0 +1,32 @@ +string('email')->index(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('password_resets'); + } +} diff --git a/core/database/migrations/2019_07_08_000856_create_configs_table.php b/core/database/migrations/2019_07_08_000856_create_configs_table.php new file mode 100644 index 0000000..ba992a7 --- /dev/null +++ b/core/database/migrations/2019_07_08_000856_create_configs_table.php @@ -0,0 +1,35 @@ +bigIncrements('id'); + $table->string('key')->unique(); + $table->longText('value')->nullable(); + $table->string('label')->nullable(); + $table->boolean('visible')->default(1); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('configs'); + } +} diff --git a/core/database/migrations/2019_07_08_113413_create_transactions_table.php b/core/database/migrations/2019_07_08_113413_create_transactions_table.php new file mode 100644 index 0000000..692f7ac --- /dev/null +++ b/core/database/migrations/2019_07_08_113413_create_transactions_table.php @@ -0,0 +1,39 @@ +bigIncrements('id'); + $table->tinyInteger('type')->index(); + $table->bigInteger('amount'); + $table->json('payment_info')->nullable(); + $table->json('details')->nullable(); + $table->boolean('status')->default(0)->index(); + $table->boolean('verified')->default(0)->index(); + $table->dateTime('paid_at')->nullable(); + $table->dateTime('verified_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('transactions'); + } +} diff --git a/core/database/migrations/2019_07_08_143755_create_forms_table.php b/core/database/migrations/2019_07_08_143755_create_forms_table.php new file mode 100644 index 0000000..6de82de --- /dev/null +++ b/core/database/migrations/2019_07_08_143755_create_forms_table.php @@ -0,0 +1,40 @@ +bigIncrements('id'); + $table->string('title'); + $table->longText('description')->nullable(); + $table->bigInteger('amount')->nullable(); + $table->integer('pay_limit')->nullable(); + $table->integer('pay_count')->default(0); + $table->string('image')->nullable(); + $table->json('fields')->nullable(); + $table->boolean('default')->default(0); + $table->boolean('status')->default(1); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('forms'); + } +} diff --git a/core/database/migrations/2019_07_24_131447_create_factors_table.php b/core/database/migrations/2019_07_24_131447_create_factors_table.php new file mode 100644 index 0000000..4b207dc --- /dev/null +++ b/core/database/migrations/2019_07_24_131447_create_factors_table.php @@ -0,0 +1,38 @@ +bigIncrements('id'); + $table->string('title')->nullable(); + $table->bigInteger('amount'); + $table->integer('tax')->default(0); + $table->json('items')->nullable(); + $table->boolean('paid')->default(0); + $table->bigInteger('transaction_id')->nullable(); + $table->tinyInteger('status')->default(\App\Factor::$status['active']); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('factors'); + } +} diff --git a/core/database/migrations/2019_08_19_151712_create_files_table.php b/core/database/migrations/2019_08_19_151712_create_files_table.php new file mode 100644 index 0000000..cf441f3 --- /dev/null +++ b/core/database/migrations/2019_08_19_151712_create_files_table.php @@ -0,0 +1,41 @@ +bigIncrements('id'); + $table->string('title'); + $table->longText('description')->nullable(); + $table->bigInteger('amount')->nullable(); + $table->integer('pay_limit')->nullable(); + $table->integer('pay_count')->default(0); + $table->integer('expire_day')->nullable(); + $table->string('image')->nullable(); + $table->json('fields')->nullable(); + $table->string('file')->nullable(); + $table->boolean('status')->default(1); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('files'); + } +} diff --git a/core/database/migrations/2019_09_06_225148_add_prefix_fp_to_tables.php b/core/database/migrations/2019_09_06_225148_add_prefix_fp_to_tables.php new file mode 100644 index 0000000..6416794 --- /dev/null +++ b/core/database/migrations/2019_09_06_225148_add_prefix_fp_to_tables.php @@ -0,0 +1,40 @@ +tinyInteger('form_size')->default(4); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('fp_forms', function ($table) { + $table->dropColumn('form_size'); + }); + } +} diff --git a/core/database/migrations/2019_09_06_235958_add_form_size_field_to_fp_files_table.php b/core/database/migrations/2019_09_06_235958_add_form_size_field_to_fp_files_table.php new file mode 100644 index 0000000..13d5ec1 --- /dev/null +++ b/core/database/migrations/2019_09_06_235958_add_form_size_field_to_fp_files_table.php @@ -0,0 +1,32 @@ +tinyInteger('form_size')->default(4); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('fp_files', function ($table) { + $table->dropColumn('form_size'); + }); + } +} diff --git a/core/database/seeds/.gitkeep b/core/database/seeds/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/core/database/seeds/ConfigsTableSeeder.php b/core/database/seeds/ConfigsTableSeeder.php new file mode 100644 index 0000000..098d572 --- /dev/null +++ b/core/database/seeds/ConfigsTableSeeder.php @@ -0,0 +1,41 @@ + 'sadad_merchent', + 'value' => '', + 'label' => 'شماره پذیرنده' + ]); + + Config::create([ + 'key' => 'sadad_terminal', + 'value' => '', + 'label' => 'شماره ترمینال' + ]); + + Config::create([ + 'key' => 'sadad_api_keys', + 'value' => '', + 'label' => 'کلید ترمینال' + ]); + + + Config::create([ + 'key' => 'live_stats', + 'value' => false, + 'label' => lang('lang.live_stats'), + 'visible' => 0 + ]); + } +} diff --git a/core/database/seeds/DatabaseSeeder.php b/core/database/seeds/DatabaseSeeder.php new file mode 100644 index 0000000..572405b --- /dev/null +++ b/core/database/seeds/DatabaseSeeder.php @@ -0,0 +1,22 @@ +call(ConfigsTableSeeder::class); + $this->call(FormsTableSeeder::class); + + Model::reguard(); + } +} diff --git a/core/database/seeds/FormsTableSeeder.php b/core/database/seeds/FormsTableSeeder.php new file mode 100644 index 0000000..3b7447f --- /dev/null +++ b/core/database/seeds/FormsTableSeeder.php @@ -0,0 +1,33 @@ + 'فرم پرداخت‌', + 'description' => 'توضیحاتی در مورد فرم پرداخت', + 'fields' => [ + [ + 'name' => 'input_0', + 'label' => 'نام و نام خانوادگی‌', + 'required' => 1 + ], + [ + 'name' => 'input_1', + 'label' => 'شماره موبایل‌', + 'required' => 0 + ], + ], + 'default' => 1 + ]); + } +} diff --git a/core/resources/lang/fa/auth.php b/core/resources/lang/fa/auth.php new file mode 100644 index 0000000..d770ae3 --- /dev/null +++ b/core/resources/lang/fa/auth.php @@ -0,0 +1,19 @@ + 'اطلاعات وارد شده اشتباه می باشد', + 'throttle' => 'تعداد تلاش ناموفق برای ورود بیشتر از حد مجاز شد. لطفا بعد از :seconds ثانیه دوباره تلاش کنید.', + +]; diff --git a/core/resources/lang/fa/configs.php b/core/resources/lang/fa/configs.php new file mode 100644 index 0000000..7da0b8a --- /dev/null +++ b/core/resources/lang/fa/configs.php @@ -0,0 +1,7 @@ + 'آدرس سایت', + 'site_title' => '', + 'site_description' => '', +]; \ No newline at end of file diff --git a/core/resources/lang/fa/lang.php b/core/resources/lang/fa/lang.php new file mode 100644 index 0000000..bf7b6c7 --- /dev/null +++ b/core/resources/lang/fa/lang.php @@ -0,0 +1,140 @@ + 'ورود', + 'email' => 'ایمیل', + 'password' => 'کلمه عبور', + 'forget_your_password' => 'فراموشی کلمه عبور', + 'remember_me' => 'مرا به خاطر بسپار', + "description" => "توضیحات", + "not_found" => "متاسفانه صفحه مورد نظر شما پیدا نشد", + "is_required" => "الزامی است", + "must_be_numeric" => "باید به صورت عدد باشد", + "transaction_failed" => "تراکنش با خطا مواجه شد‌", + "details" => "جزئیات", + "transaction_details" => "جزئیات تراکنش", + "payir_transaction_id" => "شماره تراکنش", + "card_number" => "شماره کارت", + "logout" => "خروج از بخش مدیریت", + "dashboard" => "داشبورد", + "transactions" => "تراکنش ها", + "success" => "موفق", + "title" => "عنوان", + "failed" => "ناموفق", + "security_settings" => "تنظیمات امنیتی", + "id" => "شناسه", + "image" => "تصویر", + "actions" => "عملیات", + "amount" => "مبلغ (ریال)", + "edit" => "ویرایش", + "time" => "ساعت", + "required" => "اجباری", + "optional" => "اختیاری", + "delete" => "حذف", + "added" => "افزوده شد", + "rial" => "ریال", + "add" => "افزودن", + "date" => "تاریخ", + "current_password" => "کلمه عبور فعلی", + "new_password" => "کلمه عبور جدید", + "confirm_password" => "تایید کلمه عبور", + "save" => "ذخیره", + "inputs" => "ورودی ها", + "changes_saved" => "تغییرات ذخیره شد", + "status" => "وضعیت", + "update" => "بروزرسانی", + "check" => "بررسی", + "install_update" => "نصب بروزرسانی", + "you_are_using_latest_version" => "شما از آخرین نسخه استفاده میکنید", + "no_update_available" => "بروزرسانی جدیدی در دسترس نیست", + "there_is_new_release" => "نسخه جدیدی منتشر شده است", + "current_version" => "نسخه فعلی", + "updating" => "در حال بروزرسانی...", + "entering" => "وارد کردن", + "receipt" => "رسید پرداخت", + "repay" => "پرداخت مجدد", + "filter" => "فیلتر", + "configs" => "تنظیمات", + "factors" => "فاکتورهای پرداخت", + "default" => "پیشفرض", + "make_default" => "تبدیل به پیشفرض", + "forms" => "فرم های پرداخت", + "form_details" => "جزییات فرم", + "edit_form" => "ویرایش فرم", + "add_new_form" => "افزودن فرم جدید", + "cannot_delete_default_form" => "فرم پیشفرض را نمیتوان حذف کرد", + "add_new_factor" => "افزودن فاکتور جدید", + "edit_factor" => "ویرایش فاکتور", + "advanced" => "موارد پیشرفته", + "pay_limit" => "محدودیت تعداد پرداخت", + "fields" => "فیلد های فرم‌", + "add_new_field" => "افزودن فیلد جدید", + "required_field" => "فیلد اجباری", + "invalid_password" => "کلمه عبور وارد شده اشتباه است", + "to_optional_amount_leave_empty" => "برای اینکه فیلد مبلغ دلخواه باشد این فیلد را خالی بگذارید", + "to_unlimited_payment_leave_empty" => "برای تعداد نامحدود این فیلد را خالی بگذارید", + "demo_mode" => "در حالت دمو این امکان وجود ندارد", + "payment_receipt" => "رسید پرداخت", + "income" => "درآمد", + "transactions_count" => "تعداد تراکنش", + "active" => "فعال", + "deactive" => "غیر فعال", + "live_stats" => "آمار زنده", + "activate" => "فعال سازی", + "deactivate" => "غیرفعال سازی", + "pay" => "پرداخت‌", + "updated" => "بروزرسانی با موفقیت انجام شد", + "current_version_changes" => "تغییرات نسخه فعلی", + "new_version_changes" => "تغییرات نسخه جدید", + "paid" => "پرداخت شده", + "not_paid" => "پرداخت نشده", + "add_new_item" => "افزودن آیتم جدید", + "items" => "آیتم ها", + "tax" => "مالیات (درصد)", + "factor_min_items" => "فاکتور حداقل باید یک آیتم داشته باشد", + "send_factor" => "ارسال فاکتور", + "row" => "ردیف", + "item_name" => "نام محصول", + "item_count" => "تعداد", + "item_price" => "قیمت واحد (ریال)", + "item_description" => "توضیحات", + "total_amount" => "مبلغ کل (ریال)", + "factor" => "فاکتور", + "telegram" => "تلگرام", + "whatsapp" => "واتس اپ", + "previous" => "قبلی", + "next" => "بعدی", + "themes" => "قالب ها", + "theme_changed" => "قالب سایت تغییر داده شد", + "theme_not_found" => "قالب انتخاب شده موجود نمی باشد و یا حذف شده است", + "new_release_available" => "نسخه جدید", + "install_theme" => "نصب قالب جدید", + "install" => "نصب", + "theme_installed" => "قالب با موفقیت نصب شد", + "theme_not_installed" => "خطا در نصب قالب", + "theme_already_installed" => "قالب مورد نظر قبلا نصب شده است. برای بروزرسانی قالب فعلی را حذف کنید و سپس قالب جدید را نصب کنید", + "version" => "نسخه", + "server_error" => "خطای سرور", + "scripts" => "اسکریپت ها", + "styles" => "استایل های css", + "download" => "دانلود", + "download_link" => "لینک دانلود", + "sell_file" => "فروش فایل", + "files" => "فایل ها", + "file" => "فایل", + "file_details" => "جزییات فایل", + "edit_file" => "ویرایش فایل", + "add_new_file" => "فروش فایل جدید", + "sell_count" => "تعداد فروش", + "expire_day_title" => "لینک دانلود بعد از چند روز از خرید منقضی شود؟", + "type" => "نوع", + "share" => "اشتراک گذاری", + "form_size" => "اندازه عرض فرم پرداخت", + "form_size_lg" => "بزرگ", + "form_size_md" => "متوسط", + "form_size_sm" => "کوچک", + "reset_password" => "بازیابی کلمه عبور", + "factor_is_paid" => "پرداخت شده", + "new_transaction" => "تراکنش جدید", + "view" => "مشاهده", +]; diff --git a/core/resources/lang/fa/pagination.php b/core/resources/lang/fa/pagination.php new file mode 100644 index 0000000..e00adbf --- /dev/null +++ b/core/resources/lang/fa/pagination.php @@ -0,0 +1,19 @@ + '« قبلی', + 'next' => 'بعدی »', + +]; diff --git a/core/resources/lang/fa/passwords.php b/core/resources/lang/fa/passwords.php new file mode 100644 index 0000000..24a9395 --- /dev/null +++ b/core/resources/lang/fa/passwords.php @@ -0,0 +1,22 @@ + 'کلمه عبور باید حداقل ۸ کاراکتر باشد.', + 'reset' => 'کلمه عبور شما بازیابی شد!', + 'sent' => 'ایمیل بازیابی کلمه عبور برای شما ارسال شد!', + 'token' => 'توکن بازیابی کلمه عبور اشتباه می باشد.', + 'user' => "آدرس ایمیل وارد شده اشتباه می باشد.", + +]; diff --git a/core/resources/lang/fa/validation.php b/core/resources/lang/fa/validation.php new file mode 100644 index 0000000..3f908c6 --- /dev/null +++ b/core/resources/lang/fa/validation.php @@ -0,0 +1,95 @@ + 'نام باید به انگلیسی وارد شود.', + 'accepted' => ':attribute بایستی تایید شود.', + 'active_url' => ':attribute یک آدرس معتبر نمی باشد.', + 'after' => ':attribute باید تاریخی بعد از :date باشد.', + 'alpha' => ':attribute فقط می تواند حروف باشد.', + 'alpha_dash' => ':attribute فقط می تواند حروف و اعداد و (-) باشد.', + 'alpha_num' => ':attribute فقط می تواند حروف و اعداد باشد.', + 'array' => ':attribute فقط می تواند آرایه باشد.', + 'before' => ':attribute باید تاریخی قبل از :date باشد.', + 'between' => [ + 'numeric' => ':attribute فقط می تواند بین :min و :max باشد.', + 'file' => ':attribute فقط می تواند بین :min و :max کیلوبایت باشد.', + 'string' => ':attribute فقط می تواند بین :min و :max کاراکتر باشد..', + 'array' => ':attribute فقط می تواند بین :min و :max آیتم باشد.', + ], + 'boolean' => ':attribute فقط می تواند درست یا نادرست باشد.', + 'confirmed' => ':attribute مطابقت ندارد.', + 'date' => ':attribute یک تاریخ معتبر نمی باشد.', + 'date_format' => ':attribute با فرمت :format مطابقت ندارد.', + 'different' => ':attribute و :other بایستی متفاوت باشند.', + 'digits' => ':attribute بایستی :digits رقم باشد.', + 'digits_between' => ':attribute بایستی بین :min و :max عدد باشد.', + 'email' => 'فرمت :attribute صحیح نمی باشد.', + 'exists' => ':attribute انتخاب شده موجود نمی باشد.', + 'filled' => 'پر کردن :attribute الزامی می باشد.', + 'image' => ':attribute بایستی فرمت تصویر داشته باشد.', + 'in' => ':attribute انتخاب شده صحیح نمی باشد.', + 'integer' => ':attribute بایستی یک عدد صحیح باشد.', + 'ip' => 'The :attribute must be a valid IP address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'max' => [ + 'numeric' => ':attribute نباید از :max بزرگتر باشد.', + 'file' => ':attribute نباید بزرگتر از :max کیلوبایت باشد.', + 'string' => ':attribute نباید بزرگتر از :max کاراکتر باشد.', + 'array' => ':attribute نباید بزرگتر از :max آیتم باشد.', + ], + 'mimes' => 'فرمت :attribute باید :values باشد.', + 'min' => [ + 'numeric' => ':attribute حداقل باید :min باشد.', + 'file' => ':attribute حداقل باید :min کیلوبایت باشد.', + 'string' => ':attribute حداقل باید :min کاراکتر باشد.', + 'array' => ':attribute حداقل باید :min آیتم باشد.', + ], + 'not_in' => 'مورد انتخاب شده وجود ندارد.', + 'numeric' => ':attribute باید بصورت عدد باشد.', + 'regex' => 'فرمت :attribute صحیح نمی باشد.', + 'required' => 'وارد کردن :attribute الزامی می باشد.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values is present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size' => [ + 'numeric' => ':attribute باید :size رقم باشد.', + 'file' => 'The :attribute must be :size kilobytes.', + 'string' => 'The :attribute must be :size characters.', + 'array' => 'The :attribute must contain :size items.', + ], + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid zone.', + 'unique' => 'با :attribute وارد شده حسابی در سیستم وجود دارد.', + 'url' => 'فرمت :attribute صحیح نمی باشد', + 'greater_than_rial' => 'مبلغ باید بیشتر از 1000 ریال باشد', + 'custom' => [ + ], + 'city_belong' => 'شهر انتخاب شده با استان مطابقت ندارد.', + 'greater_than_toman' => ':attribute وارد شده بایستی بزرگتر از 100 تومان باشد', + 'image_size' => 'حجم تصویر ارسالی نباید بیشتر از 1 مگابایت باشد', + 'attributes' => [ + 'id' => 'شناسه', + 'first_name' => 'نام', + 'last_name' => 'نام خانوادگی', + 'email' => 'ایمیل', + 'password' => 'کلمه عبور', + 'username' => 'نام کاربری', + 'amount' => 'مبلغ', + 'bank_sheba' => 'شماره شبا', + 'site_url' => 'آدرس سایت', + 'site_title' => 'عنوان سایت', + 'site_description' => 'توضیحات سایت', + 'db_host' => 'آدرس دیتابیس', + 'db_name' => 'نام دیتابیس', + 'db_username' => 'نام کاربری دیتابیس', + 'db_password' => 'کلمه عبور دیتابیس', + 'admin_email' => 'آدرس ایمیل مدیر سیستم', + 'admin_password' => 'کلمه عبور مدیر سیستم', + 'title' => 'عنوان‌', + 'current_password' => 'کلمه عبور فعلی', + 'tax' => 'مالیات', + 'file' => 'فایل', + ] +]; diff --git a/core/resources/views/admin/configs/index.blade.php b/core/resources/views/admin/configs/index.blade.php new file mode 100644 index 0000000..66bcb59 --- /dev/null +++ b/core/resources/views/admin/configs/index.blade.php @@ -0,0 +1,51 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.configs') }}@endsection + +@section('content') +
+
{{ lang('lang.configs') }}
+
+
+
+
+ {{ csrf_field() }} + @foreach($configs as $config) +
+ + +
+ @endforeach + +
+
+
+
+
+
+
+
+
{{ lang('lang.scripts') }}
+
+
+ {{ csrf_field() }} + + +
+
+
+
+
+
+
{{ lang('lang.styles') }}
+
+
+ {{ csrf_field() }} + + +
+
+
+
+
+@endsection diff --git a/core/resources/views/admin/dashboard.blade.php b/core/resources/views/admin/dashboard.blade.php new file mode 100644 index 0000000..ed48d96 --- /dev/null +++ b/core/resources/views/admin/dashboard.blade.php @@ -0,0 +1,166 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.dashboard') }}@endsection + +@section('content') +
+
+
+
+ {{ lang('lang.income') }} +
+ + +
+
+
+ +
+
+
+
+
+
+ {{ lang('lang.transactions_count') }} +
+ + +
+
+
+ +
+
+
+
+@endsection + +@push('styles') + +@endpush + +@push('scripts') + + +@endpush diff --git a/core/resources/views/admin/factors/add.blade.php b/core/resources/views/admin/factors/add.blade.php new file mode 100644 index 0000000..ae377fe --- /dev/null +++ b/core/resources/views/admin/factors/add.blade.php @@ -0,0 +1,88 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.add_new_factor') }}@endsection + +@section('content') +
+
+ {{ lang('lang.add_new_factor') }} +
+
+
+
+
+ {{ csrf_field() }} +
+ + +
+
+ + +
+
+ + {{ lang('lang.add_new_item') }} +
+
+
+ + + + +
+
+
+
+
+ +
+
+
+
+
+
+@endsection + +@push('styles') + +@endpush + +@push('scripts') + +@endpush diff --git a/core/resources/views/admin/factors/edit.blade.php b/core/resources/views/admin/factors/edit.blade.php new file mode 100644 index 0000000..4cdfb98 --- /dev/null +++ b/core/resources/views/admin/factors/edit.blade.php @@ -0,0 +1,93 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.edit_factor') }}@endsection + +@section('content') +
+
+ {{ lang('lang.edit_factor') }} +
+
+
+
+
+ {{ csrf_field() }} +
+ + +
+
+ + +
+
+ + {{ lang('lang.add_new_item') }} +
+ @foreach($factor->items as $key => $item) +
+
+ + + + + @if($key > 0) + حذف + @endif +
+
+ @endforeach +
+
+
+ +
+
+
+
+
+
+@endsection + +@push('styles') + +@endpush + +@push('scripts') + +@endpush diff --git a/core/resources/views/admin/factors/index.blade.php b/core/resources/views/admin/factors/index.blade.php new file mode 100644 index 0000000..8a0ca2c --- /dev/null +++ b/core/resources/views/admin/factors/index.blade.php @@ -0,0 +1,87 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.factors') }}@endsection + +@section('content') +
+
{{ lang('lang.filter') }}
+
+
+
+ + +
+
+ +
+
+
+
+
+
+ {{ lang('lang.factors') }} + {{ lang('lang.add_new_factor') }} +
+
+
+ + + + + + + + + + + + @foreach ($factors as $factor) + + + + + + + + @endforeach + +
{{ lang('lang.id') }}{{ lang('lang.title') }}{{ lang('lang.amount') }}{{ lang('lang.status') }}{{ lang('lang.actions') }}
{{ $factor->id }} + {{ $factor->title }} + {{ custom_money_format($factor->amount) }} + @if($factor->paid) + {{ lang('lang.paid') }} + {{ lang('lang.transaction_details') }} + @else + {{ lang('lang.not_paid') }} + @endif + + @if(!$factor->paid) +
+ + +
+ @endif +
+ + +
+
+
+
+ @include('fp::extensions.pagination', ['paginator' => $factors]) +
+
+
+@endsection diff --git a/core/resources/views/admin/files/add.blade.php b/core/resources/views/admin/files/add.blade.php new file mode 100644 index 0000000..31eee53 --- /dev/null +++ b/core/resources/views/admin/files/add.blade.php @@ -0,0 +1,125 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.add_new_file') }}@endsection + +@section('content') +
+
+ {{ lang('lang.add_new_file') }} +
+
+
+
+
+ {{ csrf_field() }} +
+ + +
+
+ + + @include('extensions.editor', ['element' => '#txt-description']) +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ +
+
+
+
+
+
+@endsection + +@push('styles') + +@endpush + +@push('scripts') + +@endpush diff --git a/core/resources/views/admin/files/edit.blade.php b/core/resources/views/admin/files/edit.blade.php new file mode 100644 index 0000000..8783ab5 --- /dev/null +++ b/core/resources/views/admin/files/edit.blade.php @@ -0,0 +1,125 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.edit_file') }}@endsection + +@section('content') +
+
+ {{ lang('lang.edit_file') }} +
+
+
+
+
+ {{ csrf_field() }} +
+ + +
+
+ + + @include('extensions.editor', ['element' => '#txt-description']) +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ form_size == 12) checked @endif> + +
+
+ form_size == 8) checked @endif> + +
+
+ form_size == 4) checked @endif> + +
+
+
+
+ + +
+
+ + +
+
+ + {{ lang('lang.add_new_field') }} +
+ @foreach($file->fields as $key => $field) +
+
+
+ +
+ + حذف +
+
+ @endforeach +
+
+
+
+ +
+
+
+
+
+
+@endsection + +@push('styles') + +@endpush + +@push('scripts') + +@endpush diff --git a/core/resources/views/admin/files/index.blade.php b/core/resources/views/admin/files/index.blade.php new file mode 100644 index 0000000..f464b5b --- /dev/null +++ b/core/resources/views/admin/files/index.blade.php @@ -0,0 +1,65 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.files') }}@endsection + +@section('content') +
+
+ {{ lang('lang.files') }} + {{ lang('lang.add_new_file') }} +
+
+
+ + + + + + + + + + + + @foreach ($files as $file) + + + + + + + + @endforeach + +
{{ lang('lang.id') }}{{ lang('lang.title') }}{{ lang('lang.amount') }}{{ lang('lang.sell_count') }}{{ lang('lang.actions') }}
{{ $file->id }} + {{ $file->title }} + @if($file->default) + {{ lang('lang.default') }} + @endif + {{ custom_money_format($file->amount) }}{{ $file->pay_count }} +
+ + +
+
+ + +
+
+
+
+ @include('fp::extensions.pagination', ['paginator' => $files]) +
+
+
+@endsection diff --git a/core/resources/views/admin/forms/add.blade.php b/core/resources/views/admin/forms/add.blade.php new file mode 100644 index 0000000..58cadf4 --- /dev/null +++ b/core/resources/views/admin/forms/add.blade.php @@ -0,0 +1,117 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.add_new_form') }}@endsection + +@section('content') +
+
+ {{ lang('lang.add_new_form') }} +
+
+
+
+
+ {{ csrf_field() }} +
+ + +
+
+ + + @include('extensions.editor', ['element' => '#txt-description']) +
+
+ + +
+ + +
+ +
+
+
+
+
+
+@endsection + +@push('styles') + +@endpush + +@push('scripts') + +@endpush diff --git a/core/resources/views/admin/forms/edit.blade.php b/core/resources/views/admin/forms/edit.blade.php new file mode 100644 index 0000000..2588c0e --- /dev/null +++ b/core/resources/views/admin/forms/edit.blade.php @@ -0,0 +1,117 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.edit_form') }}@endsection + +@section('content') +
+
+ {{ lang('lang.edit_form') }} +
+
+
+
+
+ {{ csrf_field() }} +
+ + +
+
+ + + @include('extensions.editor', ['element' => '#txt-description']) +
+
+ + +
+
+
+ +
+
+ form_size == 12) checked @endif> + +
+
+ form_size == 8) checked @endif> + +
+
+ form_size == 4) checked @endif> + +
+
+
+
+ + +
+
+ + +
+
+ + {{ lang('lang.add_new_field') }} +
+ @foreach($form->fields as $key => $field) +
+
+
+ +
+ + حذف +
+
+ @endforeach +
+
+
+
+ +
+
+
+
+
+
+@endsection + +@push('styles') + +@endpush + +@push('scripts') + +@endpush diff --git a/core/resources/views/admin/forms/index.blade.php b/core/resources/views/admin/forms/index.blade.php new file mode 100644 index 0000000..6a92ef3 --- /dev/null +++ b/core/resources/views/admin/forms/index.blade.php @@ -0,0 +1,66 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.forms') }}@endsection + +@section('content') +
+
+ {{ lang('lang.forms') }} + {{ lang('lang.add_new_form') }} +
+
+
+ + + + + + + + + + + @foreach ($forms as $form) + + + + + + + @endforeach + +
{{ lang('lang.id') }}{{ lang('lang.title') }}{{ lang('lang.amount') }}{{ lang('lang.actions') }}
{{ $form->id }} + {{ $form->title }} + @if($form->default) + {{ lang('lang.default') }} + @endif + {{ $form->amount ? custom_money_format($form->amount) : 'دلخواه' }} +
+ + +
+
+ + +
+
+
+
+ @include('fp::extensions.pagination', ['paginator' => $forms]) +
+
+
+@endsection diff --git a/core/resources/views/admin/security-settings/index.blade.php b/core/resources/views/admin/security-settings/index.blade.php new file mode 100644 index 0000000..0b068b1 --- /dev/null +++ b/core/resources/views/admin/security-settings/index.blade.php @@ -0,0 +1,31 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.security_settings') }}@endsection + +@section('content') +
+
{{ lang('lang.security_settings') }}
+
+
+
+
+ {{ csrf_field() }} +
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+@endsection diff --git a/core/resources/views/admin/theme/index.blade.php b/core/resources/views/admin/theme/index.blade.php new file mode 100644 index 0000000..057c7c6 --- /dev/null +++ b/core/resources/views/admin/theme/index.blade.php @@ -0,0 +1,75 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.themes') }}@endsection + +@section('content') +
در حال حاضر دو قالب تیره و روشن در سیستم وجود دارد.
+
+
{{ lang('lang.install_theme') }}
+
+
+ {!! csrf_field() !!} +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
{{ lang('lang.themes') }}
+
+ @if(count($themes)) + @foreach($themes as $theme) + +
+ +
+ @endforeach + @else +
هنوز قالبی نصب نکرده اید.
+ @endif +
+
+@endsection + +@push('styles') + +@endpush diff --git a/core/resources/views/admin/transactions/detail.blade.php b/core/resources/views/admin/transactions/detail.blade.php new file mode 100644 index 0000000..0d90b56 --- /dev/null +++ b/core/resources/views/admin/transactions/detail.blade.php @@ -0,0 +1,153 @@ + + + +@include('fp::partials.admin.header') + + +
+
+

{{ lang('lang.transaction_details') }}

+
+
+ + + + + + + + + + + + + + + @if(isset($transaction->payment_info['trans_id'])) + + + + + @endif + @if(isset($transaction->payment_info['card_number'])) + + + + + @endif + @if($transaction->type == \App\Transaction::$type['form'] && $transaction->form()) + + + + + @if($transaction->details && isset($transaction->details['form_fields'])) + + + + + @endif + @endif + @if($transaction->type == \App\Transaction::$type['file'] && $transaction->file()) + + + + + @if($transaction->details && isset($transaction->details['file_fields'])) + + + + + @endif + @endif + @if($transaction->type == \App\Transaction::$type['factor'] && $transaction->factor()) + + + + + @if($transaction->details && isset($transaction->details['factor_items'])) + + + + + @endif + @endif + + + + + +
+ {{ lang('lang.id') }} + {{ $transaction->id }}
+ {{ lang('lang.amount') }} + {{ custom_money_format($transaction->amount) }}
+ {{ lang('lang.status') }} + + {{ $transaction->status && $transaction->verified ? lang('lang.success') : lang('lang.failed') }} +
+ {{ lang('lang.payir_transaction_id') }} + + {{ $transaction->payment_info['trans_id'] }} +
+ {{ lang('lang.card_number') }} + + {{ $transaction->payment_info['card_number'] }} +
{{ lang('lang.form_details') }} + {{ $transaction->form()->title }} +
+ {{ lang('lang.inputs') }} + + @foreach ($transaction->details['form_fields'] as $input) + {{ $input['label'] }} : {{ $input['value'] }}
+ @endforeach +
{{ lang('lang.form_details') }} + {{ $transaction->file()->title }} +
+ {{ lang('lang.inputs') }} + + @foreach ($transaction->details['file_fields'] as $input) + {{ $input['label'] }} : {{ $input['value'] }}
+ @endforeach +
{{ lang('lang.title') }} + {{ $transaction->factor()->title }} +
+ {{ lang('lang.factor') }} + + + + + + + + + + + + + @foreach($transaction->details['factor_items'] as $key => $item) + + + + + + + + @endforeach + +
{{ lang('lang.row') }}{{ lang('lang.item_name') }}{{ lang('lang.item_count') }}{{ lang('lang.item_price') }}{{ lang('lang.item_description') }}
{{ $key + 1 }}{{ $item['name'] }}{{ $item['count'] }}{{ $item['price'] }}{{ $item['description'] }}
+ + + + + + + + + +
{{ lang('lang.tax') }}{{ $transaction->factor()->tax }}{{ lang('lang.total_amount') }}{{ custom_money_format($transaction->factor()->amount) }}
+
+ {{ lang('lang.date') }} + {{ $transaction->full_jalali_created_at }}
+
+
+ diff --git a/core/resources/views/admin/transactions/index.blade.php b/core/resources/views/admin/transactions/index.blade.php new file mode 100644 index 0000000..6f60481 --- /dev/null +++ b/core/resources/views/admin/transactions/index.blade.php @@ -0,0 +1,79 @@ +@extends('fp::layouts.admin') + +@section('page-title'){{ lang('lang.transactions') }}@endsection + +@section('content') +
+
{{ lang('lang.filter') }}
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+
{{ lang('lang.transactions') }}
+
+
+ + + + + + + + + + + + + @foreach ($transactions as $transaction) + + + + + + + + + @endforeach + +
{{ lang('lang.id') }}{{ lang('lang.type') }}{{ lang('lang.amount') }}{{ lang('lang.date') }}{{ lang('lang.status') }}{{ lang('lang.actions') }}
{{ $transaction->id }}{{ \App\Transaction::$typeLabels[$transaction->type] }}{{ custom_money_format($transaction->amount) }}{{ $transaction->full_jalali_created_at }} + {{ $transaction->status && $transaction->verified ? lang('lang.success') : lang('lang.failed') }} + + {{ lang('lang.details') }} +
+
+
+ @include('fp::extensions.pagination', ['paginator' => $transactions]) +
+
+
+@endsection diff --git a/core/resources/views/auth/login.blade.php b/core/resources/views/auth/login.blade.php new file mode 100644 index 0000000..87b8f92 --- /dev/null +++ b/core/resources/views/auth/login.blade.php @@ -0,0 +1,41 @@ +@extends('fp::layouts.admin') + +@section('content') +
+
+
+
+
{{ lang('lang.login') }}
+
+
+ {{ csrf_field() }} + @include('fp::extensions.alert') +
+ + +
+
+ + +
+
+
+ + +
+
+
+ + {{ lang('lang.forget_your_password') }} +
+
+
+
+
+
+
+@endsection diff --git a/core/resources/views/auth/password.blade.php b/core/resources/views/auth/password.blade.php new file mode 100644 index 0000000..4787ce2 --- /dev/null +++ b/core/resources/views/auth/password.blade.php @@ -0,0 +1,28 @@ +@extends('fp::layouts.admin') + +@section('content') +
+
+
+
+
{{ lang('lang.reset_password') }}
+
+
+ {{ csrf_field() }} + @include('fp::extensions.alert') +
+ + +
+
+ +
+
+
+
+
+
+
+@endsection \ No newline at end of file diff --git a/core/resources/views/auth/reset.blade.php b/core/resources/views/auth/reset.blade.php new file mode 100644 index 0000000..344b618 --- /dev/null +++ b/core/resources/views/auth/reset.blade.php @@ -0,0 +1,37 @@ +@extends('fp::layouts.admin') + +@section('content') +
+
+
+
+
{{ lang('lang.reset_password') }}
+
+
+ {{ csrf_field() }} + @include('fp::extensions.alert') + +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+
+
+@endsection \ No newline at end of file diff --git a/core/resources/views/emails/password.blade.php b/core/resources/views/emails/password.blade.php new file mode 100644 index 0000000..76a9ed4 --- /dev/null +++ b/core/resources/views/emails/password.blade.php @@ -0,0 +1,3 @@ +برای بازیابی کلمه عبور بر روی لینک زیر کلیک کنید: +
+{{ url('/password/reset/'.$token) }} diff --git a/core/resources/views/emails/transaction.blade.php b/core/resources/views/emails/transaction.blade.php new file mode 100644 index 0000000..87ad359 --- /dev/null +++ b/core/resources/views/emails/transaction.blade.php @@ -0,0 +1,55 @@ +
+

{{ lang('lang.new_transaction') }}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ lang('lang.id') }} + {{ $transaction->id }}
+ {{ lang('lang.amount') }} + {{ custom_money_format($transaction->amount) }}
+ {{ lang('lang.status') }} + {{ $transaction->status ? lang('lang.success') : lang('lang.failed') }}
+ {{ lang('lang.payir_transaction_id') }} + {{ $transaction->payment_info['trans_id'] }}
+ {{ lang('lang.card_number') }} + + {{ $transaction->payment_info['card_number'] }} +
+ {{ lang('lang.date') }} + {{ $transaction->full_jalali_created_at }}
+ {{ lang('lang.transaction_details') }} + + {{ lang('lang.view') }} +
+
+
diff --git a/core/resources/views/errors/404.blade.php b/core/resources/views/errors/404.blade.php new file mode 100644 index 0000000..61a79a8 --- /dev/null +++ b/core/resources/views/errors/404.blade.php @@ -0,0 +1,47 @@ + + + + 404 Not Found + + + + + + +
+
+
404 Not Found
+
+
+ + diff --git a/core/resources/views/errors/500.blade.php b/core/resources/views/errors/500.blade.php new file mode 100644 index 0000000..0c0adef --- /dev/null +++ b/core/resources/views/errors/500.blade.php @@ -0,0 +1,47 @@ + + + + Error 500 + + + + + + +
+
+
Error 500
+
+
+ + diff --git a/core/resources/views/errors/503.blade.php b/core/resources/views/errors/503.blade.php new file mode 100644 index 0000000..3d3b00f --- /dev/null +++ b/core/resources/views/errors/503.blade.php @@ -0,0 +1,47 @@ + + + + Error 503 + + + + + + +
+
+
Error 503
+
+
+ + diff --git a/core/resources/views/extensions/alert.blade.php b/core/resources/views/extensions/alert.blade.php new file mode 100644 index 0000000..7f5c533 --- /dev/null +++ b/core/resources/views/extensions/alert.blade.php @@ -0,0 +1,25 @@ +@if (isset($errors) && count($errors) == 1) +
+ @foreach ($errors->all() as $error) + {!! $error !!} + @endforeach +
+@elseif(isset($errors) && count($errors) > 1) +
+

+ @foreach ($errors->all() as $error) + {!! $error !!}
+ @endforeach +

+
+@endif +@if(session('alert')) +
+ {!! session('message') !!} +
+@endif +@if(session('status')) +
+ {!! session('status') !!} +
+@endif \ No newline at end of file diff --git a/core/resources/views/extensions/editor.blade.php b/core/resources/views/extensions/editor.blade.php new file mode 100644 index 0000000..da07acb --- /dev/null +++ b/core/resources/views/extensions/editor.blade.php @@ -0,0 +1,13 @@ +@push('styles') + +@endpush + +@push('scripts') + + +@endpush \ No newline at end of file diff --git a/core/resources/views/extensions/pagination.blade.php b/core/resources/views/extensions/pagination.blade.php new file mode 100644 index 0000000..095abf9 --- /dev/null +++ b/core/resources/views/extensions/pagination.blade.php @@ -0,0 +1,35 @@ +@if ($paginator->lastPage() > 1) +
    +
  • + {{ lang('lang.previous') }} +
  • + @if($paginator->currentPage() > 3) +
  • + 1 +
  • + @endif + @if($paginator->currentPage() > 4) +
  • ...
  • + @endif + @foreach(range(1, $paginator->lastPage()) as $i) + @if($i >= $paginator->currentPage() - 2 && $i <= $paginator->currentPage() + 2) + @if ($i == $paginator->currentPage()) +
  • {{ $i }}
  • + @else +
  • + {{ $i }} +
  • + @endif + @endif + @endforeach + @if($paginator->currentPage() < $paginator->lastPage() - 3) +
  • ...
  • + @endif + @if($paginator->currentPage() < $paginator->lastPage() - 2) +
  • {{ $paginator->lastPage() }}
  • + @endif +
  • + {{ lang('lang.next') }} +
  • +
+@endif \ No newline at end of file diff --git a/core/resources/views/home/factor/index.blade.php b/core/resources/views/home/factor/index.blade.php new file mode 100644 index 0000000..663461a --- /dev/null +++ b/core/resources/views/home/factor/index.blade.php @@ -0,0 +1,67 @@ +@extends('fp::layouts.home') + +@section('page-title') + {{ $factor->title }} +@endsection + +@section('content') +
+
+
+
+
+ {!! csrf_field() !!} +
+

+ {{ $factor->title }} + @if($factor->paid) + {{ lang('lang.factor_is_paid') }} + @endif +

+
+ + + + + + + + + + + + @foreach($factor->items as $key => $item) + + + + + + + + @endforeach + +
{{ lang('lang.row') }}{{ lang('lang.item_name') }}{{ lang('lang.item_count') }}{{ lang('lang.item_price') }}{{ lang('lang.item_description') }}
{{ $key + 1 }}{{ $item['name'] }}{{ $item['count'] }}{{ $item['price'] }}{{ $item['description'] }}
+ + + + + + + + + +
{{ lang('lang.tax') }}{{ $factor->tax }}{{ lang('lang.total_amount') }}{{ custom_money_format($factor->amount) }}
+
+ @include('fp::extensions.alert') + @if(!$factor->paid) +
+ +
+ @endif +
+
+
+
+
+
+@endsection diff --git a/core/resources/views/home/file/index.blade.php b/core/resources/views/home/file/index.blade.php new file mode 100644 index 0000000..d991db8 --- /dev/null +++ b/core/resources/views/home/file/index.blade.php @@ -0,0 +1,46 @@ +@extends('fp::layouts.home') + +@section('page-title') + {{ $file->title }} +@endsection + +@section('content') +
+
+
+
+
+ {!! csrf_field() !!} +
+

{{ $file->title }}

+ @if($file->description) +

{!! $file->description !!}

+ @endif + @if($file->image) +
+ + {{ $file->title }} + +
+ @endif + @if($file->fields) + @foreach($file->fields as $key => $f) +
+ +
+ @endforeach + @endif +
+ amount) disabled="disabled" value="{{ custom_money_format($file->amount) }} {{ lang('lang.rial') }}" @else value="{{ old('amount') }}" @endif required> +
+ @include('fp::extensions.alert') +
+ +
+
+
+
+
+
+
+@endsection diff --git a/core/resources/views/home/form/index.blade.php b/core/resources/views/home/form/index.blade.php new file mode 100644 index 0000000..c1b32d0 --- /dev/null +++ b/core/resources/views/home/form/index.blade.php @@ -0,0 +1,46 @@ +@extends('fp::layouts.home') + +@section('page-title') + {{ $form->title }} +@endsection + +@section('content') +
+
+
+
+
+ {!! csrf_field() !!} +
+

{{ $form->title }}

+ @if($form->description) +

{!! $form->description !!}

+ @endif + @if($form->image) +
+ + {{ $form->title }} + +
+ @endif + @if($form->fields) + @foreach($form->fields as $key => $f) +
+ +
+ @endforeach + @endif +
+ amount) disabled="disabled" value="{{ custom_money_format($form->amount) }} {{ lang('lang.rial') }}" @else value="{{ old('amount') }}" @endif required> +
+ @include('fp::extensions.alert') +
+ +
+
+
+
+
+
+
+@endsection diff --git a/core/resources/views/home/receipt.blade.php b/core/resources/views/home/receipt.blade.php new file mode 100644 index 0000000..a0de666 --- /dev/null +++ b/core/resources/views/home/receipt.blade.php @@ -0,0 +1,75 @@ +@extends('fp::layouts.home') + +@section('page-title') + {{ lang('lang.payment_receipt') }} +@endsection + +@section('content') +
+
+
+
+
+

{{ lang('lang.receipt') }}

+ @if($transaction && $transaction->status && $transaction->verified) +
+ + + + + + + + + + + + + + + + + + + + + + + + @if($transaction->type == \App\Transaction::$type['file']) + + + + + @endif + +
+ {{ lang('lang.id') }} + {{ $transaction->id }}
+ {{ lang('lang.amount') }} + {{ custom_money_format($transaction->amount) }}
+ {{ lang('lang.status') }} + {{ $transaction->status ? lang('lang.success') : lang('lang.failed') }}
+ {{ lang('lang.payir_transaction_id') }} + {{ $transaction->payment_info['trans_id'] }}
+ {{ lang('lang.date') }} + {{ $transaction->full_jalali_created_at }}
+ {{ lang('lang.download') }} + + {{ lang('lang.download_link') }} +
+
+ @else +
+ {{ lang('lang.transaction_failed') }} +
+ @if($transaction) + {{ lang('lang.repay') }} + @endif + @endif +
+
+
+
+
+@endsection diff --git a/core/resources/views/install/complete.blade.php b/core/resources/views/install/complete.blade.php new file mode 100644 index 0000000..bf84d11 --- /dev/null +++ b/core/resources/views/install/complete.blade.php @@ -0,0 +1,67 @@ + + + + + نصب سیستم آسان پرداخت الکترونیک سداد + + + + + +
+
+
+

نصب اسکریپت

+
+
+
+
+
+ {{ csrf_field() }} +
+
+ @include('extensions.alert') +
+ : {{ old('site_url') }} + +
+
+ : {{ old('site_title') }} + +
+
+ : {{ old('site_description') }} + +
+
+
+ : {{ old('db_host') }} +
+
+ : {{ old('db_name') }} +
+
+ : {{ old('db_username') }} +
+
+ : ****** +
+
+
+ : {{ old('admin_email') }} + +
+
+ : ****** + +
+
+
+
+ ویرایش اطلاعات‌ + +
+
+ + + diff --git a/core/resources/views/install/index.blade.php b/core/resources/views/install/index.blade.php new file mode 100644 index 0000000..b3116af --- /dev/null +++ b/core/resources/views/install/index.blade.php @@ -0,0 +1,70 @@ + + + + + نصب سیستم آسان پرداخت الکترونیک سداد + + + + + +
+
+
+

نصب اسکریپت

+
+
+
+
+
+ {{ csrf_field() }} +
+
+ @include('extensions.alert') +
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ +
+
+ + + diff --git a/core/resources/views/layouts/admin.blade.php b/core/resources/views/layouts/admin.blade.php new file mode 100644 index 0000000..8cde46d --- /dev/null +++ b/core/resources/views/layouts/admin.blade.php @@ -0,0 +1,12 @@ + + + +@include('fp::partials.admin.header') + + +@include('fp::partials.admin.navbar') +@include('fp::partials.admin.main') +@include('fp::partials.admin.footer') + + + diff --git a/core/resources/views/layouts/home.blade.php b/core/resources/views/layouts/home.blade.php new file mode 100644 index 0000000..e5b6e75 --- /dev/null +++ b/core/resources/views/layouts/home.blade.php @@ -0,0 +1,11 @@ + + + +@include('fp::partials.home.header') + + +@yield('content') +@include('fp::partials.home.footer') + + + diff --git a/core/resources/views/partials/admin/content.blade.php b/core/resources/views/partials/admin/content.blade.php new file mode 100644 index 0000000..e700328 --- /dev/null +++ b/core/resources/views/partials/admin/content.blade.php @@ -0,0 +1,4 @@ +
+ @include('fp::extensions.alert') + @yield('content') +
\ No newline at end of file diff --git a/core/resources/views/partials/admin/footer.blade.php b/core/resources/views/partials/admin/footer.blade.php new file mode 100644 index 0000000..c1eb1e6 --- /dev/null +++ b/core/resources/views/partials/admin/footer.blade.php @@ -0,0 +1,11 @@ + + + + + +@stack('scripts') \ No newline at end of file diff --git a/core/resources/views/partials/admin/header.blade.php b/core/resources/views/partials/admin/header.blade.php new file mode 100644 index 0000000..5d55445 --- /dev/null +++ b/core/resources/views/partials/admin/header.blade.php @@ -0,0 +1,8 @@ + + + + + @yield('page-title') + + @stack('styles') + \ No newline at end of file diff --git a/core/resources/views/partials/admin/main.blade.php b/core/resources/views/partials/admin/main.blade.php new file mode 100644 index 0000000..a71e843 --- /dev/null +++ b/core/resources/views/partials/admin/main.blade.php @@ -0,0 +1,19 @@ +
+
+ @if(auth()->check()) +
+ @include('fp::partials.admin.sidebar') + @include('fp::partials.admin.content') +
+ @else + @yield('content') + @endif +
+ +
+
+

توسعه توسط فراداده

+
+ + +
\ No newline at end of file diff --git a/core/resources/views/partials/admin/navbar.blade.php b/core/resources/views/partials/admin/navbar.blade.php new file mode 100644 index 0000000..fcd02e0 --- /dev/null +++ b/core/resources/views/partials/admin/navbar.blade.php @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/core/resources/views/partials/admin/sidebar.blade.php b/core/resources/views/partials/admin/sidebar.blade.php new file mode 100644 index 0000000..7229145 --- /dev/null +++ b/core/resources/views/partials/admin/sidebar.blade.php @@ -0,0 +1,16 @@ + diff --git a/core/resources/views/partials/home/footer.blade.php b/core/resources/views/partials/home/footer.blade.php new file mode 100644 index 0000000..6e6e72a --- /dev/null +++ b/core/resources/views/partials/home/footer.blade.php @@ -0,0 +1,5 @@ +@if(site_config('scripts')) +
+ {!! site_config('scripts') !!} +
+@endif \ No newline at end of file diff --git a/core/resources/views/partials/home/header.blade.php b/core/resources/views/partials/home/header.blade.php new file mode 100644 index 0000000..11aadf8 --- /dev/null +++ b/core/resources/views/partials/home/header.blade.php @@ -0,0 +1,12 @@ + + + + + @yield('page-title') + + @if(site_config('styles')) + + @endif + \ No newline at end of file diff --git a/core/storage/app/.gitignore b/core/storage/app/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/core/storage/app/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/core/storage/framework/.gitignore b/core/storage/framework/.gitignore new file mode 100644 index 0000000..953edb7 --- /dev/null +++ b/core/storage/framework/.gitignore @@ -0,0 +1,7 @@ +config.php +routes.php +compiled.php +services.json +events.scanned.php +routes.scanned.php +down diff --git a/core/storage/framework/cache/.gitignore b/core/storage/framework/cache/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/core/storage/framework/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/core/storage/framework/sessions/.gitignore b/core/storage/framework/sessions/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/core/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/core/storage/framework/views/.gitignore b/core/storage/framework/views/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/core/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/core/storage/logs/.gitignore b/core/storage/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/core/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/core/tests/TestCase.php b/core/tests/TestCase.php new file mode 100644 index 0000000..8578b17 --- /dev/null +++ b/core/tests/TestCase.php @@ -0,0 +1,25 @@ +make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); + + return $app; + } +} diff --git a/core/update-installer.php b/core/update-installer.php new file mode 100644 index 0000000..72f786d --- /dev/null +++ b/core/update-installer.php @@ -0,0 +1,61 @@ + 2) { + $src = __DIR__ . '/tmp/' . $dirs[2]; + $dst = __DIR__; +} + +function delete_dir($dirPath) +{ + if (file_exists($dirPath)) { + if (substr($dirPath, strlen($dirPath) - 1, 1) != '/') { + $dirPath .= '/'; + } + $files = glob($dirPath . '{,.}[!.,!..]*', GLOB_MARK | GLOB_BRACE); + + foreach ($files as $file) { + if (is_dir($file)) { + delete_dir($file); + } else { + unlink($file); + } + } + rmdir($dirPath); + } +} + +function recurse_copy($src, $dst) +{ + $dir = opendir($src); + @mkdir($dst); + while (false !== ($file = readdir($dir))) { + if (($file != '.') && ($file != '..')) { + if (is_dir($src . '/' . $file)) { + recurse_copy($src . '/' . $file, $dst . '/' . $file); + } else { + copy($src . '/' . $file, $dst . '/' . $file); + } + } + } + closedir($dir); +} + +rename(__DIR__ . '/core/config.php', __DIR__ . '/config.php'); +delete_dir(__DIR__ . '/core/app'); +delete_dir(__DIR__ . '/core/bootstrap'); +delete_dir(__DIR__ . '/core/config'); +delete_dir(__DIR__ . '/core/database'); +delete_dir(__DIR__ . '/core/resources'); +delete_dir(__DIR__ . '/core/tests'); +delete_dir(__DIR__ . '/core/vendor'); +recurse_copy($src, $dst); +rename(__DIR__ . '/config.php', __DIR__ . '/core/config.php'); + +if ($_GET['finishUrl']) { + header('location: ' . $_GET['finishUrl']); +} else { + header('location: ' . '/admin/update/finish'); +} diff --git a/core/vendor/autoload.php b/core/vendor/autoload.php new file mode 100644 index 0000000..48d0c87 --- /dev/null +++ b/core/vendor/autoload.php @@ -0,0 +1,7 @@ + +Copyright (c) 2014-2017 Graham Campbell + +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/core/vendor/classpreloader/classpreloader/composer.json b/core/vendor/classpreloader/classpreloader/composer.json new file mode 100644 index 0000000..307d2f9 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/composer.json @@ -0,0 +1,41 @@ +{ + "name": "classpreloader/classpreloader", + "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", + "keywords": ["autoload", "class", "preload"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "require":{ + "php": ">=5.5.9", + "nikic/php-parser": "^1.0|^2.0|^3.0" + }, + "require-dev":{ + "phpunit/phpunit": "^4.8|^5.0" + }, + "autoload": { + "psr-4": { + "ClassPreloader\\": "src/" + } + }, + "autoload-dev": { + "classmap": ["tests/stubs/"] + }, + "config": { + "preferred-install": "dist" + }, + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/core/vendor/classpreloader/classpreloader/src/ClassList.php b/core/vendor/classpreloader/classpreloader/src/ClassList.php new file mode 100644 index 0000000..cff1a5a --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/ClassList.php @@ -0,0 +1,115 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader; + +/** + * This is the class list class. + * + * This maintains a list of classes using a sort of doubly-linked list. + */ +class ClassList +{ + /** + * The head node of the list. + * + * @var \ClassPreloader\ClassNode + */ + protected $head; + + /** + * The current node of the list. + * + * @var \ClassPreloader\ClassNode + */ + protected $current; + + /** + * Create a new class list instance. + * + * @return void + */ + public function __construct() + { + $this->clear(); + } + + /** + * Clear the contents of the list and reset the head node and current node. + * + * @return void + */ + public function clear() + { + $this->head = new ClassNode(); + $this->current = $this->head; + } + + /** + * Traverse to the next node in the list. + * + * @return void + */ + public function next() + { + if (isset($this->current->next)) { + $this->current = $this->current->next; + } else { + $this->current->next = new ClassNode(null, $this->current); + $this->current = $this->current->next; + } + } + + /** + * Insert a value at the current position in the list. + * + * Any currently set value at this position will be pushed back in the list + * after the new value. + * + * @param mixed $value + * + * @return void + */ + public function push($value) + { + if (!$this->current->value) { + $this->current->value = $value; + } else { + $temp = $this->current; + $this->current = new ClassNode($value, $temp->prev); + $this->current->next = $temp; + $temp->prev = $this->current; + if ($temp === $this->head) { + $this->head = $this->current; + } else { + $this->current->prev->next = $this->current; + } + } + } + + /** + * Traverse the ClassList and return a list of classes. + * + * @return array + */ + public function getClasses() + { + $classes = []; + $current = $this->head; + while ($current && $current->value) { + $classes[] = $current->value; + $current = $current->next; + } + + return array_filter($classes); + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/ClassLoader.php b/core/vendor/classpreloader/classpreloader/src/ClassLoader.php new file mode 100644 index 0000000..7016f3b --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/ClassLoader.php @@ -0,0 +1,150 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader; + +/** + * This is the class loader class. + * + * This creates an autoloader that intercepts and keeps track of each include + * in order that files must be included. This autoloader proxies to all other + * underlying autoloaders. + */ +class ClassLoader +{ + /** + * The list of loaded classes. + * + * @var \ClassPreloader\ClassList + */ + public $classList; + + /** + * Create a new class loader instance. + * + * @return void + */ + public function __construct() + { + $this->classList = new ClassList(); + } + + /** + * Destroy the class loader. + * + * This makes sure we're unregistered from the autoloader. + * + * @return void + */ + public function __destruct() + { + $this->unregister(); + } + + /** + * Wrap a block of code in the autoloader and get a list of loaded classes. + * + * @param callable $func + * + * @return \ClassPreloader\Config + */ + public static function getIncludes($func) + { + $loader = new static(); + call_user_func($func, $loader); + $loader->unregister(); + + $config = new Config(); + foreach ($loader->getFilenames() as $file) { + $config->addFile($file); + } + + return $config; + } + + /** + * Registers this instance as an autoloader. + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'loadClass'], true, true); + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister([$this, 'loadClass']); + } + + /** + * Loads the given class, interface or trait. + * + * We'll return true if it was loaded. + * + * @param string $class + * + * @return bool + */ + public function loadClass($class) + { + foreach (spl_autoload_functions() as $func) { + if (is_array($func) && $func[0] === $this) { + continue; + } + $this->classList->push($class); + if (call_user_func($func, $class)) { + break; + } + } + + $this->classList->next(); + + return true; + } + + /** + * Get an array of loaded file names in order of loading. + * + * @return array + */ + public function getFilenames() + { + $files = []; + foreach ($this->classList->getClasses() as $class) { + // Push interfaces before classes if not already loaded + try { + $r = new \ReflectionClass($class); + foreach ($r->getInterfaces() as $inf) { + $name = $inf->getFileName(); + if ($name && !in_array($name, $files)) { + $files[] = $name; + } + } + if (!in_array($r->getFileName(), $files)) { + $files[] = $r->getFileName(); + } + } catch (\ReflectionException $e) { + // We ignore all exceptions related to reflection because in + // some cases class doesn't need to exist. We're ignoring all + // problems with classes, interfaces and traits. + } + } + + return $files; + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/ClassNode.php b/core/vendor/classpreloader/classpreloader/src/ClassNode.php new file mode 100644 index 0000000..b227a05 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/ClassNode.php @@ -0,0 +1,56 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader; + +/** + * This is the class node class. + * + * This class contains a value, and the previous/next pointers. + */ +class ClassNode +{ + /** + * The next node pointer. + * + * @var \ClassPreloader\ClassNode|null + */ + public $next; + + /** + * The previous node pointer. + * + * @var \ClassPreloader\ClassNode|null + */ + public $prev; + + /** + * The value of the class node. + * + * @var mixed + */ + public $value; + + /** + * Create a new class node instance. + * + * @param mixed $value + * @param \ClassPreloader\ClassNode|null $prev + * + * @return void + */ + public function __construct($value = null, $prev = null) + { + $this->value = $value; + $this->prev = $prev; + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/ClassPreloader.php b/core/vendor/classpreloader/classpreloader/src/ClassPreloader.php new file mode 100644 index 0000000..48c8102 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/ClassPreloader.php @@ -0,0 +1,179 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader; + +use ClassPreloader\Parser\NodeTraverser; +use PhpParser\Node\Stmt\Namespace_ as NamespaceNode; +use PhpParser\Parser; +use PhpParser\PrettyPrinter\Standard as PrettyPrinter; +use RuntimeException; + +/** + * This is the class preloader class. + * + * This is the main point of entry for interacting with this package. + */ +class ClassPreloader +{ + /** + * The printer. + * + * @var \PhpParser\PrettyPrinter\Standard + */ + protected $printer; + + /** + * The parser. + * + * @var \PhpParser\Parser + */ + protected $parser; + + /** + * The traverser. + * + * @var \ClassPreloader\Parser\NodeTraverser + */ + protected $traverser; + + /** + * Create a new class preloader instance. + * + * @param \PhpParser\PrettyPrinter\Standard $printer + * @param \PhpParser\Parser $parser + * @param \ClassPreloader\Parser\NodeTraverser $traverser + * + * @return void + */ + public function __construct(PrettyPrinter $printer, Parser $parser, NodeTraverser $traverser) + { + $this->printer = $printer; + $this->parser = $parser; + $this->traverser = $traverser; + } + + /** + * Prepare the output file and directory. + * + * @param string $output + * @param bool $strict + * + * @throws \RuntimeException + * + * @return resource + */ + public function prepareOutput($output, $strict = false) + { + if ($strict && version_compare(PHP_VERSION, '7') < 1) { + throw new RuntimeException('Strict mode requires PHP 7 or greater.'); + } + + $dir = dirname($output); + + if (!is_dir($dir) && !mkdir($dir, 0777, true)) { + throw new RuntimeException("Unable to create directory $dir."); + } + + $handle = fopen($output, 'w'); + + if (!$handle) { + throw new RuntimeException("Unable to open $output for writing."); + } + + if ($strict) { + fwrite($handle, "parser->parse($content); + $stmts = $this->traverser->traverseFile($parsed, $file); + $pretty = $this->printer->prettyPrint($stmts); + + $pretty = preg_replace( + '#^(<\?php)?[\s]*(/\*\*?.*?\*/)?[\s]*(declare[\s]*\([\s]*strict_types[\s]*=[\s]*1[\s]*\);)?#s', + '', + $pretty + ); + + return $this->getCodeWrappedIntoNamespace($parsed, $pretty); + } + + /** + * Wrap the code into a namespace. + * + * @param array $parsed + * @param string $pretty + * + * @return string + */ + protected function getCodeWrappedIntoNamespace(array $parsed, $pretty) + { + if ($this->parsedCodeHasNamespaces($parsed)) { + $pretty = preg_replace('/^\s*(namespace.*);/im', '${1} {', $pretty, 1)."\n}\n"; + } else { + $pretty = sprintf("namespace {\n%s\n}\n", $pretty); + } + + return preg_replace('/(? + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader; + +/** + * This is the config class. + */ +class Config +{ + /** + * The array of file names. + * + * @var array + */ + protected $filenames = []; + + /** + * The array of exclusive filters. + * + * @var array + */ + protected $exclusiveFilters = []; + + /** + * The array of inclusive filters. + * + * @var array + */ + protected $inclusiveFilters = []; + + /** + * Add the filename owned by the config. + * + * @param string $filename + * + * @return \ClassPreloader\Config + */ + public function addFile($filename) + { + $this->filenames[] = $filename; + + return $this; + } + + /** + * Get an array of file names that satisfy any added filters. + * + * @return array + */ + public function getFilenames() + { + $filenames = []; + foreach ($this->filenames as $f) { + foreach ($this->inclusiveFilters as $filter) { + if (!preg_match($filter, $f)) { + continue 2; + } + } + foreach ($this->exclusiveFilters as $filter) { + if (preg_match($filter, $f)) { + continue 2; + } + } + $filenames[] = $f; + } + + return $filenames; + } + + /** + * Add a filter used to filter out file names matching the pattern. + * + * We're filtering the classes using a regular expression. + * + * @param string $pattern + * + * @return \ClassPreloader\Config + */ + public function addExclusiveFilter($pattern) + { + $this->exclusiveFilters[] = $pattern; + + return $this; + } + + /** + * Add a filter used to grab only file names matching the pattern. + * + * We're filtering the classes using a regular expression. + * + * @param string $pattern Regular expression pattern + * + * @return \ClassPreloader\Config + */ + public function addInclusiveFilter($pattern) + { + $this->inclusiveFilters[] = $pattern; + + return $this; + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/Exceptions/DirConstantException.php b/core/vendor/classpreloader/classpreloader/src/Exceptions/DirConstantException.php new file mode 100644 index 0000000..f7f03d4 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Exceptions/DirConstantException.php @@ -0,0 +1,23 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Exceptions; + +use Exception; + +/** + * This is the dir constant exception class. + */ +class DirConstantException extends Exception implements VisitorExceptionInterface +{ + // +} diff --git a/core/vendor/classpreloader/classpreloader/src/Exceptions/FileConstantException.php b/core/vendor/classpreloader/classpreloader/src/Exceptions/FileConstantException.php new file mode 100644 index 0000000..c773a0f --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Exceptions/FileConstantException.php @@ -0,0 +1,23 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Exceptions; + +use Exception; + +/** + * This is the file constant exception class. + */ +class FileConstantException extends Exception implements VisitorExceptionInterface +{ + // +} diff --git a/core/vendor/classpreloader/classpreloader/src/Exceptions/StrictTypesException.php b/core/vendor/classpreloader/classpreloader/src/Exceptions/StrictTypesException.php new file mode 100644 index 0000000..b830be5 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Exceptions/StrictTypesException.php @@ -0,0 +1,23 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Exceptions; + +use Exception; + +/** + * This is the strict types exception class. + */ +class StrictTypesException extends Exception implements VisitorExceptionInterface +{ + // +} diff --git a/core/vendor/classpreloader/classpreloader/src/Exceptions/VisitorExceptionInterface.php b/core/vendor/classpreloader/classpreloader/src/Exceptions/VisitorExceptionInterface.php new file mode 100644 index 0000000..d3236a7 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Exceptions/VisitorExceptionInterface.php @@ -0,0 +1,21 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Exceptions; + +/** + * This is the visitor exception interface. + */ +interface VisitorExceptionInterface +{ + // +} diff --git a/core/vendor/classpreloader/classpreloader/src/Factory.php b/core/vendor/classpreloader/classpreloader/src/Factory.php new file mode 100644 index 0000000..ea9d070 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Factory.php @@ -0,0 +1,95 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader; + +use ClassPreloader\Parser\DirVisitor; +use ClassPreloader\Parser\FileVisitor; +use ClassPreloader\Parser\NodeTraverser; +use ClassPreloader\Parser\StrictTypesVisitor; +use PhpParser\Lexer; +use PhpParser\Parser; +use PhpParser\ParserFactory; +use PhpParser\PrettyPrinter\Standard as PrettyPrinter; + +/** + * This is the class preloader factory class. + * + * This class is a simple way to create a class preloader instance. + */ +class Factory +{ + /** + * Create a new class preloader instance. + * + * Any options provided determine how the node traverser is setup. + * + * @param bool[] $options + * + * @return \ClassPreloader\ClassPreloader + */ + public function create(array $options = []) + { + $printer = new PrettyPrinter(); + + $parser = $this->getParser(); + + $options = array_merge(['dir' => true, 'file' => true, 'skip' => false, 'strict' => false], $options); + + $traverser = $this->getTraverser($options['dir'], $options['file'], $options['skip'], $options['strict']); + + return new ClassPreloader($printer, $parser, $traverser); + } + + /** + * Get the parser to use. + * + * @return \PhpParser\Parser + */ + protected function getParser() + { + if (class_exists(ParserFactory::class)) { + return (new ParserFactory())->create(ParserFactory::PREFER_PHP7); + } + + return new Parser(new Lexer()); + } + + /** + * Get the node traverser to use. + * + * @param bool $dir + * @param bool $file + * @param bool $skip + * @param bool $strict + * + * @return \ClassPreloader\Parser\NodeTraverser + */ + protected function getTraverser($dir, $file, $skip, $strict) + { + $traverser = new NodeTraverser(); + + if ($dir) { + $traverser->addVisitor(new DirVisitor($skip)); + } + + if ($file) { + $traverser->addVisitor(new FileVisitor($skip)); + } + + if (!$strict) { + $traverser->addVisitor(new StrictTypesVisitor()); + } + + return $traverser; + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/Parser/AbstractNodeVisitor.php b/core/vendor/classpreloader/classpreloader/src/Parser/AbstractNodeVisitor.php new file mode 100644 index 0000000..4e9dc3b --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Parser/AbstractNodeVisitor.php @@ -0,0 +1,64 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Parser; + +use PhpParser\NodeVisitorAbstract; + +/** + * This is the abstract node visitor class. + * + * This is used to track the filename. + */ +abstract class AbstractNodeVisitor extends NodeVisitorAbstract +{ + /** + * The current file being parsed. + * + * @var string + */ + protected $filename = ''; + + /** + * Set the full path to the current file being parsed. + * + * @param string $filename + * + * @return \ClassPreloader\Parser\AbstractNodeVisitor + */ + public function setFilename($filename) + { + $this->filename = $filename; + + return $this; + } + + /** + * Get the full path to the current file being parsed. + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * Get the directory of the current file being parsed. + * + * @return string + */ + public function getDir() + { + return dirname($this->getFilename()); + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/Parser/DirVisitor.php b/core/vendor/classpreloader/classpreloader/src/Parser/DirVisitor.php new file mode 100644 index 0000000..6a5953f --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Parser/DirVisitor.php @@ -0,0 +1,65 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Parser; + +use ClassPreloader\Exceptions\DirConstantException; +use PhpParser\Node; +use PhpParser\Node\Scalar\MagicConst\Dir as DirNode; +use PhpParser\Node\Scalar\String_ as StringNode; + +/** + * This is the directory node visitor class. + * + * This is used to replace all references to __DIR__ with the actual directory. + */ +class DirVisitor extends AbstractNodeVisitor +{ + /** + * Should we skip the file if it contains a dir constant? + * + * @var bool + */ + protected $skip = false; + + /** + * Create a new directory visitor instance. + * + * @param bool $skip + * + * @return void + */ + public function __construct($skip = false) + { + $this->skip = $skip; + } + + /** + * Enter and modify the node. + * + * @param \PhpParser\Node $node + * + * @throws \ClassPreloader\Exceptions\DirConstantException + * + * @return \PhpParser\Node\Scalar\String_|null + */ + public function enterNode(Node $node) + { + if ($node instanceof DirNode) { + if ($this->skip) { + throw new DirConstantException(); + } + + return new StringNode($this->getDir()); + } + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/Parser/FileVisitor.php b/core/vendor/classpreloader/classpreloader/src/Parser/FileVisitor.php new file mode 100644 index 0000000..5130564 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Parser/FileVisitor.php @@ -0,0 +1,65 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Parser; + +use ClassPreloader\Exceptions\FileConstantException; +use PhpParser\Node; +use PhpParser\Node\Scalar\MagicConst\File as FileNode; +use PhpParser\Node\Scalar\String_ as StringNode; + +/** + * This is the file node visitor class. + * + * This is used to replace all references to __FILE__ with the actual file. + */ +class FileVisitor extends AbstractNodeVisitor +{ + /** + * Should we skip the file if it contains a file constant? + * + * @var bool + */ + protected $skip = false; + + /** + * Create a new file visitor instance. + * + * @param bool $skip + * + * @return void + */ + public function __construct($skip = false) + { + $this->skip = $skip; + } + + /** + * Enter and modify the node. + * + * @param \PhpParser\Node $node + * + * @throws \ClassPreloader\Exceptions\FileConstantException + * + * @return \PhpParser\Node\Scalar\String_|null + */ + public function enterNode(Node $node) + { + if ($node instanceof FileNode) { + if ($this->skip) { + throw new FileConstantException(); + } + + return new StringNode($this->getFilename()); + } + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/Parser/NodeTraverser.php b/core/vendor/classpreloader/classpreloader/src/Parser/NodeTraverser.php new file mode 100644 index 0000000..fec6de4 --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Parser/NodeTraverser.php @@ -0,0 +1,43 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Parser; + +use PhpParser\NodeTraverser as BaseTraverser; + +/** + * This is the file node visitor class. + * + * This allows a filename to be set when visiting. + */ +class NodeTraverser extends BaseTraverser +{ + /** + * Transverse the file. + * + * @param array $nodes + * @param string $filename + * + * @return \PhpParser\Node[] + */ + public function traverseFile(array $nodes, $filename) + { + // Set the correct state on each visitor + foreach ($this->visitors as $visitor) { + if ($visitor instanceof AbstractNodeVisitor) { + $visitor->setFilename($filename); + } + } + + return $this->traverse($nodes); + } +} diff --git a/core/vendor/classpreloader/classpreloader/src/Parser/StrictTypesVisitor.php b/core/vendor/classpreloader/classpreloader/src/Parser/StrictTypesVisitor.php new file mode 100644 index 0000000..f72133d --- /dev/null +++ b/core/vendor/classpreloader/classpreloader/src/Parser/StrictTypesVisitor.php @@ -0,0 +1,41 @@ + + * (c) Michael Dowling + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassPreloader\Parser; + +use ClassPreloader\Exceptions\StrictTypesException; +use PhpParser\Node; +use PhpParser\Node\Stmt\DeclareDeclare; + +/** + * This is the strict types visitor class. + * + * This allows us to identify files containing stict types declorations. + */ +class StrictTypesVisitor extends AbstractNodeVisitor +{ + /** + * Enter and modify the node. + * + * @param \PhpParser\Node $node + * + * @throws \ClassPreloader\Exceptions\StrictTypesException + * + * @return null + */ + public function enterNode(Node $node) + { + if ($node instanceof DeclareDeclare && ($node->getLine() === 1 || $node->getLine() === 2)) { + throw new StrictTypesException(); + } + } +} diff --git a/core/vendor/composer/ClassLoader.php b/core/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..dc02dfb --- /dev/null +++ b/core/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * 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, PSR-4 and classmap class loader. + * + * $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 + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +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(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + 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-4 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; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * 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) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + 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; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/core/vendor/composer/LICENSE b/core/vendor/composer/LICENSE new file mode 100644 index 0000000..f0157a6 --- /dev/null +++ b/core/vendor/composer/LICENSE @@ -0,0 +1,56 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Composer +Upstream-Contact: Jordi Boggiano +Source: https://github.com/composer/composer + +Files: * +Copyright: 2016, Nils Adermann + 2016, Jordi Boggiano +License: Expat + +Files: src/Composer/Util/TlsHelper.php +Copyright: 2016, Nils Adermann + 2016, Jordi Boggiano + 2013, Evan Coury +License: Expat and BSD-2-Clause + +License: BSD-2-Clause + 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. + +License: Expat + 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/core/vendor/composer/autoload_classmap.php b/core/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..294bfc0 --- /dev/null +++ b/core/vendor/composer/autoload_classmap.php @@ -0,0 +1,25 @@ + $baseDir . '/database/migrations/2019_09_06_235958_add_form_size_field_to_fp_files_table.php', + 'AddFormSizeFieldToFpFormsTable' => $baseDir . '/database/migrations/2019_09_06_235603_add_form_size_field_to_fp_forms_table.php', + 'AddPrefixFpToTables' => $baseDir . '/database/migrations/2019_09_06_225148_add_prefix_fp_to_tables.php', + 'ConfigsTableSeeder' => $baseDir . '/database/seeds/ConfigsTableSeeder.php', + 'CreateConfigsTable' => $baseDir . '/database/migrations/2019_07_08_000856_create_configs_table.php', + 'CreateFactorsTable' => $baseDir . '/database/migrations/2019_07_24_131447_create_factors_table.php', + 'CreateFilesTable' => $baseDir . '/database/migrations/2019_08_19_151712_create_files_table.php', + 'CreateFormsTable' => $baseDir . '/database/migrations/2019_07_08_143755_create_forms_table.php', + 'CreatePasswordResetsTable' => $baseDir . '/database/migrations/2014_10_12_100000_create_password_resets_table.php', + 'CreateTransactionsTable' => $baseDir . '/database/migrations/2019_07_08_113413_create_transactions_table.php', + 'CreateUsersTable' => $baseDir . '/database/migrations/2014_10_12_000000_create_users_table.php', + 'DatabaseSeeder' => $baseDir . '/database/seeds/DatabaseSeeder.php', + 'FormsTableSeeder' => $baseDir . '/database/seeds/FormsTableSeeder.php', + 'IlluminateQueueClosure' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/IlluminateQueueClosure.php', + 'SessionHandlerInterface' => $vendorDir . '/symfony/http-foundation/Resources/stubs/SessionHandlerInterface.php', + 'TestCase' => $baseDir . '/tests/TestCase.php', +); diff --git a/core/vendor/composer/autoload_files.php b/core/vendor/composer/autoload_files.php new file mode 100644 index 0000000..4f70ce2 --- /dev/null +++ b/core/vendor/composer/autoload_files.php @@ -0,0 +1,21 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', + '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', + 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php', + '65fec9ebcfbb3cbb4fd0d519687aea01' => $vendorDir . '/danielstjules/stringy/src/Create.php', + '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', + 'e7223560d890eab89cda23685e711e2c' => $vendorDir . '/psy/psysh/src/Psy/functions.php', + 'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php', + '58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php', + 'e4e590a9b5afe940db71ee1662c02677' => $vendorDir . '/morilog/jalali/src/helpers.php', + '6f63623309b417353d909e0e864f4beb' => $baseDir . '/app/Support/helpers.php', +); diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..44eb987 --- /dev/null +++ b/core/vendor/composer/autoload_namespaces.php @@ -0,0 +1,13 @@ + array($vendorDir . '/kylekatarnls/update-helper/src'), + 'JakubOnderka\\PhpConsoleHighlighter' => array($vendorDir . '/jakub-onderka/php-console-highlighter/src'), + 'Dotenv' => array($vendorDir . '/vlucas/phpdotenv/src'), + 'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib'), +); diff --git a/core/vendor/composer/autoload_psr4.php b/core/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..99a673f --- /dev/null +++ b/core/vendor/composer/autoload_psr4.php @@ -0,0 +1,40 @@ + array($vendorDir . '/dnoegel/php-xdg-base-dir/src'), + 'Symfony\\Polyfill\\Util\\' => array($vendorDir . '/symfony/polyfill-util'), + 'Symfony\\Polyfill\\Php56\\' => array($vendorDir . '/symfony/polyfill-php56'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), + 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), + 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), + 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), + 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), + 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'SuperClosure\\' => array($vendorDir . '/jeremeamia/superclosure/src'), + 'Stringy\\' => array($vendorDir . '/danielstjules/stringy/src'), + 'Psy\\' => array($vendorDir . '/psy/psysh/src/Psy'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), + 'Morilog\\Jalali\\' => array($vendorDir . '/morilog/jalali/src'), + 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), + 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), + 'JakubOnderka\\PhpConsoleColor\\' => array($vendorDir . '/jakub-onderka/php-console-color/src'), + 'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'), + 'Cron\\' => array($vendorDir . '/mtdowling/cron-expression/src/Cron'), + 'ClassPreloader\\' => array($vendorDir . '/classpreloader/classpreloader/src'), + 'App\\' => array($baseDir . '/app'), + '' => array($vendorDir . '/nesbot/carbon/src'), +); diff --git a/core/vendor/composer/autoload_real.php b/core/vendor/composer/autoload_real.php new file mode 100644 index 0000000..0328d94 --- /dev/null +++ b/core/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInitcc9210e519f1afec598572c2b8194f9d::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $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); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInitcc9210e519f1afec598572c2b8194f9d::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequirecc9210e519f1afec598572c2b8194f9d($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequirecc9210e519f1afec598572c2b8194f9d($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/core/vendor/composer/autoload_static.php b/core/vendor/composer/autoload_static.php new file mode 100644 index 0000000..150fb0c --- /dev/null +++ b/core/vendor/composer/autoload_static.php @@ -0,0 +1,269 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', + '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', + 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php', + '65fec9ebcfbb3cbb4fd0d519687aea01' => __DIR__ . '/..' . '/danielstjules/stringy/src/Create.php', + '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', + 'e7223560d890eab89cda23685e711e2c' => __DIR__ . '/..' . '/psy/psysh/src/Psy/functions.php', + 'f0906e6318348a765ffb6eb24e0d0938' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/helpers.php', + '58571171fd5812e6e447dce228f52f4d' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/helpers.php', + 'e4e590a9b5afe940db71ee1662c02677' => __DIR__ . '/..' . '/morilog/jalali/src/helpers.php', + '6f63623309b417353d909e0e864f4beb' => __DIR__ . '/../..' . '/app/Support/helpers.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'X' => + array ( + 'XdgBaseDir\\' => 11, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Util\\' => 22, + 'Symfony\\Polyfill\\Php56\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Component\\VarDumper\\' => 28, + 'Symfony\\Component\\Translation\\' => 30, + 'Symfony\\Component\\Routing\\' => 26, + 'Symfony\\Component\\Process\\' => 26, + 'Symfony\\Component\\HttpKernel\\' => 29, + 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\DomCrawler\\' => 29, + 'Symfony\\Component\\Debug\\' => 24, + 'Symfony\\Component\\CssSelector\\' => 30, + 'Symfony\\Component\\Console\\' => 26, + 'SuperClosure\\' => 13, + 'Stringy\\' => 8, + ), + 'P' => + array ( + 'Psy\\' => 4, + 'Psr\\Log\\' => 8, + 'PhpParser\\' => 10, + ), + 'M' => + array ( + 'Morilog\\Jalali\\' => 15, + 'Monolog\\' => 8, + ), + 'L' => + array ( + 'League\\Flysystem\\' => 17, + ), + 'J' => + array ( + 'JakubOnderka\\PhpConsoleColor\\' => 29, + ), + 'I' => + array ( + 'Illuminate\\' => 11, + ), + 'C' => + array ( + 'Cron\\' => 5, + 'ClassPreloader\\' => 15, + ), + 'A' => + array ( + 'App\\' => 4, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'XdgBaseDir\\' => + array ( + 0 => __DIR__ . '/..' . '/dnoegel/php-xdg-base-dir/src', + ), + 'Symfony\\Polyfill\\Util\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-util', + ), + 'Symfony\\Polyfill\\Php56\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php56', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Component\\VarDumper\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-dumper', + ), + 'Symfony\\Component\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation', + ), + 'Symfony\\Component\\Routing\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/routing', + ), + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/process', + ), + 'Symfony\\Component\\HttpKernel\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-kernel', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\DomCrawler\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/dom-crawler', + ), + 'Symfony\\Component\\Debug\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/debug', + ), + 'Symfony\\Component\\CssSelector\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/css-selector', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'SuperClosure\\' => + array ( + 0 => __DIR__ . '/..' . '/jeremeamia/superclosure/src', + ), + 'Stringy\\' => + array ( + 0 => __DIR__ . '/..' . '/danielstjules/stringy/src', + ), + 'Psy\\' => + array ( + 0 => __DIR__ . '/..' . '/psy/psysh/src/Psy', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'PhpParser\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser', + ), + 'Morilog\\Jalali\\' => + array ( + 0 => __DIR__ . '/..' . '/morilog/jalali/src', + ), + 'Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', + ), + 'League\\Flysystem\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem/src', + ), + 'JakubOnderka\\PhpConsoleColor\\' => + array ( + 0 => __DIR__ . '/..' . '/jakub-onderka/php-console-color/src', + ), + 'Illuminate\\' => + array ( + 0 => __DIR__ . '/..' . '/laravel/framework/src/Illuminate', + ), + 'Cron\\' => + array ( + 0 => __DIR__ . '/..' . '/mtdowling/cron-expression/src/Cron', + ), + 'ClassPreloader\\' => + array ( + 0 => __DIR__ . '/..' . '/classpreloader/classpreloader/src', + ), + 'App\\' => + array ( + 0 => __DIR__ . '/../..' . '/app', + ), + ); + + public static $fallbackDirsPsr4 = array ( + 0 => __DIR__ . '/..' . '/nesbot/carbon/src', + ); + + public static $prefixesPsr0 = array ( + 'U' => + array ( + 'UpdateHelper\\' => + array ( + 0 => __DIR__ . '/..' . '/kylekatarnls/update-helper/src', + ), + ), + 'J' => + array ( + 'JakubOnderka\\PhpConsoleHighlighter' => + array ( + 0 => __DIR__ . '/..' . '/jakub-onderka/php-console-highlighter/src', + ), + ), + 'D' => + array ( + 'Dotenv' => + array ( + 0 => __DIR__ . '/..' . '/vlucas/phpdotenv/src', + ), + 'Doctrine\\Common\\Inflector\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/inflector/lib', + ), + ), + ); + + public static $classMap = array ( + 'AddFormSizeFieldToFpFilesTable' => __DIR__ . '/../..' . '/database/migrations/2019_09_06_235958_add_form_size_field_to_fp_files_table.php', + 'AddFormSizeFieldToFpFormsTable' => __DIR__ . '/../..' . '/database/migrations/2019_09_06_235603_add_form_size_field_to_fp_forms_table.php', + 'AddPrefixFpToTables' => __DIR__ . '/../..' . '/database/migrations/2019_09_06_225148_add_prefix_fp_to_tables.php', + 'ConfigsTableSeeder' => __DIR__ . '/../..' . '/database/seeds/ConfigsTableSeeder.php', + 'CreateConfigsTable' => __DIR__ . '/../..' . '/database/migrations/2019_07_08_000856_create_configs_table.php', + 'CreateFactorsTable' => __DIR__ . '/../..' . '/database/migrations/2019_07_24_131447_create_factors_table.php', + 'CreateFilesTable' => __DIR__ . '/../..' . '/database/migrations/2019_08_19_151712_create_files_table.php', + 'CreateFormsTable' => __DIR__ . '/../..' . '/database/migrations/2019_07_08_143755_create_forms_table.php', + 'CreatePasswordResetsTable' => __DIR__ . '/../..' . '/database/migrations/2014_10_12_100000_create_password_resets_table.php', + 'CreateTransactionsTable' => __DIR__ . '/../..' . '/database/migrations/2019_07_08_113413_create_transactions_table.php', + 'CreateUsersTable' => __DIR__ . '/../..' . '/database/migrations/2014_10_12_000000_create_users_table.php', + 'DatabaseSeeder' => __DIR__ . '/../..' . '/database/seeds/DatabaseSeeder.php', + 'FormsTableSeeder' => __DIR__ . '/../..' . '/database/seeds/FormsTableSeeder.php', + 'IlluminateQueueClosure' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Queue/IlluminateQueueClosure.php', + 'SessionHandlerInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Resources/stubs/SessionHandlerInterface.php', + 'TestCase' => __DIR__ . '/../..' . '/tests/TestCase.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitcc9210e519f1afec598572c2b8194f9d::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitcc9210e519f1afec598572c2b8194f9d::$prefixDirsPsr4; + $loader->fallbackDirsPsr4 = ComposerStaticInitcc9210e519f1afec598572c2b8194f9d::$fallbackDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInitcc9210e519f1afec598572c2b8194f9d::$prefixesPsr0; + $loader->classMap = ComposerStaticInitcc9210e519f1afec598572c2b8194f9d::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json new file mode 100644 index 0000000..9b22e49 --- /dev/null +++ b/core/vendor/composer/installed.json @@ -0,0 +1,2200 @@ +[ + { + "name": "classpreloader/classpreloader", + "version": "3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ClassPreloader/ClassPreloader.git", + "reference": "4729e438e0ada350f91148e7d4bb9809342575ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/4729e438e0ada350f91148e7d4bb9809342575ff", + "reference": "4729e438e0ada350f91148e7d4bb9809342575ff", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.0|^2.0|^3.0", + "php": ">=5.5.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.0" + }, + "time": "2017-12-10T11:40:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "ClassPreloader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", + "keywords": [ + "autoload", + "class", + "preload" + ] + }, + { + "name": "danielstjules/stringy", + "version": "1.10.0", + "version_normalized": "1.10.0.0", + "source": { + "type": "git", + "url": "https://github.com/danielstjules/Stringy.git", + "reference": "4749c205db47ee5b32e8d1adf6d9aff8db6caf3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/danielstjules/Stringy/zipball/4749c205db47ee5b32e8d1adf6d9aff8db6caf3b", + "reference": "4749c205db47ee5b32e8d1adf6d9aff8db6caf3b", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2015-07-23T00:54:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Stringy\\": "src/" + }, + "files": [ + "src/Create.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel St. Jules", + "email": "danielst.jules@gmail.com", + "homepage": "http://www.danielstjules.com" + } + ], + "description": "A string manipulation library with multibyte support", + "homepage": "https://github.com/danielstjules/Stringy", + "keywords": [ + "UTF", + "helpers", + "manipulation", + "methods", + "multibyte", + "string", + "utf-8", + "utility", + "utils" + ] + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "0.1", + "version_normalized": "0.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + }, + "time": "2014-10-24T07:27:01+00:00", + "type": "project", + "installation-source": "dist", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php" + }, + { + "name": "doctrine/inflector", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "time": "2015-11-06T14:35:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ] + }, + { + "name": "jakub-onderka/php-console-color", + "version": "v0.2", + "version_normalized": "0.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "1.0", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "~4.3", + "squizlabs/php_codesniffer": "1.*" + }, + "time": "2018-09-29T17:23:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "JakubOnderka\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ] + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.3.2", + "version_normalized": "0.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "shasum": "" + }, + "require": { + "jakub-onderka/php-console-color": "~0.1", + "php": ">=5.3.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~0.5", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "time": "2015-04-20T18:58:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleHighlighter": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ] + }, + { + "name": "jeremeamia/superclosure", + "version": "2.4.0", + "version_normalized": "2.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/jeremeamia/super_closure.git", + "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/5707d5821b30b9a07acfb4d76949784aaa0e9ce9", + "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.2|^2.0|^3.0|^4.0", + "php": ">=5.4", + "symfony/polyfill-php56": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0" + }, + "time": "2018-03-21T22:21:57+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "SuperClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia", + "role": "Developer" + } + ], + "description": "Serialize Closure objects, including their context and binding", + "homepage": "https://github.com/jeremeamia/super_closure", + "keywords": [ + "closure", + "function", + "lambda", + "parser", + "serializable", + "serialize", + "tokenizer" + ] + }, + { + "name": "kylekatarnls/update-helper", + "version": "1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/kylekatarnls/update-helper.git", + "reference": "b34a46d7f5ec1795b4a15ac9d46b884377262df9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kylekatarnls/update-helper/zipball/b34a46d7f5ec1795b4a15ac9d46b884377262df9", + "reference": "b34a46d7f5ec1795b4a15ac9d46b884377262df9", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0", + "php": ">=5.3.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "dev-master", + "composer/composer": "^2.0.x-dev", + "phpunit/phpunit": ">=4.8.35 <6.0" + }, + "time": "2019-06-05T08:34:23+00:00", + "type": "composer-plugin", + "extra": { + "class": "UpdateHelper\\ComposerPlugin" + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "UpdateHelper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Update helper" + }, + { + "name": "laravel/framework", + "version": "v5.1.46", + "version_normalized": "5.1.46.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "7f2f892e62163138121e8210b92b21394fda8d1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/7f2f892e62163138121e8210b92b21394fda8d1c", + "reference": "7f2f892e62163138121e8210b92b21394fda8d1c", + "shasum": "" + }, + "require": { + "classpreloader/classpreloader": "~2.0|~3.0", + "danielstjules/stringy": "~1.8", + "doctrine/inflector": "~1.0", + "ext-mbstring": "*", + "ext-openssl": "*", + "jeremeamia/superclosure": "~2.0", + "league/flysystem": "~1.0", + "monolog/monolog": "~1.11", + "mtdowling/cron-expression": "~1.0", + "nesbot/carbon": "~1.19", + "paragonie/random_compat": "~1.4", + "php": ">=5.5.9", + "psy/psysh": "0.7.*", + "swiftmailer/swiftmailer": "~5.1", + "symfony/console": "2.7.*", + "symfony/css-selector": "2.7.*|2.8.*", + "symfony/debug": "2.7.*", + "symfony/dom-crawler": "2.7.*", + "symfony/finder": "2.7.*", + "symfony/http-foundation": "2.7.*", + "symfony/http-kernel": "2.7.*", + "symfony/process": "2.7.*", + "symfony/routing": "2.7.*", + "symfony/translation": "2.7.*", + "symfony/var-dumper": "2.7.*", + "vlucas/phpdotenv": "~1.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "~3.0", + "iron-io/iron_mq": "~2.0", + "mockery/mockery": "~0.9.4", + "pda/pheanstalk": "~3.0", + "phpunit/phpunit": "~4.0", + "predis/predis": "~1.0" + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).", + "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~5.3|~6.0).", + "iron-io/iron_mq": "Required to use the iron queue driver (~2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", + "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + }, + "time": "2017-03-24T16:31:45+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/Illuminate/Queue/IlluminateQueueClosure.php" + ], + "files": [ + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "http://laravel.com", + "keywords": [ + "framework", + "laravel" + ] + }, + { + "name": "league/flysystem", + "version": "1.0.53", + "version_normalized": "1.0.53.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "08e12b7628f035600634a5e76d95b5eb66cea674" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/08e12b7628f035600634a5e76d95b5eb66cea674", + "reference": "08e12b7628f035600634a5e76d95b5eb66cea674", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.10" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "time": "2019-06-18T20:09:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ] + }, + { + "name": "monolog/monolog", + "version": "1.24.0", + "version_normalized": "1.24.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "time": "2018-11-05T09:00:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ] + }, + { + "name": "morilog/jalali", + "version": "v2.3.0", + "version_normalized": "2.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/morilog/jalali.git", + "reference": "6a2a692fde8bb4695acf45a654bad609e402040a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/morilog/jalali/zipball/6a2a692fde8bb4695acf45a654bad609e402040a", + "reference": "6a2a692fde8bb4695acf45a654bad609e402040a", + "shasum": "" + }, + "require": { + "illuminate/support": "^5.0", + "nesbot/carbon": "^1.21", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2017-09-06T19:39:20+00:00", + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Morilog\\Jalali\\JalaliServiceProvider" + ], + "aliases": { + "jDate": "Morilog\\Jalali\\Facades\\jDate" + } + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Morilog\\Jalali\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Milad Rey", + "email": "miladr@gmail.com" + }, + { + "name": "Morteza Parvini", + "email": "m.parvini@outlook.com" + } + ], + "description": "eThis Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in Laravel 5 applications, based on Jalali (Shamsi) DateTime class.", + "keywords": [ + "Jalali", + "date", + "datetime", + "laravel", + "morilog" + ] + }, + { + "name": "mtdowling/cron-expression", + "version": "v1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/mtdowling/cron-expression.git", + "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/9504fa9ea681b586028adaaa0877db4aecf32bad", + "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "time": "2017-01-23T04:29:33+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ] + }, + { + "name": "nesbot/carbon", + "version": "1.39.0", + "version_normalized": "1.39.0.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0", + "reference": "dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0", + "shasum": "" + }, + "require": { + "kylekatarnls/update-helper": "^1.1", + "php": ">=5.3.9", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" + }, + "require-dev": { + "composer/composer": "^1.2", + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "time": "2019-06-11T09:07:59+00:00", + "bin": [ + "bin/upgrade-carbon" + ], + "type": "library", + "extra": { + "update-helper": "Carbon\\Upgrade", + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ] + }, + { + "name": "nikic/php-parser", + "version": "v2.1.1", + "version_normalized": "2.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4dd659edadffdc2143e4753df655d866dbfeedf0", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2016-09-16T12:04:44+00:00", + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v1.4.3", + "version_normalized": "1.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/9b3899e3c3ddde89016f576edb8c489708ad64cd", + "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2018-04-04T21:48:54+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ] + }, + { + "name": "psr/log", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2018-11-20T15:27:04+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "psy/psysh", + "version": "v0.7.2", + "version_normalized": "0.7.2.0", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280", + "shasum": "" + }, + "require": { + "dnoegel/php-xdg-base-dir": "0.1", + "jakub-onderka/php-console-highlighter": "0.3.*", + "nikic/php-parser": "^1.2.1|~2.0", + "php": ">=5.3.9", + "symfony/console": "~2.3.10|^2.4.2|~3.0", + "symfony/var-dumper": "~2.7|~3.0" + }, + "require-dev": { + "fabpot/php-cs-fixer": "~1.5", + "phpunit/phpunit": "~3.7|~4.0|~5.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/finder": "~2.1|~3.0" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." + }, + "time": "2016-03-09T05:03:14+00:00", + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/Psy/functions.php" + ], + "psr-4": { + "Psy\\": "src/Psy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ] + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.12", + "version_normalized": "5.4.12.0", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "181b89f18a90f8925ef805f950d47a7190e9b950" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950", + "reference": "181b89f18a90f8925ef805f950d47a7190e9b950", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "time": "2018-07-31T09:26:32+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-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": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ] + }, + { + "name": "symfony/console", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "574cb4cfaa01ba115fc2fc0c2355b2c5472a4804" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/574cb4cfaa01ba115fc2fc0c2355b2c5472a4804", + "reference": "574cb4cfaa01ba115fc2fc0c2355b2c5472a4804", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/process": "~2.1" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "time": "2018-05-13T15:44:36+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/css-selector", + "version": "v2.8.50", + "version_normalized": "2.8.50.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "7b1692e418d7ccac24c373528453bc90e42797de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/7b1692e418d7ccac24c373528453bc90e42797de", + "reference": "7b1692e418d7ccac24c373528453bc90e42797de", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "time": "2018-11-11T11:18:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/debug", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "4a7330f29b3d215f8bacf076689f9d1c3d568681" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/4a7330f29b3d215f8bacf076689f9d1c3d568681", + "reference": "4a7330f29b3d215f8bacf076689f9d1c3d568681", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2" + }, + "time": "2018-08-03T11:24:48+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/dom-crawler", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "d905e1c5885735ee66af60c205429b9941f24752" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d905e1c5885735ee66af60c205429b9941f24752", + "reference": "d905e1c5885735ee66af60c205429b9941f24752", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/css-selector": "~2.3" + }, + "suggest": { + "symfony/css-selector": "" + }, + "time": "2018-05-01T22:30:49+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.50", + "version_normalized": "2.8.50.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2018-11-21T14:20:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/finder", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "34226a3aa279f1e356ad56181b91acfdc9a2525c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/34226a3aa279f1e356ad56181b91acfdc9a2525c", + "reference": "34226a3aa279f1e356ad56181b91acfdc9a2525c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "time": "2018-05-14T06:36:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/http-foundation", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "b67e5cbd2bf837fb3681f2c4965826d6c6758532" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b67e5cbd2bf837fb3681f2c4965826d6c6758532", + "reference": "b67e5cbd2bf837fb3681f2c4965826d6c6758532", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.4" + }, + "time": "2019-04-16T09:58:21+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/http-kernel", + "version": "v2.7.52", + "version_normalized": "2.7.52.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "435064b3b143f79469206915137c21e88b56bfb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/435064b3b143f79469206915137c21e88b56bfb9", + "reference": "435064b3b143f79469206915137c21e88b56bfb9", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0", + "symfony/debug": "^2.6.2", + "symfony/event-dispatcher": "^2.6.7", + "symfony/http-foundation": "~2.7.36|^2.8.29", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/config": "<2.7", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "symfony/browser-kit": "~2.3", + "symfony/class-loader": "~2.1", + "symfony/config": "~2.7", + "symfony/console": "~2.3", + "symfony/css-selector": "^2.0.5", + "symfony/dependency-injection": "~2.2", + "symfony/dom-crawler": "^2.0.5", + "symfony/expression-language": "~2.4", + "symfony/finder": "^2.0.5", + "symfony/process": "^2.0.5", + "symfony/routing": "~2.2", + "symfony/stopwatch": "~2.3", + "symfony/templating": "~2.2", + "symfony/translation": "^2.0.5", + "symfony/var-dumper": "~2.6" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "time": "2019-04-17T16:37:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2019-02-06T07:57:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ] + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2019-02-06T07:57:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/f4dddbc5c3471e1b700a147a20ae17cdb72dbe42", + "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "time": "2019-02-06T07:57:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-util", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "b46c6cae28a3106735323f00a0c38eccf2328897" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/b46c6cae28a3106735323f00a0c38eccf2328897", + "reference": "b46c6cae28a3106735323f00a0c38eccf2328897", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2019-02-08T14:16:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ] + }, + { + "name": "symfony/process", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "eda637e05670e2afeec3842dcd646dce94262f6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/eda637e05670e2afeec3842dcd646dce94262f6b", + "reference": "eda637e05670e2afeec3842dcd646dce94262f6b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "time": "2018-08-03T11:24:48+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/routing", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "33bd5882f201f9a3b7dd9640b95710b71304c4fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/33bd5882f201f9a3b7dd9640b95710b71304c4fb", + "reference": "33bd5882f201f9a3b7dd9640b95710b71304c4fb", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.7", + "symfony/expression-language": "~2.4", + "symfony/http-foundation": "~2.3", + "symfony/yaml": "^2.0.5" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "time": "2018-02-28T09:36:59+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ] + }, + { + "name": "symfony/translation", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "1959c78c5a32539ef221b3e18a961a96d949118f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/1959c78c5a32539ef221b3e18a961a96d949118f", + "reference": "1959c78c5a32539ef221b3e18a961a96d949118f", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.7", + "symfony/intl": "~2.7.25|^2.8.18", + "symfony/yaml": "~2.2" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "time": "2018-05-17T10:34:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/var-dumper", + "version": "v2.7.51", + "version_normalized": "2.7.51.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "6f9271e94369db05807b261fcfefe4cd1aafd390" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6f9271e94369db05807b261fcfefe4cd1aafd390", + "reference": "6f9271e94369db05807b261fcfefe4cd1aafd390", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "suggest": { + "ext-symfony_debug": "" + }, + "time": "2018-04-22T05:56:10+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ] + }, + { + "name": "vlucas/phpdotenv", + "version": "v1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa", + "reference": "0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2015-05-30T15:59:26+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Dotenv": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "homepage": "http://github.com/vlucas/phpdotenv", + "keywords": [ + "dotenv", + "env", + "environment" + ] + } +] diff --git a/core/vendor/danielstjules/stringy/.gitignore b/core/vendor/danielstjules/stringy/.gitignore new file mode 100644 index 0000000..8db156e --- /dev/null +++ b/core/vendor/danielstjules/stringy/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +.DS_Store diff --git a/core/vendor/danielstjules/stringy/.travis.yml b/core/vendor/danielstjules/stringy/.travis.yml new file mode 100644 index 0000000..f7637c8 --- /dev/null +++ b/core/vendor/danielstjules/stringy/.travis.yml @@ -0,0 +1,7 @@ +language: php +php: + - 5.6 + - 5.5 + - 5.4 + - 5.3 + - hhvm diff --git a/core/vendor/danielstjules/stringy/CHANGELOG.md b/core/vendor/danielstjules/stringy/CHANGELOG.md new file mode 100644 index 0000000..379c629 --- /dev/null +++ b/core/vendor/danielstjules/stringy/CHANGELOG.md @@ -0,0 +1,119 @@ +### 1.10.0 (2015-07-22) + + * Added trimLeft, trimRight + * Added support for unicode whitespace to trim + * Added delimit + * Added indexOf and indexOfLast + * Added htmlEncode and htmlDecode + * Added "Ç" in toAscii() + +### 1.9.0 (2015-02-09) + + * Added hasUpperCase and hasLowerCase + * Added $removeUnsupported parameter to toAscii() + * Improved toAscii support with additional Unicode spaces, Vietnamese chars, + and numerous other characters + * Separated the charsArray from toAscii as a protected method that may be + extended by inheriting classes + * Chars array is cached for better performance + +### 1.8.1 (2015-01-08) + + * Optimized chars() + * Added "ä Ä Ö Ü"" in toAscii() + * Added support for Unicode spaces in toAscii() + * Replaced instances of self::create() with static::create() + * Added missing test cases for safeTruncate() and longestCommonSuffix() + * Updated Stringy\create() to avoid collision when it already exists + +### 1.8.0 (2015-01-03) + + * Listed ext-mbstring in composer.json + * Added Stringy\create function for PHP 5.6 + +### 1.7.0 (2014-10-14) + + * Added containsAll and containsAny + * Light cleanup + +### 1.6.0 (2014-09-14) + + * Added toTitleCase + +### 1.5.2 (2014-07-09) + + * Announced support for HHVM + +### 1.5.1 (2014-04-19) + + * Fixed toAscii() failing to remove remaining non-ascii characters + * Updated slugify() to treat dash and underscore as delimiters by default + * Updated slugify() to remove leading and trailing delimiter, if present + +### 1.5.0 (2014-03-19) + + * Made both str and encoding protected, giving property access to subclasses + * Added getEncoding() + * Fixed isJSON() giving false negatives + * Cleaned up and simplified: replace(), collapseWhitespace(), underscored(), + dasherize(), pad(), padLeft(), padRight() and padBoth() + * Fixed handling consecutive invalid chars in slugify() + * Removed conflicting hard sign transliteration in toAscii() + +### 1.4.0 (2014-02-12) + + * Implemented the IteratorAggregate interface, added chars() + * Renamed count() to countSubstr() + * Updated count() to implement Countable interface + * Implemented the ArrayAccess interface with positive and negative indices + * Switched from PSR-0 to PSR-4 autoloading + +### 1.3.0 (2013-12-16) + + * Additional Bulgarian support for toAscii + * str property made private + * Constructor casts first argument to string + * Constructor throws an InvalidArgumentException when given an array + * Constructor throws an InvalidArgumentException when given an object without + a __toString method + +### 1.2.2 (2013-12-04) + + * Updated create function to use late static binding + * Added optional $replacement param to slugify + +### 1.2.1 (2013-10-11) + + * Cleaned up tests + * Added homepage to composer.json + +### 1.2.0 (2013-09-15) + + * Fixed pad's use of InvalidArgumentException + * Fixed replace(). It now correctly treats regex special chars as normal chars + * Added additional Cyrillic letters to toAscii + * Added $caseSensitive to contains() and count() + * Added toLowerCase() + * Added toUpperCase() + * Added regexReplace() + +### 1.1.0 (2013-08-31) + + * Fix for collapseWhitespace() + * Added isHexadecimal() + * Added constructor to Stringy\Stringy + * Added isSerialized() + * Added isJson() + +### 1.0.0 (2013-08-1) + + * 1.0.0 release + * Added test coverage for Stringy::create and method chaining + * Added tests for returned type + * Fixed StaticStringy::replace(). It was returning a Stringy object instead of string + * Renamed standardize() to the more appropriate toAscii() + * Cleaned up comments and README + +### 1.0.0-rc.1 (2013-07-28) + + * Release candidate diff --git a/core/vendor/danielstjules/stringy/LICENSE.txt b/core/vendor/danielstjules/stringy/LICENSE.txt new file mode 100644 index 0000000..0b70302 --- /dev/null +++ b/core/vendor/danielstjules/stringy/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2013 Daniel St. Jules + +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/core/vendor/danielstjules/stringy/README.md b/core/vendor/danielstjules/stringy/README.md new file mode 100644 index 0000000..ab09bdc --- /dev/null +++ b/core/vendor/danielstjules/stringy/README.md @@ -0,0 +1,1190 @@ +![Stringy](http://danielstjules.com/github/stringy-logo.png) + +A PHP string manipulation library with multibyte support. Offers both OO method +chaining and a procedural-style static wrapper. Tested and compatible with +PHP 5.3+ and HHVM. Inspired by underscore.string.js. + +[![Build Status](https://api.travis-ci.org/danielstjules/Stringy.svg?branch=master)](https://travis-ci.org/danielstjules/Stringy) + +* [Requiring/Loading](#requiringloading) +* [OO and Procedural](#oo-and-procedural) +* [Implemented Interfaces](#implemented-interfaces) +* [PHP 5.6 Creation](#php-56-creation) +* [Methods](#methods) + * [at](#at) + * [camelize](#camelize) + * [chars](#chars) + * [collapseWhitespace](#collapsewhitespace) + * [contains](#contains) + * [containsAll](#containsall) + * [containsAny](#containsany) + * [countSubstr](#countsubstr) + * [create](#create) + * [dasherize](#dasherize) + * [delimit](#delimit) + * [endsWith](#endswith) + * [ensureLeft](#ensureleft) + * [ensureRight](#ensureright) + * [first](#first) + * [getEncoding](#getencoding) + * [hasLowerCase](#haslowercase) + * [hasUpperCase](#hasuppercase) + * [htmlDecode](#htmldecode) + * [htmlEncode](#htmlencode) + * [humanize](#humanize) + * [indexOf](#indexof) + * [indexOfLast](#indexoflast) + * [insert](#insert) + * [isAlpha](#isalpha) + * [isAlphanumeric](#isalphanumeric) + * [isBlank](#isblank) + * [isHexadecimal](#ishexadecimal) + * [isJson](#isjson) + * [isLowerCase](#islowercase) + * [isSerialized](#isserialized) + * [isUpperCase](#isuppercase) + * [last](#last) + * [length](#length) + * [longestCommonPrefix](#longestcommonprefix) + * [longestCommonSuffix](#longestcommonsuffix) + * [longestCommonSubstring](#longestcommonsubstring) + * [lowerCaseFirst](#lowercasefirst) + * [pad](#pad) + * [padBoth](#padboth) + * [padLeft](#padleft) + * [padRight](#padright) + * [regexReplace](#regexreplace) + * [removeLeft](#removeleft) + * [removeRight](#removeright) + * [replace](#replace) + * [reverse](#reverse) + * [safeTruncate](#safetruncate) + * [shuffle](#shuffle) + * [slugify](#slugify) + * [startsWith](#startswith) + * [substr](#substr) + * [surround](#surround) + * [swapCase](#swapcase) + * [tidy](#tidy) + * [titleize](#titleize) + * [toAscii](#toascii) + * [toLowerCase](#tolowercase) + * [toSpaces](#tospaces) + * [toTabs](#totabs) + * [toTitleCase](#totitlecase) + * [toUpperCase](#touppercase) + * [trim](#trim) + * [trimLeft](#trimLeft) + * [trimRight](#trimRight) + * [truncate](#truncate) + * [underscored](#underscored) + * [upperCamelize](#uppercamelize) + * [upperCaseFirst](#uppercasefirst) +* [Links](#links) +* [Tests](#tests) +* [License](#license) + +## Requiring/Loading + +If you're using Composer to manage dependencies, you can include the following +in your composer.json file: + +```json +{ + "require": { + "danielstjules/stringy": "~1.10" + } +} +``` + +Then, after running `composer update` or `php composer.phar update`, you can +load the class using Composer's autoloading: + +```php +require 'vendor/autoload.php'; +``` + +Otherwise, you can simply require the file directly: + +```php +require_once 'path/to/Stringy/src/Stringy.php'; +// or +require_once 'path/to/Stringy/src/StaticStringy.php'; +``` + +And in either case, I'd suggest using an alias. + +```php +use Stringy\Stringy as S; +// or +use Stringy\StaticStringy as S; +``` + +## OO and Procedural + +The library offers both OO method chaining with `Stringy\Stringy`, as well as +procedural-style static method calls with `Stringy\StaticStringy`. An example +of the former is the following: + +```php +use Stringy\Stringy as S; +echo S::create('Fòô Bàř', 'UTF-8')->collapseWhitespace()->swapCase(); // 'fÒÔ bÀŘ' +``` + +`Stringy\Stringy` has a __toString() method, which returns the current string +when the object is used in a string context, ie: +`(string) S::create('foo') // 'foo'` + +Using the static wrapper, an alternative is the following: + +```php +use Stringy\StaticStringy as S; +$string = S::collapseWhitespace('Fòô Bàř', 'UTF-8'); +echo S::swapCase($string, 'UTF-8'); // 'fÒÔ bÀŘ' +``` + +## Implemented Interfaces + +`Stringy\Stringy` implements the `IteratorAggregate` interface, meaning that +`foreach` can be used with an instance of the class: + +``` php +$stringy = S::create('Fòô Bàř', 'UTF-8'); +foreach ($stringy as $char) { + echo $char; +} +// 'Fòô Bàř' +``` + +It implements the `Countable` interface, enabling the use of `count()` to +retrieve the number of characters in the string: + +``` php +$stringy = S::create('Fòô', 'UTF-8'); +count($stringy); // 3 +``` + +Furthermore, the `ArrayAccess` interface has been implemented. As a result, +`isset()` can be used to check if a character at a specific index exists. And +since `Stringy\Stringy` is immutable, any call to `offsetSet` or `offsetUnset` +will throw an exception. `offsetGet` has been implemented, however, and accepts +both positive and negative indexes. Invalid indexes result in an +`OutOfBoundsException`. + +``` php +$stringy = S::create('Bàř', 'UTF-8'); +echo $stringy[2]; // 'ř' +echo $stringy[-2]; // 'à' +isset($stringy[-4]); // false + +$stringy[3]; // OutOfBoundsException +$stringy[2] = 'a'; // Exception +``` + +## PHP 5.6 Creation + +As of PHP 5.6, [`use function`](https://wiki.php.net/rfc/use_function) is +available for importing functions. Stringy exposes a namespaced function, +`Stringy\create`, which emits the same behaviour as `Stringy\Stringy::create()`. +If running PHP 5.6, or another runtime that supports the `use function` syntax, +you can take advantage of an even simpler API as seen below: + +``` php +use function Stringy\create as s; + +// Instead of: S::create('Fòô Bàř', 'UTF-8') +s('Fòô Bàř', 'UTF-8')->collapseWhitespace()->swapCase(); +``` + +## Methods + +In the list below, any static method other than S::create refers to a method in +`Stringy\StaticStringy`. For all others, they're found in `Stringy\Stringy`. +Furthermore, all methods that return a Stringy object or string do not modify +the original. Stringy objects are immutable. + +*Note: If `$encoding` is not given, it defaults to `mb_internal_encoding()`.* + +#### at + +$stringy->at(int $index) + +S::at(int $index [, string $encoding ]) + +Returns the character at $index, with indexes starting at 0. + +```php +S::create('fòô bàř', 'UTF-8')->at(6); +S::at('fòô bàř', 6, 'UTF-8'); // 'ř' +``` + +#### camelize + +$stringy->camelize(); + +S::camelize(string $str [, string $encoding ]) + +Returns a camelCase version of the string. Trims surrounding spaces, +capitalizes letters following digits, spaces, dashes and underscores, +and removes spaces, dashes, as well as underscores. + +```php +S::create('Camel-Case')->camelize(); +S::camelize('Camel-Case'); // 'camelCase' +``` + +#### chars + +$stringy->chars(); + +S::chars(string $str [, string $encoding ]) + +Returns an array consisting of the characters in the string. + +```php +S::create('Fòô Bàř', 'UTF-8')->chars(); +S::chars('Fòô Bàř', 'UTF-8'); // array(F', 'ò', 'ô', ' ', 'B', 'à', 'ř') +``` + +#### collapseWhitespace + +$stringy->collapseWhitespace() + +S::collapseWhitespace(string $str [, string $encoding ]) + +Trims the string and replaces consecutive whitespace characters with a +single space. This includes tabs and newline characters, as well as +multibyte whitespace such as the thin space and ideographic space. + +```php +S::create(' Ο συγγραφέας ')->collapseWhitespace(); +S::collapseWhitespace(' Ο συγγραφέας '); // 'Ο συγγραφέας' +``` + +#### contains + +$stringy->contains(string $needle [, boolean $caseSensitive = true ]) + +S::contains(string $haystack, string $needle [, boolean $caseSensitive = true [, string $encoding ]]) + +Returns true if the string contains $needle, false otherwise. By default, +the comparison is case-sensitive, but can be made insensitive +by setting $caseSensitive to false. + +```php +S::create('Ο συγγραφέας είπε', 'UTF-8')->contains('συγγραφέας'); +S::contains('Ο συγγραφέας είπε', 'συγγραφέας', 'UTF-8'); // true +``` + +#### containsAll + +$stringy->containsAll(array $needles [, boolean $caseSensitive = true ]) + +S::containsAll(string $haystack, array $needles [, boolean $caseSensitive = true [, string $encoding ]]) + +Returns true if the string contains all $needles, false otherwise. By +default the comparison is case-sensitive, but can be made insensitive by +setting $caseSensitive to false. + +```php +S::create('Str contains foo and bar')->containsAll(array('foo', 'bar')); +S::containsAll('Str contains foo and bar', array('foo', 'bar')); // true +``` + +#### containsAny + +$stringy->containsAny(array $needles [, boolean $caseSensitive = true ]) + +S::containsAny(string $haystack, array $needles [, boolean $caseSensitive = true [, string $encoding ]]) + +Returns true if the string contains any $needles, false otherwise. By +default the comparison is case-sensitive, but can be made insensitive by +setting $caseSensitive to false. + +```php +S::create('Str contains foo')->containsAny(array('foo', 'bar')); +S::containsAny('Str contains foo', array('foo', 'bar')); // true +``` + +#### countSubstr + +$stringy->countSubstr(string $substring [, boolean $caseSensitive = true ]) + +S::countSubstr(string $str, string $substring [, boolean $caseSensitive = true [, string $encoding ]]) + +Returns the number of occurrences of $substring in the given string. +By default, the comparison is case-sensitive, but can be made insensitive +by setting $caseSensitive to false. + +```php +S::create('Ο συγγραφέας είπε', 'UTF-8')->countSubstr('α'); +S::countSubstr('Ο συγγραφέας είπε', 'α', 'UTF-8'); // 2 +``` + +#### create + +S::create(mixed $str [, $encoding ]) + +Creates a Stringy object and assigns both str and encoding properties +the supplied values. $str is cast to a string prior to assignment, and if +$encoding is not specified, it defaults to mb_internal_encoding(). It +then returns the initialized object. Throws an InvalidArgumentException +if the first argument is an array or object without a __toString method. + +```php +$stringy = S::create('fòô bàř', 'UTF-8'); // 'fòô bàř' +``` + +#### dasherize + +$stringy->dasherize(); + +S::dasherize(string $str [, string $encoding ]) + +Returns a lowercase and trimmed string separated by dashes. Dashes are +inserted before uppercase characters (with the exception of the first +character of the string), and in place of spaces as well as underscores. + +```php +S::create('TestDCase')->dasherize(); +S::dasherize('TestDCase'); // 'test-d-case' +``` + +#### delimit + +$stringy->delimit($delimiter); + +S::delimit(string $str [, string $delimiter, string $encoding ]) + +Returns a lowercase and trimmed string separated by the given delimiter. +Delimiters are inserted before uppercase characters (with the exception +of the first character of the string), and in place of spaces, dashes, +and underscores. Alpha delimiters are not converted to lowercase. + +```php +S::create('TestDCase')->delimit('>>'); +S::delimit('TestCase', '>>'); // 'test>>case' +``` + +#### endsWith + +$stringy->endsWith(string $substring [, boolean $caseSensitive = true ]) + +S::endsWith(string $str, string $substring [, boolean $caseSensitive = true [, string $encoding ]]) + +Returns true if the string ends with $substring, false otherwise. By +default, the comparison is case-sensitive, but can be made insensitive by +setting $caseSensitive to false. + +```php +S::create('FÒÔ bàřs', 'UTF-8')->endsWith('àřs', true); +S::endsWith('FÒÔ bàřs', 'àřs', true, 'UTF-8'); // true +``` + +#### ensureLeft + +$stringy->ensureLeft(string $substring) + +S::ensureLeft(string $substring [, string $encoding ]) + +Ensures that the string begins with $substring. If it doesn't, it's prepended. + +```php +S::create('foobar')->ensureLeft('http://'); +S::ensureLeft('foobar', 'http://'); // 'http://foobar' +``` + +#### ensureRight + +$stringy->ensureRight(string $substring) + +S::ensureRight(string $substring [, string $encoding ]) + +Ensures that the string begins with $substring. If it doesn't, it's appended. + +```php +S::create('foobar')->ensureRight('.com'); +S::ensureRight('foobar', '.com'); // 'foobar.com' +``` + +#### first + +$stringy->first(int $n) + +S::first(int $n [, string $encoding ]) + +Returns the first $n characters of the string. + +```php +S::create('fòô bàř', 'UTF-8')->first(3); +S::first('fòô bàř', 3, 'UTF-8'); // 'fòô' +``` + +#### getEncoding + +$stringy->getEncoding() + +Returns the encoding used by the Stringy object. + +```php +S::create('fòô bàř', 'UTF-8')->getEncoding(); // 'UTF-8' +``` + +#### hasLowerCase + +$stringy->hasLowerCase() + +S::hasLowerCase(string $str [, string $encoding ]) + +Returns true if the string contains a lower case char, false otherwise. + +```php +S::create('fòô bàř', 'UTF-8')->hasLowerCase(); +S::hasLowerCase('fòô bàř', 'UTF-8'); // true +``` + +#### hasUpperCase + +$stringy->hasUpperCase() + +S::hasUpperCase(string $str [, string $encoding ]) + +Returns true if the string contains an upper case char, false otherwise. + +```php +S::create('fòô bàř', 'UTF-8')->hasUpperCase(); +S::hasUpperCase('fòô bàř', 'UTF-8'); // false +``` + +#### htmlDecode + +$stringy->htmlDecode() + +S::htmlDecode(string $str [, int $flags, string $encoding ]) + +Convert all HTML entities to their applicable characters. + +```php +S::create('&')->htmlDecode(); +S::htmlDecode('&'); // '&' +``` + +#### htmlEncode + +$stringy->htmlEncode() + +S::htmlEncode(string $str [, int $flags, string $encoding ]) + +Convert all applicable characters to HTML entities. + +```php +S::create('&')->htmlEncode(); +S::htmlEncode('&'); // '&' +``` + +#### humanize + +$stringy->humanize() + +S::humanize(string $str [, string $encoding ]) + +Capitalizes the first word of the string, replaces underscores with +spaces, and strips '_id'. + +```php +S::create('author_id')->humanize(); +S::humanize('author_id'); // 'Author' +``` + +#### indexOf + +$stringy->indexOf(string $needle [, $offset = 0 ]); + +S::indexOf(string $haystack , string $needle [, $offset = 0 [, $encoding = null ]]) + +Returns the index of the first occurrence of $needle in the string, +and false if not found. Accepts an optional offset from which to begin +the search. + +```php +S::create('string', 'UTF-8')->indexOf('ing'); +S::indexOf('string', 'ing'); // 3 +``` + +#### indexOfLast + +$stringy->indexOfLast(string $needle [, $offset = 0 ]); + +S::indexOfLast(string $haystack , string $needle [, $offset = 0 [, $encoding = null ]]) + +Returns the index of the last occurrence of $needle in the string, +and false if not found. Accepts an optional offset from which to begin +the search. + +```php +S::create('string', 'UTF-8')->indexOfLast('ing'); +S::indexOfLast('string string', 'ing'); // 10 +``` + +#### insert + +$stringy->insert(int $index, string $substring) + +S::insert(string $str, int $index, string $substring [, string $encoding ]) + +Inserts $substring into the string at the $index provided. + +```php +S::create('fòô bà', 'UTF-8')->insert('ř', 6); +S::insert('fòô bà', 'ř', 6, 'UTF-8'); // 'fòô bàř' +``` + +#### isAlpha + +$stringy->isAlpha() + +S::isAlpha(string $str [, string $encoding ]) + +Returns true if the string contains only alphabetic chars, false otherwise. + +```php +S::create('丹尼爾', 'UTF-8')->isAlpha(); +S::isAlpha('丹尼爾', 'UTF-8'); // true +``` + +#### isAlphanumeric + +$stringy->isAlphanumeric() + +S::isAlphanumeric(string $str [, string $encoding ]) + +Returns true if the string contains only alphabetic and numeric chars, false +otherwise. + +```php +S::create('دانيال1', 'UTF-8')->isAlphanumeric(); +S::isAlphanumeric('دانيال1', 'UTF-8'); // true +``` + +#### isBlank + +$stringy->isBlank() + +S::isBlank(string $str [, string $encoding ]) + +Returns true if the string contains only whitespace chars, false otherwise. + +```php +S::create("\n\t \v\f")->isBlank(); +S::isBlank("\n\t \v\f"); // true +``` + +#### isHexadecimal + +$stringy->isHexadecimal() + +S::isHexadecimal(string $str [, string $encoding ]) + +Returns true if the string contains only hexadecimal chars, false otherwise. + +```php +S::create('A102F')->isHexadecimal(); +S::isHexadecimal('A102F'); // true +``` + +#### isJson + +$stringy->isJson() + +S::isJson(string $str [, string $encoding ]) + +Returns true if the string is JSON, false otherwise. + +```php +S::create('{"foo":"bar"}')->isJson(); +S::isJson('{"foo":"bar"}'); // true +``` + +#### isLowerCase + +$stringy->isLowerCase() + +S::isLowerCase(string $str [, string $encoding ]) + +Returns true if the string contains only lower case chars, false otherwise. + +```php +S::create('fòô bàř', 'UTF-8')->isLowerCase(); +S::isLowerCase('fòô bàř', 'UTF-8'); // true +``` + +#### isSerialized + +$stringy->isSerialized() + +S::isSerialized(string $str [, string $encoding ]) + +Returns true if the string is serialized, false otherwise. + +```php +S::create('a:1:{s:3:"foo";s:3:"bar";}', 'UTF-8')->isSerialized(); +S::isSerialized('a:1:{s:3:"foo";s:3:"bar";}', 'UTF-8'); // true +``` + +#### isUpperCase + +$stringy->isUpperCase() + +S::isUpperCase(string $str [, string $encoding ]) + +Returns true if the string contains only upper case chars, false otherwise. + +```php +S::create('FÒÔBÀŘ', 'UTF-8')->isUpperCase(); +S::isUpperCase('FÒÔBÀŘ', 'UTF-8'); // true +``` + +#### last + +$stringy->last(int $n) + +S::last(int $n [, string $encoding ]) + +Returns the last $n characters of the string. + +```php +S::create('fòô bàř', 'UTF-8')->last(3); +S::last('fòô bàř', 3, 'UTF-8'); // 'bàř' +``` + +#### length + +$stringy->length() + +S::length(string $str [, string $encoding ]) + +Returns the length of the string. An alias for PHP's mb_strlen() function. + +```php +S::create('fòô bàř', 'UTF-8')->length(); +S::length('fòô bàř', 'UTF-8'); // 7 +``` + +#### longestCommonPrefix + +$stringy->longestCommonPrefix(string $otherStr) + +S::longestCommonPrefix(string $str, string $otherStr [, $encoding ]) + +Returns the longest common prefix between the string and $otherStr. + +```php +S::create('fòô bar', 'UTF-8')->longestCommonPrefix('fòr bar'); +S::longestCommonPrefix('fòô bar', 'fòr bar', 'UTF-8'); // 'fò' +``` + +#### longestCommonSuffix + +$stringy->longestCommonSuffix(string $otherStr) + +S::longestCommonSuffix(string $str, string $otherStr [, $encoding ]) + +Returns the longest common suffix between the string and $otherStr. + +```php +S::create('fòô bàř', 'UTF-8')->longestCommonSuffix('fòr bàř'); +S::longestCommonSuffix('fòô bàř', 'fòr bàř', 'UTF-8'); // ' bàř' +``` + +#### longestCommonSubstring + +$stringy->longestCommonSubstring(string $otherStr) + +S::longestCommonSubstring(string $str, string $otherStr [, $encoding ]) + +Returns the longest common substring between the string and $otherStr. In the +case of ties, it returns that which occurs first. + +```php +S::create('foo bar')->longestCommonSubstring('boo far'); +S::longestCommonSubstring('foo bar', 'boo far'); // 'oo ' +``` + +#### lowerCaseFirst + +$stringy->lowerCaseFirst(); + +S::lowerCaseFirst(string $str [, string $encoding ]) + +Converts the first character of the supplied string to lower case. + +```php +S::create('Σ test', 'UTF-8')->lowerCaseFirst(); +S::lowerCaseFirst('Σ test', 'UTF-8'); // 'σ test' +``` + +#### pad + +$stringy->pad(int $length [, string $padStr = ' ' [, string $padType = 'right' ]]) + +S::pad(string $str , int $length [, string $padStr = ' ' [, string $padType = 'right' [, string $encoding ]]]) + +Pads the string to a given length with $padStr. If length is less than +or equal to the length of the string, no padding takes places. The default +string used for padding is a space, and the default type (one of 'left', +'right', 'both') is 'right'. Throws an InvalidArgumentException if +$padType isn't one of those 3 values. + +```php +S::create('fòô bàř', 'UTF-8')->pad( 10, '¬ø', 'left'); +S::pad('fòô bàř', 10, '¬ø', 'left', 'UTF-8'); // '¬ø¬fòô bàř' +``` + +#### padBoth + +$stringy->padBoth(int $length [, string $padStr = ' ' ]) + +S::padBoth(string $str , int $length [, string $padStr = ' ' [, string $encoding ]]) + +Returns a new string of a given length such that both sides of the string +string are padded. Alias for pad() with a $padType of 'both'. + +```php +S::create('foo bar')->padBoth(9, ' '); +S::padBoth('foo bar', 9, ' '); // ' foo bar ' +``` + +#### padLeft + +$stringy->padLeft(int $length [, string $padStr = ' ' ]) + +S::padLeft(string $str , int $length [, string $padStr = ' ' [, string $encoding ]]) + +Returns a new string of a given length such that the beginning of the +string is padded. Alias for pad() with a $padType of 'left'. + +```php +S::create($str, $encoding)->padLeft($length, $padStr); +S::padLeft('foo bar', 9, ' '); // ' foo bar' +``` + +#### padRight + +$stringy->padRight(int $length [, string $padStr = ' ' ]) + +S::padRight(string $str , int $length [, string $padStr = ' ' [, string $encoding ]]) + +Returns a new string of a given length such that the end of the string is +padded. Alias for pad() with a $padType of 'right'. + +```php +S::create('foo bar')->padRight(10, '_*'); +S::padRight('foo bar', 10, '_*'); // 'foo bar_*_' +``` + +#### regexReplace + +$stringy->regexReplace(string $pattern, string $replacement [, string $options = 'msr']) + +S::regexReplace(string $str, string $pattern, string $replacement [, string $options = 'msr' [, string $encoding ]]) + +Replaces all occurrences of $pattern in $str by $replacement. An alias +for mb_ereg_replace(). Note that the 'i' option with multibyte patterns +in mb_ereg_replace() requires PHP 5.4+. This is due to a lack of support +in the bundled version of Oniguruma in PHP 5.3. + +```php +S::create('fòô ', 'UTF-8')->regexReplace('f[òô]+\s', 'bàř', 'msr'); +S::regexReplace('fòô ', 'f[òô]+\s', 'bàř', 'msr', 'UTF-8'); // 'bàř' +``` + +#### removeLeft + +$stringy->removeLeft(string $substring) + +S::removeLeft(string $str, string $substring [, string $encoding ]) + +Returns a new string with the prefix $substring removed, if present. + +```php +S::create('fòô bàř', 'UTF-8')->removeLeft('fòô '); +S::removeLeft('fòô bàř', 'fòô ', 'UTF-8'); // 'bàř' +``` + +#### removeRight + +$stringy->removeRight(string $substring) + +S::removeRight(string $str, string $substring [, string $encoding ]) + +Returns a new string with the suffix $substring removed, if present. + +```php +S::create('fòô bàř', 'UTF-8')->removeRight(' bàř'); +S::removeRight('fòô bàř', ' bàř', 'UTF-8'); // 'fòô' +``` + +#### replace + +$stringy->replace(string $search, string $replacement) + +S::replace(string $str, string $search, string $replacement [, string $encoding ]) + +Replaces all occurrences of $search in $str by $replacement. + +```php +S::create('fòô bàř fòô bàř', 'UTF-8')->replace('fòô ', ''); +S::replace('fòô bàř fòô bàř', 'fòô ', '', 'UTF-8'); // 'bàř bàř' +``` + +#### reverse + +$stringy->reverse() + +S::reverse(string $str [, string $encoding ]) + +Returns a reversed string. A multibyte version of strrev(). + +```php +S::create('fòô bàř', 'UTF-8')->reverse(); +S::reverse('fòô bàř', 'UTF-8'); // 'řàb ôòf' +``` + +#### safeTruncate + +$stringy->safeTruncate(int $length [, string $substring = '' ]) + +S::safeTruncate(string $str, int $length [, string $substring = '' [, string $encoding ]]) + +Truncates the string to a given length, while ensuring that it does not +split words. If $substring is provided, and truncating occurs, the +string is further truncated so that the substring may be appended without +exceeding the desired length. + +```php +S::create('What are your plans today?')->safeTruncate(22, '...'); +S::safeTruncate('What are your plans today?', 22, '...'); // 'What are your plans...' +``` + +#### shuffle + +$stringy->shuffle() + +S::shuffle(string $str [, string $encoding ]) + +A multibyte str_shuffle() function. It returns a string with its characters in +random order. + +```php +S::create('fòô bàř', 'UTF-8')->shuffle(); +S::shuffle('fòô bàř', 'UTF-8'); // 'àôřb òf' +``` + +#### slugify + +$stringy->slugify([ string $replacement = '-' ]) + +S::slugify(string $str [, string $replacement = '-' ]) + +Converts the string into an URL slug. This includes replacing non-ASCII +characters with their closest ASCII equivalents, removing remaining +non-ASCII and non-alphanumeric characters, and replacing whitespace with +$replacement. The replacement defaults to a single dash, and the string +is also converted to lowercase. + +```php +S::create('Using strings like fòô bàř')->slugify(); +S::slugify('Using strings like fòô bàř'); // 'using-strings-like-foo-bar' +``` + +#### startsWith + +$stringy->startsWith(string $substring [, boolean $caseSensitive = true ]) + +S::startsWith(string $str, string $substring [, boolean $caseSensitive = true [, string $encoding ]]) + +Returns true if the string begins with $substring, false otherwise. +By default, the comparison is case-sensitive, but can be made insensitive +by setting $caseSensitive to false. + +```php +S::create('FÒÔ bàřs', 'UTF-8')->startsWith('fòô bàř', false); +S::startsWith('FÒÔ bàřs', 'fòô bàř', false, 'UTF-8'); // true +``` + +#### substr + +$stringy->substr(int $start [, int $length ]) + +S::substr(string $str, int $start [, int $length [, string $encoding ]]) + +Returns the substring beginning at $start with the specified $length. +It differs from the mb_substr() function in that providing a $length of +null will return the rest of the string, rather than an empty string. + +```php +S::create('fòô bàř', 'UTF-8')->substr(2, 3); +S::substr('fòô bàř', 2, 3, 'UTF-8'); // 'ô b' +``` + +#### surround + +$stringy->surround(string $substring) + +S::surround(string $str, string $substring) + +Surrounds a string with the given substring. + +```php +S::create(' ͜ ')->surround('ʘ'); +S::surround(' ͜ ', 'ʘ'); // 'ʘ ͜ ʘ' +``` + +#### swapCase + +$stringy->swapCase(); + +S::swapCase(string $str [, string $encoding ]) + +Returns a case swapped version of the string. + +```php +S::create('Ντανιλ', 'UTF-8')->swapCase(); +S::swapCase('Ντανιλ', 'UTF-8'); // 'νΤΑΝΙΛ' +``` + +#### tidy + +$stringy->tidy() + +S::tidy(string $str) + +Returns a string with smart quotes, ellipsis characters, and dashes from +Windows-1252 (commonly used in Word documents) replaced by their ASCII equivalents. + +```php +S::create('“I see…”')->tidy(); +S::tidy('“I see…”'); // '"I see..."' +``` + +#### titleize + +$stringy->titleize([ string $encoding ]) + +S::titleize(string $str [, array $ignore [, string $encoding ]]) + +Returns a trimmed string with the first letter of each word capitalized. +Ignores the case of other letters, preserving any acronyms. Also accepts +an array, $ignore, allowing you to list words not to be capitalized. + +```php +$ignore = array('at', 'by', 'for', 'in', 'of', 'on', 'out', 'to', 'the'); +S::create('i like to watch DVDs at home', 'UTF-8')->titleize($ignore); +S::titleize('i like to watch DVDs at home', $ignore, 'UTF-8'); +// 'I Like to Watch DVDs at Home' +``` + +#### toAscii + +$stringy->toAscii() + +S::toAscii(string $str [, boolean $removeUnsupported = true]) + +Returns an ASCII version of the string. A set of non-ASCII characters are +replaced with their closest ASCII counterparts, and the rest are removed +unless instructed otherwise. + +```php +S::create('fòô bàř')->toAscii(); +S::toAscii('fòô bàř'); // 'foo bar' +``` + +#### toLowerCase + +$stringy->toLowerCase() + +S::toLowerCase(string $str [, string $encoding ]) + +Converts all characters in the string to lowercase. An alias for PHP's +mb_strtolower(). + +```php +S::create('FÒÔ BÀŘ', 'UTF-8')->toLowerCase(); +S::toLowerCase('FÒÔ BÀŘ', 'UTF-8'); // 'fòô bàř' +``` + +#### toSpaces + +$stringy->toSpaces([ tabLength = 4 ]) + +S::toSpaces(string $str [, int $tabLength = 4 ]) + +Converts each tab in the string to some number of spaces, as defined by +$tabLength. By default, each tab is converted to 4 consecutive spaces. + +```php +S::create(' String speech = "Hi"')->toSpaces(); +S::toSpaces(' String speech = "Hi"'); // ' String speech = "Hi"' +``` + +#### toTabs + +$stringy->toTabs([ tabLength = 4 ]) + +S::toTabs(string $str [, int $tabLength = 4 ]) + +Converts each occurrence of some consecutive number of spaces, as defined +by $tabLength, to a tab. By default, each 4 consecutive spaces are +converted to a tab. + +```php +S::create(' fòô bàř')->toTabs(); +S::toTabs(' fòô bàř'); // ' fòô bàř' +``` + +#### toTitleCase + +$stringy->toTitleCase() + +S::toTitleCase(string $str [, string $encoding ]) + +Converts the first character of each word in the string to uppercase. + +```php +S::create('fòô bàř', 'UTF-8')->toTitleCase(); +S::toTitleCase('fòô bàř', 'UTF-8'); // 'Fòô Bàř' +``` + +#### toUpperCase + +$stringy->toUpperCase() + +S::toUpperCase(string $str [, string $encoding ]) + +Converts all characters in the string to uppercase. An alias for PHP's +mb_strtoupper(). + +```php +S::create('fòô bàř', 'UTF-8')->toUpperCase(); +S::toUpperCase('fòô bàř', 'UTF-8'); // 'FÒÔ BÀŘ' +``` + +#### trim + +$stringy->trim([, string $chars]) + +S::trim(string $str [, string $chars [, string $encoding ]]) + +Returns a string with whitespace removed from the start and end of the +string. Supports the removal of unicode whitespace. Accepts an optional +string of characters to strip instead of the defaults. + +```php +S::create(' fòô bàř ', 'UTF-8')->trim(); +S::trim(' fòô bàř '); // 'fòô bàř' +``` + +#### trimLeft + +$stringy->trimLeft([, string $chars]) + +S::trimLeft(string $str [, string $chars [, string $encoding ]]) + +Returns a string with whitespace removed from the start of the string. +Supports the removal of unicode whitespace. Accepts an optional +string of characters to strip instead of the defaults. + +```php +S::create(' fòô bàř ', 'UTF-8')->trimLeft(); +S::trimLeft(' fòô bàř '); // 'fòô bàř ' +``` + +#### trimRight + +$stringy->trimRight([, string $chars]) + +S::trimRight(string $str [, string $chars [, string $encoding ]]) + +Returns a string with whitespace removed from the end of the string. +Supports the removal of unicode whitespace. Accepts an optional +string of characters to strip instead of the defaults. + +```php +S::create(' fòô bàř ', 'UTF-8')->trimRight(); +S::trimRight(' fòô bàř '); // ' fòô bàř' +``` + +#### truncate + +$stringy->truncate(int $length [, string $substring = '' ]) + +S::truncate(string $str, int $length [, string $substring = '' [, string $encoding ]]) + +Truncates the string to a given length. If $substring is provided, and +truncating occurs, the string is further truncated so that the substring +may be appended without exceeding the desired length. + +```php +S::create('What are your plans today?')->truncate(19, '...'); +S::truncate('What are your plans today?', 19, '...'); // 'What are your pl...' +``` + +#### underscored + +$stringy->underscored(); + +S::underscored(string $str [, string $encoding ]) + +Returns a lowercase and trimmed string separated by underscores. +Underscores are inserted before uppercase characters (with the exception +of the first character of the string), and in place of spaces as well as dashes. + +```php +S::create('TestUCase')->underscored(); +S::underscored('TestUCase'); // 'test_u_case' +``` + +#### upperCamelize + +$stringy->upperCamelize(); + +S::upperCamelize(string $str [, string $encoding ]) + +Returns an UpperCamelCase version of the supplied string. It trims +surrounding spaces, capitalizes letters following digits, spaces, dashes +and underscores, and removes spaces, dashes, underscores. + +```php +S::create('Upper Camel-Case')->upperCamelize(); +S::upperCamelize('Upper Camel-Case'); // 'UpperCamelCase' +``` + +#### upperCaseFirst + +$stringy->upperCaseFirst(); + +S::upperCaseFirst(string $str [, string $encoding ]) + +Converts the first character of the supplied string to upper case. + +```php +S::create('σ test', 'UTF-8')->upperCaseFirst(); +S::upperCaseFirst('σ test', 'UTF-8'); // 'Σ test' +``` + +## Links + +The following is a list of libraries that extend Stringy: + + * [SliceableStringy](https://github.com/danielstjules/SliceableStringy): +Python-like string slices in PHP + * [SubStringy](https://github.com/TCB13/SubStringy): +Advanced substring methods + +## Tests + +From the project directory, tests can be ran using `phpunit` + +## License + +Released under the MIT License - see `LICENSE.txt` for details. diff --git a/core/vendor/danielstjules/stringy/composer.json b/core/vendor/danielstjules/stringy/composer.json new file mode 100644 index 0000000..f71f252 --- /dev/null +++ b/core/vendor/danielstjules/stringy/composer.json @@ -0,0 +1,35 @@ +{ + "name": "danielstjules/stringy", + "description": "A string manipulation library with multibyte support", + "keywords": [ + "multibyte", "string", "manipulation", "utility", "methods", "utf-8", + "helpers", "utils", "utf" + ], + "homepage": "https://github.com/danielstjules/Stringy", + "license": "MIT", + "authors": [ + { + "name": "Daniel St. Jules", + "email": "danielst.jules@gmail.com", + "homepage": "http://www.danielstjules.com" + } + ], + "require": { + "php": ">=5.3.0", + "ext-mbstring": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "support": { + "issues": "https://github.com/danielstjules/Stringy/issues", + "source": "https://github.com/danielstjules/Stringy" + }, + "autoload": { + "psr-4": { "Stringy\\": "src/" }, + "files": ["src/Create.php"] + }, + "autoload-dev": { + "classmap": [ "tests" ] + } +} diff --git a/core/vendor/danielstjules/stringy/phpunit.xml.dist b/core/vendor/danielstjules/stringy/phpunit.xml.dist new file mode 100644 index 0000000..987dbbf --- /dev/null +++ b/core/vendor/danielstjules/stringy/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + + tests/CommonTest.php + tests/StringyTest.php + tests/StaticStringyTest.php + tests/CreateTest.php + + + diff --git a/core/vendor/danielstjules/stringy/src/Create.php b/core/vendor/danielstjules/stringy/src/Create.php new file mode 100644 index 0000000..c6a2f44 --- /dev/null +++ b/core/vendor/danielstjules/stringy/src/Create.php @@ -0,0 +1,19 @@ +chars(); + } + + /** + * Converts the first character of the supplied string to upper case. + * + * @param string $str String to modify + * @param string $encoding The character encoding + * @return string String with the first character being upper case + */ + public static function upperCaseFirst($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->upperCaseFirst(); + } + + /** + * Converts the first character of the supplied string to lower case. + * + * @param string $str String to modify + * @param string $encoding The character encoding + * @return string String with the first character being lower case + */ + public static function lowerCaseFirst($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->lowerCaseFirst(); + } + + /** + * Returns a camelCase version of the string. Trims surrounding spaces, + * capitalizes letters following digits, spaces, dashes and underscores, + * and removes spaces, dashes, as well as underscores. + * + * @param string $str String to convert to camelCase + * @param string $encoding The character encoding + * @return string String in camelCase + */ + public static function camelize($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->camelize(); + } + + /** + * Returns an UpperCamelCase version of the supplied string. It trims + * surrounding spaces, capitalizes letters following digits, spaces, dashes + * and underscores, and removes spaces, dashes, underscores. + * + * @param string $str String to convert to UpperCamelCase + * @param string $encoding The character encoding + * @return string String in UpperCamelCase + */ + public static function upperCamelize($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->upperCamelize(); + } + + /** + * Returns a lowercase and trimmed string separated by dashes. Dashes are + * inserted before uppercase characters (with the exception of the first + * character of the string), and in place of spaces as well as underscores. + * + * @param string $str String to convert + * @param string $encoding The character encoding + * @return string Dasherized string + */ + public static function dasherize($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->dasherize(); + } + + /** + * Returns a lowercase and trimmed string separated by underscores. + * Underscores are inserted before uppercase characters (with the exception + * of the first character of the string), and in place of spaces as well as + * dashes. + * + * @param string $str String to convert + * @param string $encoding The character encoding + * @return string Underscored string + */ + public static function underscored($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->underscored(); + } + + /** + * Returns a lowercase and trimmed string separated by the given delimiter. + * Delimiters are inserted before uppercase characters (with the exception + * of the first character of the string), and in place of spaces, dashes, + * and underscores. Alpha delimiters are not converted to lowercase. + * + * @param string $str String to convert + * @param string $delimiter Sequence used to separate parts of the string + * @param string $encoding The character encoding + * @return string String with delimiter + */ + public static function delimit($str, $delimiter, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->delimit($delimiter); + } + + /** + * Returns a case swapped version of the string. + * + * @param string $str String to swap case + * @param string $encoding The character encoding + * @return string String with each character's case swapped + */ + public static function swapCase($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->swapCase(); + } + + /** + * Returns a trimmed string with the first letter of each word capitalized. + * Ignores the case of other letters, preserving any acronyms. Also accepts + * an array, $ignore, allowing you to list words not to be capitalized. + * + * @param string $str String to titleize + * @param string $encoding The character encoding + * @param array $ignore An array of words not to capitalize + * @return string Titleized string + */ + public static function titleize($str, $ignore = null, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->titleize($ignore); + } + + /** + * Capitalizes the first word of the string, replaces underscores with + * spaces, and strips '_id'. + * + * @param string $str String to humanize + * @param string $encoding The character encoding + * @return string A humanized string + */ + public static function humanize($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->humanize(); + } + + /** + * Returns a string with smart quotes, ellipsis characters, and dashes from + * Windows-1252 (commonly used in Word documents) replaced by their ASCII + * equivalents. + * + * @param string $str String to remove special chars + * @return string String with those characters removed + */ + public static function tidy($str) + { + return (string) Stringy::create($str)->tidy(); + } + + /** + * Trims the string and replaces consecutive whitespace characters with a + * single space. This includes tabs and newline characters, as well as + * multibyte whitespace such as the thin space and ideographic space. + * + * @param string $str The string to cleanup whitespace + * @param string $encoding The character encoding + * @return string The trimmed string with condensed whitespace + */ + public static function collapseWhitespace($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->collapseWhitespace(); + } + + /** + * Returns an ASCII version of the string. A set of non-ASCII characters are + * replaced with their closest ASCII counterparts, and the rest are removed + * unless instructed otherwise. + * + * @param string $str A string with non-ASCII characters + * @param bool $removeUnsupported Whether or not to remove the + * unsupported characters + * @return string A string containing only ASCII characters + */ + public static function toAscii($str, $removeUnsupported = true) + { + return (string) Stringy::create($str)->toAscii($removeUnsupported); + } + + /** + * Pads the string to a given length with $padStr. If length is less than + * or equal to the length of the string, no padding takes places. The + * default string used for padding is a space, and the default type (one of + * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException + * if $padType isn't one of those 3 values. + * + * @param string $str String to pad + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @param string $padType One of 'left', 'right', 'both' + * @param string $encoding The character encoding + * @return string The padded string + * @throws \InvalidArgumentException If $padType isn't one of 'right', + * 'left' or 'both' + */ + public static function pad($str, $length, $padStr = ' ', $padType = 'right', + $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->pad($length, $padStr, $padType); + } + + /** + * Returns a new string of a given length such that the beginning of the + * string is padded. Alias for pad() with a $padType of 'left'. + * + * @param string $str String to pad + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @param string $encoding The character encoding + * @return string The padded string + */ + public static function padLeft($str, $length, $padStr = ' ', $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->padLeft($length, $padStr); + } + + /** + * Returns a new string of a given length such that the end of the string + * is padded. Alias for pad() with a $padType of 'right'. + * + * @param string $str String to pad + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @param string $encoding The character encoding + * @return string The padded string + */ + public static function padRight($str, $length, $padStr = ' ', $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->padRight($length, $padStr); + } + + /** + * Returns a new string of a given length such that both sides of the + * string are padded. Alias for pad() with a $padType of 'both'. + * + * @param string $str String to pad + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @param string $encoding The character encoding + * @return string The padded string + */ + public static function padBoth($str, $length, $padStr = ' ', $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->padBoth($length, $padStr); + } + + /** + * Returns true if the string begins with $substring, false otherwise. By + * default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $str String to check the start of + * @param string $substring The substring to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @param string $encoding The character encoding + * @return bool Whether or not $str starts with $substring + */ + public static function startsWith($str, $substring, $caseSensitive = true, + $encoding = null) + { + return Stringy::create($str, $encoding) + ->startsWith($substring, $caseSensitive); + } + + /** + * Returns true if the string ends with $substring, false otherwise. By + * default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $str String to check the end of + * @param string $substring The substring to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @param string $encoding The character encoding + * @return bool Whether or not $str ends with $substring + */ + public static function endsWith($str, $substring, $caseSensitive = true, + $encoding = null) + { + return Stringy::create($str, $encoding) + ->endsWith($substring, $caseSensitive); + } + + /** + * Converts each tab in the string to some number of spaces, as defined by + * $tabLength. By default, each tab is converted to 4 consecutive spaces. + * + * @param string $str String to convert tabs to spaces + * @param int $tabLength Number of spaces to replace each tab with + * @return string String with tabs switched to spaces + */ + public static function toSpaces($str, $tabLength = 4) + { + return (string) Stringy::create($str)->toSpaces($tabLength); + } + + /** + * Converts each occurrence of some consecutive number of spaces, as + * defined by $tabLength, to a tab. By default, each 4 consecutive spaces + * are converted to a tab. + * + * @param string $str String to convert spaces to tabs + * @param int $tabLength Number of spaces to replace with a tab + * @return string String with spaces switched to tabs + */ + public static function toTabs($str, $tabLength = 4) + { + return (string) Stringy::create($str)->toTabs($tabLength); + } + + /** + * Converts all characters in the string to lowercase. An alias for PHP's + * mb_strtolower(). + * + * @param string $str String to convert case + * @param string $encoding The character encoding + * @return string The lowercase string + */ + public static function toLowerCase($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->toLowerCase(); + } + + /** + * Converts the first character of each word in the string to uppercase. + * + * @param string $str String to convert case + * @param string $encoding The character encoding + * @return string The title-cased string + */ + public static function toTitleCase($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->toTitleCase(); + } + + /** + * Converts all characters in the string to uppercase. An alias for PHP's + * mb_strtoupper(). + * + * @param string $str String to convert case + * @param string $encoding The character encoding + * @return string The uppercase string + */ + public static function toUpperCase($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->toUpperCase(); + } + + /** + * Converts the string into an URL slug. This includes replacing non-ASCII + * characters with their closest ASCII equivalents, removing remaining + * non-ASCII and non-alphanumeric characters, and replacing whitespace with + * $replacement. The replacement defaults to a single dash, and the string + * is also converted to lowercase. + * + * @param string $str Text to transform into an URL slug + * @param string $replacement The string used to replace whitespace + * @return string The corresponding URL slug + */ + public static function slugify($str, $replacement = '-') + { + return (string) Stringy::create($str)->slugify($replacement); + } + + /** + * Returns true if the string contains $needle, false otherwise. By default, + * the comparison is case-sensitive, but can be made insensitive by setting + * $caseSensitive to false. + * + * @param string $haystack String being checked + * @param string $needle Substring to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @param string $encoding The character encoding + * @return bool Whether or not $haystack contains $needle + */ + public static function contains($haystack, $needle, $caseSensitive = true, + $encoding = null) + { + return Stringy::create($haystack, $encoding) + ->contains($needle, $caseSensitive); + } + + /** + * Returns true if the string contains any $needles, false otherwise. By + * default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $haystack String being checked + * @param array $needles Substrings to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @param string $encoding The character encoding + * @return bool Whether or not $haystack contains any $needles + */ + public static function containsAny($haystack, $needles, + $caseSensitive = true, $encoding = null) + { + return Stringy::create($haystack, $encoding) + ->containsAny($needles, $caseSensitive); + } + + /** + * Returns true if the string contains all $needles, false otherwise. By + * default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $haystack String being checked + * @param array $needles Substrings to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @param string $encoding The character encoding + * @return bool Whether or not $haystack contains all $needles + */ + public static function containsAll($haystack, $needles, + $caseSensitive = true, $encoding = null) + { + return Stringy::create($haystack, $encoding) + ->containsAll($needles, $caseSensitive); + } + + /** + * Returns the index of the first occurrence of $needle in the string, + * and false if not found. Accepts an optional offset from which to begin + * the search. + * + * @param string $haystack String to search + * @param string $needle Substring to look for + * @param int $offset Offset from which to search + * @return int|bool The occurrence's index if found, otherwise false + */ + public static function indexOf($haystack, $needle, $offset = 0, + $encoding = null) + { + return Stringy::create($haystack, $encoding) + ->indexOf($needle, $offset); + } + + /** + * Returns the index of the last occurrence of $needle in the string, + * and false if not found. Accepts an optional offset from which to begin + * the search. + * + * @param string $haystack String to search + * @param string $needle Substring to look for + * @param int $offset Offset from which to search + * @return int|bool The last occurrence's index if found, otherwise false + */ + public static function indexOfLast($haystack, $needle, $offset = 0, + $encoding = null) + { + return Stringy::create($haystack, $encoding) + ->indexOfLast($needle, $offset); + } + + /** + * Surrounds a string with the given substring. + * + * @param string $str The string to surround + * @param string $substring The substring to add to both sides + * @return string The string with the substring prepended and appended + */ + public static function surround($str, $substring) + { + return (string) Stringy::create($str)->surround($substring); + } + + /** + * Inserts $substring into the string at the $index provided. + * + * @param string $str String to insert into + * @param string $substring String to be inserted + * @param int $index The index at which to insert the substring + * @param string $encoding The character encoding + * @return string The resulting string after the insertion + */ + public static function insert($str, $substring, $index, $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->insert($substring, $index); + } + + /** + * Truncates the string to a given length. If $substring is provided, and + * truncating occurs, the string is further truncated so that the substring + * may be appended without exceeding the desired length. + * + * @param string $str String to truncate + * @param int $length Desired length of the truncated string + * @param string $substring The substring to append if it can fit + * @param string $encoding The character encoding + * @return string The resulting string after truncating + */ + public static function truncate($str, $length, $substring = '', + $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->truncate($length, $substring); + } + + /** + * Truncates the string to a given length, while ensuring that it does not + * split words. If $substring is provided, and truncating occurs, the + * string is further truncated so that the substring may be appended without + * exceeding the desired length. + * + * @param string $str String to truncate + * @param int $length Desired length of the truncated string + * @param string $substring The substring to append if it can fit + * @param string $encoding The character encoding + * @return string The resulting string after truncating + */ + public static function safeTruncate($str, $length, $substring = '', + $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->safeTruncate($length, $substring); + } + + /** + * Returns a reversed string. A multibyte version of strrev(). + * + * @param string $str String to reverse + * @param string $encoding The character encoding + * @return string The reversed string + */ + public static function reverse($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->reverse(); + } + + /** + * A multibyte str_shuffle() function. It returns a string with its + * characters in random order. + * + * @param string $str String to shuffle + * @param string $encoding The character encoding + * @return string The shuffled string + */ + public static function shuffle($str, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->shuffle(); + } + + /** + * Returns a string with whitespace removed from the start and end of the + * string. Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $str String to trim + * @param string $chars Optional string of characters to strip + * @param string $encoding The character encoding + * @return string Trimmed $str + */ + public static function trim($str, $chars = null, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->trim($chars); + } + + /** + * Returns a string with whitespace removed from the start of the string. + * Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $str String to trim + * @param string $chars Optional string of characters to strip + * @param string $encoding The character encoding + * @return string Trimmed $str + */ + public static function trimLeft($str, $chars = null, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->trimLeft($chars); + } + + /** + * Returns a string with whitespace removed from the end of the string. + * Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $str String to trim + * @param string $chars Optional string of characters to strip + * @param string $encoding The character encoding + * @return string Trimmed $str + */ + public static function trimRight($str, $chars = null, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->trimRight($chars); + } + + /** + * Returns the longest common prefix between the string and $otherStr. + * + * @param string $str First string for comparison + * @param string $otherStr Second string for comparison + * @param string $encoding The character encoding + * @return string The longest common prefix + */ + public static function longestCommonPrefix($str, $otherStr, $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->longestCommonPrefix($otherStr); + } + + /** + * Returns the longest common suffix between the string and $otherStr. + * + * @param string $str First string for comparison + * @param string $otherStr Second string for comparison + * @param string $encoding The character encoding + * @return string The longest common suffix + */ + public static function longestCommonSuffix($str, $otherStr, $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->longestCommonSuffix($otherStr); + } + + /** + * Returns the longest common substring between the string and $otherStr. + * In the case of ties, it returns that which occurs first. + * + * @param string $str First string for comparison + * @param string $otherStr Second string for comparison + * @param string $encoding The character encoding + * @return string The longest common substring + */ + public static function longestCommonSubstring($str, $otherStr, + $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->longestCommonSubstring($otherStr); + } + + /** + * Returns the length of the string. An alias for PHP's mb_strlen() function. + * + * @param string $str The string to get the length of + * @param string $encoding The character encoding + * @return int The number of characters in $str given the encoding + */ + public static function length($str, $encoding = null) + { + return Stringy::create($str, $encoding)->length(); + } + + /** + * Returns the substring beginning at $start with the specified $length. + * It differs from the mb_substr() function in that providing a $length of + * null will return the rest of the string, rather than an empty string. + * + * @param string $str The string to get the length of + * @param int $start Position of the first character to use + * @param int $length Maximum number of characters used + * @param string $encoding The character encoding + * @return string The substring of $str + */ + public static function substr($str, $start, $length = null, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->substr($start, $length); + } + + /** + * Returns the character at $index, with indexes starting at 0. + * + * @param string $str The string from which to get the char + * @param int $index Position of the character + * @param string $encoding The character encoding + * @return string The character at $index + */ + public static function at($str, $index, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->at($index); + } + + /** + * Returns the first $n characters of the string. + * + * @param string $str The string from which to get the substring + * @param int $n Number of chars to retrieve from the start + * @param string $encoding The character encoding + * @return string The first $n characters + */ + public static function first($str, $n, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->first($n); + } + + /** + * Returns the last $n characters of the string. + * + * @param string $str The string from which to get the substring + * @param int $n Number of chars to retrieve from the end + * @param string $encoding The character encoding + * @return string The last $n characters + */ + public static function last($str, $n, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->last($n); + } + + /** + * Ensures that the string begins with $substring. If it doesn't, it's + * prepended. + * + * @param string $str The string to modify + * @param string $substring The substring to add if not present + * @param string $encoding The character encoding + * @return string The string prefixed by the $substring + */ + public static function ensureLeft($str, $substring, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->ensureLeft($substring); + } + + /** + * Ensures that the string begins with $substring. If it doesn't, it's + * appended. + * + * @param string $str The string to modify + * @param string $substring The substring to add if not present + * @param string $encoding The character encoding + * @return string The string suffixed by the $substring + */ + public static function ensureRight($str, $substring, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->ensureRight($substring); + } + + /** + * Returns a new string with the prefix $substring removed, if present. + * + * @param string $str String from which to remove the prefix + * @param string $substring The prefix to remove + * @param string $encoding The character encoding + * @return string The string without the prefix $substring + */ + public static function removeLeft($str, $substring, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->removeLeft($substring); + } + + /** + * Returns a new string with the suffix $substring removed, if present. + * + * @param string $str String from which to remove the suffix + * @param string $substring The suffix to remove + * @param string $encoding The character encoding + * @return string The string without the suffix $substring + */ + public static function removeRight($str, $substring, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->removeRight($substring); + } + + /** + * Returns true if the string contains a lower case char, false + * otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str contains a lower case character. + */ + public static function hasLowerCase($str, $encoding = null) + { + return Stringy::create($str, $encoding)->hasLowerCase(); + } + + /** + * Returns true if the string contains an upper case char, false + * otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str contains an upper case character. + */ + public static function hasUpperCase($str, $encoding = null) + { + return Stringy::create($str, $encoding)->hasUpperCase(); + } + + /** + * Returns true if the string contains only alphabetic chars, false + * otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str contains only alphabetic chars + */ + public static function isAlpha($str, $encoding = null) + { + return Stringy::create($str, $encoding)->isAlpha(); + } + + /** + * Returns true if the string contains only alphabetic and numeric chars, + * false otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str contains only alphanumeric chars + */ + public static function isAlphanumeric($str, $encoding = null) + { + return Stringy::create($str, $encoding)->isAlphanumeric(); + } + + /** + * Returns true if the string contains only whitespace chars, false + * otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str contains only whitespace characters + */ + public static function isBlank($str, $encoding = null) + { + return Stringy::create($str, $encoding)->isBlank(); + } + + /** + * Returns true if the string is JSON, false otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str is JSON + */ + public static function isJson($str, $encoding = null) + { + return Stringy::create($str, $encoding)->isJson(); + } + + /** + * Returns true if the string contains only lower case chars, false + * otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str contains only lower case characters + */ + public static function isLowerCase($str, $encoding = null) + { + return Stringy::create($str, $encoding)->isLowerCase(); + } + + /** + * Returns true if the string is serialized, false otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str is serialized + */ + public static function isSerialized($str, $encoding = null) + { + return Stringy::create($str, $encoding)->isSerialized(); + } + + /** + * Returns true if the string contains only upper case chars, false + * otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str contains only upper case characters + */ + public static function isUpperCase($str, $encoding = null) + { + return Stringy::create($str, $encoding)->isUpperCase(); + } + + /** + * Returns true if the string contains only hexadecimal chars, false + * otherwise. + * + * @param string $str String to check + * @param string $encoding The character encoding + * @return bool Whether or not $str contains only hexadecimal characters + */ + public static function isHexadecimal($str, $encoding = null) + { + return Stringy::create($str, $encoding)->isHexadecimal(); + } + + /** + * Returns the number of occurrences of $substring in the given string. + * By default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $str The string to search through + * @param string $substring The substring to search for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @param string $encoding The character encoding + * @return int The number of $substring occurrences + */ + public static function countSubstr($str, $substring, $caseSensitive = true, + $encoding = null) + { + return Stringy::create($str, $encoding) + ->countSubstr($substring, $caseSensitive); + } + + /** + * Replaces all occurrences of $search in $str by $replacement. + * + * @param string $str The haystack to search through + * @param string $search The needle to search for + * @param string $replacement The string to replace with + * @param string $encoding The character encoding + * @return string The resulting string after the replacements + */ + public static function replace($str, $search, $replacement, $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->replace($search, $replacement); + } + + /** + * Replaces all occurrences of $pattern in $str by $replacement. An alias + * for mb_ereg_replace(). Note that the 'i' option with multibyte patterns + * in mb_ereg_replace() requires PHP 5.4+. This is due to a lack of support + * in the bundled version of Oniguruma in PHP 5.3. + * + * @param string $str The haystack to search through + * @param string $pattern The regular expression pattern + * @param string $replacement The string to replace with + * @param string $options Matching conditions to be used + * @param string $encoding The character encoding + * @return string The resulting string after the replacements + */ + public static function regexReplace($str, $pattern, $replacement, + $options = 'msr', $encoding = null) + { + return (string) Stringy::create($str, $encoding) + ->regexReplace($pattern, $replacement, $options, $encoding); + } + + /** + * Convert all applicable characters to HTML entities. + * + * @param string $str The string to encode. + * @param int|null $flags See http://php.net/manual/en/function.htmlentities.php + * @param string $encoding The character encoding + * @return Stringy Object with the resulting $str after being html encoded. + */ + public static function htmlEncode($str, $flags = ENT_COMPAT, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->htmlEncode($flags); + } + + /** + * Convert all HTML entities to their applicable characters. + * + * @param string $str The string to decode. + * @param int|null $flags See http://php.net/manual/en/function.html-entity-decode.php + * @param string $encoding The character encoding + * @return Stringy Object with the resulting $str after being html decoded. + */ + public static function htmlDecode($str, $flags = ENT_COMPAT, $encoding = null) + { + return (string) Stringy::create($str, $encoding)->htmlDecode($flags); + } +} diff --git a/core/vendor/danielstjules/stringy/src/Stringy.php b/core/vendor/danielstjules/stringy/src/Stringy.php new file mode 100644 index 0000000..60b0694 --- /dev/null +++ b/core/vendor/danielstjules/stringy/src/Stringy.php @@ -0,0 +1,1558 @@ +str = (string) $str; + $this->encoding = $encoding ?: mb_internal_encoding(); + } + + /** + * Creates a Stringy object and assigns both str and encoding properties + * the supplied values. $str is cast to a string prior to assignment, and if + * $encoding is not specified, it defaults to mb_internal_encoding(). It + * then returns the initialized object. Throws an InvalidArgumentException + * if the first argument is an array or object without a __toString method. + * + * @param mixed $str Value to modify, after being cast to string + * @param string $encoding The character encoding + * @return Stringy A Stringy object + * @throws \InvalidArgumentException if an array or object without a + * __toString method is passed as the first argument + */ + public static function create($str, $encoding = null) + { + return new static($str, $encoding); + } + + /** + * Returns the value in $str. + * + * @return string The current value of the $str property + */ + public function __toString() + { + return $this->str; + } + + /** + * Returns the encoding used by the Stringy object. + * + * @return string The current value of the $encoding property + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Returns the length of the string, implementing the countable interface. + * + * @return int The number of characters in the string, given the encoding + */ + public function count() + { + return $this->length(); + } + + /** + * Returns a new ArrayIterator, thus implementing the IteratorAggregate + * interface. The ArrayIterator's constructor is passed an array of chars + * in the multibyte string. This enables the use of foreach with instances + * of Stringy\Stringy. + * + * @return \ArrayIterator An iterator for the characters in the string + */ + public function getIterator() + { + return new \ArrayIterator($this->chars()); + } + + /** + * Returns whether or not a character exists at an index. Offsets may be + * negative to count from the last character in the string. Implements + * part of the ArrayAccess interface. + * + * @param mixed $offset The index to check + * @return boolean Whether or not the index exists + */ + public function offsetExists($offset) + { + $length = $this->length(); + $offset = (int) $offset; + + if ($offset >= 0) { + return ($length > $offset); + } + + return ($length >= abs($offset)); + } + + /** + * Returns the character at the given index. Offsets may be negative to + * count from the last character in the string. Implements part of the + * ArrayAccess interface, and throws an OutOfBoundsException if the index + * does not exist. + * + * @param mixed $offset The index from which to retrieve the char + * @return mixed The character at the specified index + * @throws \OutOfBoundsException If the positive or negative offset does + * not exist + */ + public function offsetGet($offset) + { + $offset = (int) $offset; + $length = $this->length(); + + if (($offset >= 0 && $length <= $offset) || $length < abs($offset)) { + throw new \OutOfBoundsException('No character exists at the index'); + } + + return mb_substr($this->str, $offset, 1, $this->encoding); + } + + /** + * Implements part of the ArrayAccess interface, but throws an exception + * when called. This maintains the immutability of Stringy objects. + * + * @param mixed $offset The index of the character + * @param mixed $value Value to set + * @throws \Exception When called + */ + public function offsetSet($offset, $value) + { + // Stringy is immutable, cannot directly set char + throw new \Exception('Stringy object is immutable, cannot modify char'); + } + + /** + * Implements part of the ArrayAccess interface, but throws an exception + * when called. This maintains the immutability of Stringy objects. + * + * @param mixed $offset The index of the character + * @throws \Exception When called + */ + public function offsetUnset($offset) + { + // Don't allow directly modifying the string + throw new \Exception('Stringy object is immutable, cannot unset char'); + } + + /** + * Returns an array consisting of the characters in the string. + * + * @return array An array of string chars + */ + public function chars() + { + $chars = array(); + for ($i = 0, $l = $this->length(); $i < $l; $i++) { + $chars[] = $this->at($i)->str; + } + + return $chars; + } + + /** + * Converts the first character of the supplied string to upper case. + * + * @return Stringy Object with the first character of $str being upper case + */ + public function upperCaseFirst() + { + $first = mb_substr($this->str, 0, 1, $this->encoding); + $rest = mb_substr($this->str, 1, $this->length() - 1, + $this->encoding); + + $str = mb_strtoupper($first, $this->encoding) . $rest; + + return static::create($str, $this->encoding); + } + + /** + * Converts the first character of the string to lower case. + * + * @return Stringy Object with the first character of $str being lower case + */ + public function lowerCaseFirst() + { + $first = mb_substr($this->str, 0, 1, $this->encoding); + $rest = mb_substr($this->str, 1, $this->length() - 1, + $this->encoding); + + $str = mb_strtolower($first, $this->encoding) . $rest; + + return static::create($str, $this->encoding); + } + + /** + * Returns a camelCase version of the string. Trims surrounding spaces, + * capitalizes letters following digits, spaces, dashes and underscores, + * and removes spaces, dashes, as well as underscores. + * + * @return Stringy Object with $str in camelCase + */ + public function camelize() + { + $encoding = $this->encoding; + $stringy = $this->trim()->lowerCaseFirst(); + + $camelCase = preg_replace_callback( + '/[-_\s]+(.)?/u', + function ($match) use ($encoding) { + return $match[1] ? mb_strtoupper($match[1], $encoding) : ''; + }, + $stringy->str + ); + + $stringy->str = preg_replace_callback( + '/[\d]+(.)?/u', + function ($match) use ($encoding) { + return mb_strtoupper($match[0], $encoding); + }, + $camelCase + ); + + return $stringy; + } + + /** + * Returns an UpperCamelCase version of the supplied string. It trims + * surrounding spaces, capitalizes letters following digits, spaces, dashes + * and underscores, and removes spaces, dashes, underscores. + * + * @return Stringy Object with $str in UpperCamelCase + */ + public function upperCamelize() + { + return $this->camelize()->upperCaseFirst(); + } + + /** + * Returns a lowercase and trimmed string separated by dashes. Dashes are + * inserted before uppercase characters (with the exception of the first + * character of the string), and in place of spaces as well as underscores. + * + * @return Stringy Object with a dasherized $str + */ + public function dasherize() + { + return $this->delimit('-'); + } + + /** + * Returns a lowercase and trimmed string separated by underscores. + * Underscores are inserted before uppercase characters (with the exception + * of the first character of the string), and in place of spaces as well as + * dashes. + * + * @return Stringy Object with an underscored $str + */ + public function underscored() + { + return $this->delimit('_'); + } + + /** + * Returns a lowercase and trimmed string separated by the given delimiter. + * Delimiters are inserted before uppercase characters (with the exception + * of the first character of the string), and in place of spaces, dashes, + * and underscores. Alpha delimiters are not converted to lowercase. + * + * @param string $delimiter Sequence used to separate parts of the string + * @return Stringy Object with a delimited $str + */ + public function delimit($delimiter) + { + // Save current regex encoding so we can reset it after + $regexEncoding = mb_regex_encoding(); + mb_regex_encoding($this->encoding); + + $str = mb_ereg_replace('\B([A-Z])', '-\1', $this->trim()); + $str = mb_strtolower($str, $this->encoding); + $str = mb_ereg_replace('[-_\s]+', $delimiter, $str); + + mb_regex_encoding($regexEncoding); + + return static::create($str, $this->encoding); + } + + /** + * Returns a case swapped version of the string. + * + * @return Stringy Object whose $str has each character's case swapped + */ + public function swapCase() + { + $stringy = static::create($this->str, $this->encoding); + $encoding = $stringy->encoding; + + $stringy->str = preg_replace_callback( + '/[\S]/u', + function ($match) use ($encoding) { + if ($match[0] == mb_strtoupper($match[0], $encoding)) { + return mb_strtolower($match[0], $encoding); + } else { + return mb_strtoupper($match[0], $encoding); + } + }, + $stringy->str + ); + + return $stringy; + } + + /** + * Returns a trimmed string with the first letter of each word capitalized. + * Ignores the case of other letters, preserving any acronyms. Also accepts + * an array, $ignore, allowing you to list words not to be capitalized. + * + * @param array $ignore An array of words not to capitalize + * @return Stringy Object with a titleized $str + */ + public function titleize($ignore = null) + { + $buffer = $this->trim(); + $encoding = $this->encoding; + + $buffer = preg_replace_callback( + '/([\S]+)/u', + function ($match) use ($encoding, $ignore) { + if ($ignore && in_array($match[0], $ignore)) { + return $match[0]; + } else { + $stringy = new Stringy($match[0], $encoding); + return (string) $stringy->upperCaseFirst(); + } + }, + $buffer + ); + + return new Stringy($buffer, $encoding); + } + + /** + * Capitalizes the first word of the string, replaces underscores with + * spaces, and strips '_id'. + * + * @return Stringy Object with a humanized $str + */ + public function humanize() + { + $str = str_replace(array('_id', '_'), array('', ' '), $this->str); + + return static::create($str, $this->encoding)->trim()->upperCaseFirst(); + } + + /** + * Returns a string with smart quotes, ellipsis characters, and dashes from + * Windows-1252 (commonly used in Word documents) replaced by their ASCII + * equivalents. + * + * @return Stringy Object whose $str has those characters removed + */ + public function tidy() + { + $str = preg_replace(array( + '/\x{2026}/u', + '/[\x{201C}\x{201D}]/u', + '/[\x{2018}\x{2019}]/u', + '/[\x{2013}\x{2014}]/u', + ), array( + '...', + '"', + "'", + '-', + ), $this->str); + + return static::create($str, $this->encoding); + } + + /** + * Trims the string and replaces consecutive whitespace characters with a + * single space. This includes tabs and newline characters, as well as + * multibyte whitespace such as the thin space and ideographic space. + * + * @return Stringy Object with a trimmed $str and condensed whitespace + */ + public function collapseWhitespace() + { + return $this->regexReplace('[[:space:]]+', ' ')->trim(); + } + + /** + * Returns an ASCII version of the string. A set of non-ASCII characters are + * replaced with their closest ASCII counterparts, and the rest are removed + * unless instructed otherwise. + * + * @param bool $removeUnsupported Whether or not to remove the + * unsupported characters + * @return Stringy Object whose $str contains only ASCII characters + */ + public function toAscii($removeUnsupported = true) + { + $str = $this->str; + + foreach ($this->charsArray() as $key => $value) { + $str = str_replace($value, $key, $str); + } + + if ($removeUnsupported) { + $str = preg_replace('/[^\x20-\x7E]/u', '', $str); + } + + return static::create($str, $this->encoding); + } + + /** + * Returns the replacements for the toAscii() method. + * + * @return array An array of replacements. + */ + protected function charsArray() + { + static $charsArray; + if (isset($charsArray)) return $charsArray; + + return $charsArray = array( + 'a' => array( + 'à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', + 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ä', 'ā', 'ą', + 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', + 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', + 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ'), + 'b' => array('б', 'β', 'Ъ', 'Ь', 'ب'), + 'c' => array('ç', 'ć', 'č', 'ĉ', 'ċ'), + 'd' => array('ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', + 'д', 'δ', 'د', 'ض'), + 'e' => array('é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', + 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', + 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', + 'є', 'ə'), + 'f' => array('ф', 'φ', 'ف'), + 'g' => array('ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ج'), + 'h' => array('ĥ', 'ħ', 'η', 'ή', 'ح', 'ه'), + 'i' => array('í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', + 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', + 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', + 'ῗ', 'і', 'ї', 'и'), + 'j' => array('ĵ', 'ј', 'Ј'), + 'k' => array('ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك'), + 'l' => array('ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل'), + 'm' => array('м', 'μ', 'م'), + 'n' => array('ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن'), + 'o' => array('ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', + 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', + 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', + 'ö', 'о', 'و', 'θ'), + 'p' => array('п', 'π'), + 'r' => array('ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر'), + 's' => array('ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص'), + 't' => array('ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط'), + 'u' => array('ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', + 'ự', 'ü', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у'), + 'v' => array('в'), + 'w' => array('ŵ', 'ω', 'ώ'), + 'x' => array('χ'), + 'y' => array('ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', + 'ϋ', 'ύ', 'ΰ', 'ي'), + 'z' => array('ź', 'ž', 'ż', 'з', 'ζ', 'ز'), + 'aa' => array('ع'), + 'ae' => array('æ'), + 'ch' => array('ч'), + 'dj' => array('ђ', 'đ'), + 'dz' => array('џ'), + 'gh' => array('غ'), + 'kh' => array('х', 'خ'), + 'lj' => array('љ'), + 'nj' => array('њ'), + 'oe' => array('œ'), + 'ps' => array('ψ'), + 'sh' => array('ш'), + 'shch' => array('щ'), + 'ss' => array('ß'), + 'th' => array('þ', 'ث', 'ذ', 'ظ'), + 'ts' => array('ц'), + 'ya' => array('я'), + 'yu' => array('ю'), + 'zh' => array('ж'), + '(c)' => array('©'), + 'A' => array('Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', + 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Ä', 'Å', 'Ā', + 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', + 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', + 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А'), + 'B' => array('Б', 'Β'), + 'C' => array('Ç','Ć', 'Č', 'Ĉ', 'Ċ'), + 'D' => array('Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ'), + 'E' => array('É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', + 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', + 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', + 'Є', 'Ə'), + 'F' => array('Ф', 'Φ'), + 'G' => array('Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ'), + 'H' => array('Η', 'Ή'), + 'I' => array('Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', + 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', + 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї'), + 'K' => array('К', 'Κ'), + 'L' => array('Ĺ', 'Ł', 'Л', 'Λ', 'Ļ'), + 'M' => array('М', 'Μ'), + 'N' => array('Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν'), + 'O' => array('Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', + 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ö', 'Ø', 'Ō', + 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', + 'Ὸ', 'Ό', 'О', 'Θ', 'Ө'), + 'P' => array('П', 'Π'), + 'R' => array('Ř', 'Ŕ', 'Р', 'Ρ'), + 'S' => array('Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ'), + 'T' => array('Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ'), + 'U' => array('Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', + 'Ự', 'Û', 'Ü', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У'), + 'V' => array('В'), + 'W' => array('Ω', 'Ώ'), + 'X' => array('Χ'), + 'Y' => array('Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', + 'Ы', 'Й', 'Υ', 'Ϋ'), + 'Z' => array('Ź', 'Ž', 'Ż', 'З', 'Ζ'), + 'AE' => array('Æ'), + 'CH' => array('Ч'), + 'DJ' => array('Ђ'), + 'DZ' => array('Џ'), + 'KH' => array('Х'), + 'LJ' => array('Љ'), + 'NJ' => array('Њ'), + 'PS' => array('Ψ'), + 'SH' => array('Ш'), + 'SHCH' => array('Щ'), + 'SS' => array('ẞ'), + 'TH' => array('Þ'), + 'TS' => array('Ц'), + 'YA' => array('Я'), + 'YU' => array('Ю'), + 'ZH' => array('Ж'), + ' ' => array("\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", + "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", + "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", + "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", + "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80"), + ); + } + + /** + * Pads the string to a given length with $padStr. If length is less than + * or equal to the length of the string, no padding takes places. The + * default string used for padding is a space, and the default type (one of + * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException + * if $padType isn't one of those 3 values. + * + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @param string $padType One of 'left', 'right', 'both' + * @return Stringy Object with a padded $str + * @throws InvalidArgumentException If $padType isn't one of 'right', + * 'left' or 'both' + */ + public function pad($length, $padStr = ' ', $padType = 'right') + { + if (!in_array($padType, array('left', 'right', 'both'))) { + throw new \InvalidArgumentException('Pad expects $padType ' . + "to be one of 'left', 'right' or 'both'"); + } + + switch ($padType) { + case 'left': + return $this->padLeft($length, $padStr); + case 'right': + return $this->padRight($length, $padStr); + default: + return $this->padBoth($length, $padStr); + } + } + + /** + * Returns a new string of a given length such that the beginning of the + * string is padded. Alias for pad() with a $padType of 'left'. + * + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @return Stringy String with left padding + */ + public function padLeft($length, $padStr = ' ') + { + return $this->applyPadding($length - $this->length(), 0, $padStr); + } + + /** + * Returns a new string of a given length such that the end of the string + * is padded. Alias for pad() with a $padType of 'right'. + * + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @return Stringy String with right padding + */ + public function padRight($length, $padStr = ' ') + { + return $this->applyPadding(0, $length - $this->length(), $padStr); + } + + /** + * Returns a new string of a given length such that both sides of the + * string are padded. Alias for pad() with a $padType of 'both'. + * + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @return Stringy String with padding applied + */ + public function padBoth($length, $padStr = ' ') + { + $padding = $length - $this->length(); + + return $this->applyPadding(floor($padding / 2), ceil($padding / 2), + $padStr); + } + + /** + * Adds the specified amount of left and right padding to the given string. + * The default character used is a space. + * + * @param int $left Length of left padding + * @param int $right Length of right padding + * @param string $padStr String used to pad + * @return Stringy String with padding applied + */ + private function applyPadding($left = 0, $right = 0, $padStr = ' ') + { + $stringy = static::create($this->str, $this->encoding); + $length = mb_strlen($padStr, $stringy->encoding); + + $strLength = $stringy->length(); + $paddedLength = $strLength + $left + $right; + + if (!$length || $paddedLength <= $strLength) { + return $stringy; + } + + $leftPadding = mb_substr(str_repeat($padStr, ceil($left / $length)), 0, + $left, $stringy->encoding); + $rightPadding = mb_substr(str_repeat($padStr, ceil($right / $length)), + 0, $right, $stringy->encoding); + + $stringy->str = $leftPadding . $stringy->str . $rightPadding; + + return $stringy; + } + + /** + * Returns true if the string begins with $substring, false otherwise. By + * default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $substring The substring to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str starts with $substring + */ + public function startsWith($substring, $caseSensitive = true) + { + $substringLength = mb_strlen($substring, $this->encoding); + $startOfStr = mb_substr($this->str, 0, $substringLength, + $this->encoding); + + if (!$caseSensitive) { + $substring = mb_strtolower($substring, $this->encoding); + $startOfStr = mb_strtolower($startOfStr, $this->encoding); + } + + return (string) $substring === $startOfStr; + } + + /** + * Returns true if the string ends with $substring, false otherwise. By + * default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $substring The substring to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str ends with $substring + */ + public function endsWith($substring, $caseSensitive = true) + { + $substringLength = mb_strlen($substring, $this->encoding); + $strLength = $this->length(); + + $endOfStr = mb_substr($this->str, $strLength - $substringLength, + $substringLength, $this->encoding); + + if (!$caseSensitive) { + $substring = mb_strtolower($substring, $this->encoding); + $endOfStr = mb_strtolower($endOfStr, $this->encoding); + } + + return (string) $substring === $endOfStr; + } + + /** + * Converts each tab in the string to some number of spaces, as defined by + * $tabLength. By default, each tab is converted to 4 consecutive spaces. + * + * @param int $tabLength Number of spaces to replace each tab with + * @return Stringy Object whose $str has had tabs switched to spaces + */ + public function toSpaces($tabLength = 4) + { + $spaces = str_repeat(' ', $tabLength); + $str = str_replace("\t", $spaces, $this->str); + + return static::create($str, $this->encoding); + } + + /** + * Converts each occurrence of some consecutive number of spaces, as + * defined by $tabLength, to a tab. By default, each 4 consecutive spaces + * are converted to a tab. + * + * @param int $tabLength Number of spaces to replace with a tab + * @return Stringy Object whose $str has had spaces switched to tabs + */ + public function toTabs($tabLength = 4) + { + $spaces = str_repeat(' ', $tabLength); + $str = str_replace($spaces, "\t", $this->str); + + return static::create($str, $this->encoding); + } + + /** + * Converts the first character of each word in the string to uppercase. + * + * @return Stringy Object with all characters of $str being title-cased + */ + public function toTitleCase() + { + $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Converts all characters in the string to lowercase. An alias for PHP's + * mb_strtolower(). + * + * @return Stringy Object with all characters of $str being lowercase + */ + public function toLowerCase() + { + $str = mb_strtolower($this->str, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Converts all characters in the string to uppercase. An alias for PHP's + * mb_strtoupper(). + * + * @return Stringy Object with all characters of $str being uppercase + */ + public function toUpperCase() + { + $str = mb_strtoupper($this->str, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Converts the string into an URL slug. This includes replacing non-ASCII + * characters with their closest ASCII equivalents, removing remaining + * non-ASCII and non-alphanumeric characters, and replacing whitespace with + * $replacement. The replacement defaults to a single dash, and the string + * is also converted to lowercase. + * + * @param string $replacement The string used to replace whitespace + * @return Stringy Object whose $str has been converted to an URL slug + */ + public function slugify($replacement = '-') + { + $stringy = $this->toAscii(); + + $quotedReplacement = preg_quote($replacement); + $pattern = "/[^a-zA-Z\d\s-_$quotedReplacement]/u"; + $stringy->str = preg_replace($pattern, '', $stringy); + + return $stringy->toLowerCase()->delimit($replacement) + ->removeLeft($replacement)->removeRight($replacement); + } + + /** + * Returns true if the string contains $needle, false otherwise. By default + * the comparison is case-sensitive, but can be made insensitive by setting + * $caseSensitive to false. + * + * @param string $needle Substring to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str contains $needle + */ + public function contains($needle, $caseSensitive = true) + { + $encoding = $this->encoding; + + if ($caseSensitive) { + return (mb_strpos($this->str, $needle, 0, $encoding) !== false); + } else { + return (mb_stripos($this->str, $needle, 0, $encoding) !== false); + } + } + + /** + * Returns true if the string contains any $needles, false otherwise. By + * default the comparison is case-sensitive, but can be made insensitive by + * setting $caseSensitive to false. + * + * @param array $needles Substrings to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str contains $needle + */ + public function containsAny($needles, $caseSensitive = true) + { + if (empty($needles)) { + return false; + } + + foreach ($needles as $needle) { + if ($this->contains($needle, $caseSensitive)) { + return true; + } + } + + return false; + } + + /** + * Returns true if the string contains all $needles, false otherwise. By + * default the comparison is case-sensitive, but can be made insensitive by + * setting $caseSensitive to false. + * + * @param array $needles Substrings to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str contains $needle + */ + public function containsAll($needles, $caseSensitive = true) + { + if (empty($needles)) { + return false; + } + + foreach ($needles as $needle) { + if (!$this->contains($needle, $caseSensitive)) { + return false; + } + } + + return true; + } + + /** + * Returns the index of the first occurrence of $needle in the string, + * and false if not found. Accepts an optional offset from which to begin + * the search. + * + * @param string $needle Substring to look for + * @param int $offset Offset from which to search + * @return int|bool The occurrence's index if found, otherwise false + */ + public function indexOf($needle, $offset = 0) + { + return mb_strpos($this->str, (string) $needle, + (int) $offset, $this->encoding); + } + + /** + * Returns the index of the last occurrence of $needle in the string, + * and false if not found. Accepts an optional offset from which to begin + * the search. + * + * @param string $needle Substring to look for + * @param int $offset Offset from which to search + * @return int|bool The last occurrence's index if found, otherwise false + */ + public function indexOfLast($needle, $offset = 0) + { + return mb_strrpos($this->str, (string) $needle, + (int) $offset, $this->encoding); + } + + /** + * Surrounds $str with the given substring. + * + * @param string $substring The substring to add to both sides + * @return Stringy Object whose $str had the substring both prepended and + * appended + */ + public function surround($substring) + { + $str = implode('', array($substring, $this->str, $substring)); + + return static::create($str, $this->encoding); + } + + /** + * Inserts $substring into the string at the $index provided. + * + * @param string $substring String to be inserted + * @param int $index The index at which to insert the substring + * @return Stringy Object with the resulting $str after the insertion + */ + public function insert($substring, $index) + { + $stringy = static::create($this->str, $this->encoding); + if ($index > $stringy->length()) { + return $stringy; + } + + $start = mb_substr($stringy->str, 0, $index, $stringy->encoding); + $end = mb_substr($stringy->str, $index, $stringy->length(), + $stringy->encoding); + + $stringy->str = $start . $substring . $end; + + return $stringy; + } + + /** + * Truncates the string to a given length. If $substring is provided, and + * truncating occurs, the string is further truncated so that the substring + * may be appended without exceeding the desired length. + * + * @param int $length Desired length of the truncated string + * @param string $substring The substring to append if it can fit + * @return Stringy Object with the resulting $str after truncating + */ + public function truncate($length, $substring = '') + { + $stringy = static::create($this->str, $this->encoding); + if ($length >= $stringy->length()) { + return $stringy; + } + + // Need to further trim the string so we can append the substring + $substringLength = mb_strlen($substring, $stringy->encoding); + $length = $length - $substringLength; + + $truncated = mb_substr($stringy->str, 0, $length, $stringy->encoding); + $stringy->str = $truncated . $substring; + + return $stringy; + } + + /** + * Truncates the string to a given length, while ensuring that it does not + * split words. If $substring is provided, and truncating occurs, the + * string is further truncated so that the substring may be appended without + * exceeding the desired length. + * + * @param int $length Desired length of the truncated string + * @param string $substring The substring to append if it can fit + * @return Stringy Object with the resulting $str after truncating + */ + public function safeTruncate($length, $substring = '') + { + $stringy = static::create($this->str, $this->encoding); + if ($length >= $stringy->length()) { + return $stringy; + } + + // Need to further trim the string so we can append the substring + $encoding = $stringy->encoding; + $substringLength = mb_strlen($substring, $encoding); + $length = $length - $substringLength; + + $truncated = mb_substr($stringy->str, 0, $length, $encoding); + + // If the last word was truncated + if (mb_strpos($stringy->str, ' ', $length - 1, $encoding) != $length) { + // Find pos of the last occurrence of a space, get up to that + $lastPos = mb_strrpos($truncated, ' ', 0, $encoding); + $truncated = mb_substr($truncated, 0, $lastPos, $encoding); + } + + $stringy->str = $truncated . $substring; + + return $stringy; + } + + /** + * Returns a reversed string. A multibyte version of strrev(). + * + * @return Stringy Object with a reversed $str + */ + public function reverse() + { + $strLength = $this->length(); + $reversed = ''; + + // Loop from last index of string to first + for ($i = $strLength - 1; $i >= 0; $i--) { + $reversed .= mb_substr($this->str, $i, 1, $this->encoding); + } + + return static::create($reversed, $this->encoding); + } + + /** + * A multibyte str_shuffle() function. It returns a string with its + * characters in random order. + * + * @return Stringy Object with a shuffled $str + */ + public function shuffle() + { + $indexes = range(0, $this->length() - 1); + shuffle($indexes); + + $shuffledStr = ''; + foreach ($indexes as $i) { + $shuffledStr .= mb_substr($this->str, $i, 1, $this->encoding); + } + + return static::create($shuffledStr, $this->encoding); + } + + /** + * Returns a string with whitespace removed from the start and end of the + * string. Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $chars Optional string of characters to strip + * @return Stringy Object with a trimmed $str + */ + public function trim($chars = null) + { + $chars = ($chars) ? preg_quote($chars) : '[:space:]'; + + return $this->regexReplace("^[$chars]+|[$chars]+\$", ''); + } + + /** + * Returns a string with whitespace removed from the start of the string. + * Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $chars Optional string of characters to strip + * @return Stringy Object with a trimmed $str + */ + public function trimLeft($chars = null) + { + $chars = ($chars) ? preg_quote($chars) : '[:space:]'; + + return $this->regexReplace("^[$chars]+", ''); + } + + /** + * Returns a string with whitespace removed from the end of the string. + * Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $chars Optional string of characters to strip + * @return Stringy Object with a trimmed $str + */ + public function trimRight($chars = null) + { + $chars = ($chars) ? preg_quote($chars) : '[:space:]'; + + return $this->regexReplace("[$chars]+\$", ''); + } + + /** + * Returns the longest common prefix between the string and $otherStr. + * + * @param string $otherStr Second string for comparison + * @return Stringy Object with its $str being the longest common prefix + */ + public function longestCommonPrefix($otherStr) + { + $encoding = $this->encoding; + $maxLength = min($this->length(), mb_strlen($otherStr, $encoding)); + + $longestCommonPrefix = ''; + for ($i = 0; $i < $maxLength; $i++) { + $char = mb_substr($this->str, $i, 1, $encoding); + + if ($char == mb_substr($otherStr, $i, 1, $encoding)) { + $longestCommonPrefix .= $char; + } else { + break; + } + } + + return static::create($longestCommonPrefix, $encoding); + } + + /** + * Returns the longest common suffix between the string and $otherStr. + * + * @param string $otherStr Second string for comparison + * @return Stringy Object with its $str being the longest common suffix + */ + public function longestCommonSuffix($otherStr) + { + $encoding = $this->encoding; + $maxLength = min($this->length(), mb_strlen($otherStr, $encoding)); + + $longestCommonSuffix = ''; + for ($i = 1; $i <= $maxLength; $i++) { + $char = mb_substr($this->str, -$i, 1, $encoding); + + if ($char == mb_substr($otherStr, -$i, 1, $encoding)) { + $longestCommonSuffix = $char . $longestCommonSuffix; + } else { + break; + } + } + + return static::create($longestCommonSuffix, $encoding); + } + + /** + * Returns the longest common substring between the string and $otherStr. + * In the case of ties, it returns that which occurs first. + * + * @param string $otherStr Second string for comparison + * @return Stringy Object with its $str being the longest common substring + */ + public function longestCommonSubstring($otherStr) + { + // Uses dynamic programming to solve + // http://en.wikipedia.org/wiki/Longest_common_substring_problem + $encoding = $this->encoding; + $stringy = static::create($this->str, $encoding); + $strLength = $stringy->length(); + $otherLength = mb_strlen($otherStr, $encoding); + + // Return if either string is empty + if ($strLength == 0 || $otherLength == 0) { + $stringy->str = ''; + return $stringy; + } + + $len = 0; + $end = 0; + $table = array_fill(0, $strLength + 1, + array_fill(0, $otherLength + 1, 0)); + + for ($i = 1; $i <= $strLength; $i++) { + for ($j = 1; $j <= $otherLength; $j++) { + $strChar = mb_substr($stringy->str, $i - 1, 1, $encoding); + $otherChar = mb_substr($otherStr, $j - 1, 1, $encoding); + + if ($strChar == $otherChar) { + $table[$i][$j] = $table[$i - 1][$j - 1] + 1; + if ($table[$i][$j] > $len) { + $len = $table[$i][$j]; + $end = $i; + } + } else { + $table[$i][$j] = 0; + } + } + } + + $stringy->str = mb_substr($stringy->str, $end - $len, $len, $encoding); + + return $stringy; + } + + /** + * Returns the length of the string. An alias for PHP's mb_strlen() function. + * + * @return int The number of characters in $str given the encoding + */ + public function length() + { + return mb_strlen($this->str, $this->encoding); + } + + /** + * Returns the substring beginning at $start with the specified $length. + * It differs from the mb_substr() function in that providing a $length of + * null will return the rest of the string, rather than an empty string. + * + * @param int $start Position of the first character to use + * @param int $length Maximum number of characters used + * @return Stringy Object with its $str being the substring + */ + public function substr($start, $length = null) + { + $length = $length === null ? $this->length() : $length; + $str = mb_substr($this->str, $start, $length, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Returns the character at $index, with indexes starting at 0. + * + * @param int $index Position of the character + * @return Stringy The character at $index + */ + public function at($index) + { + return $this->substr($index, 1); + } + + /** + * Returns the first $n characters of the string. + * + * @param int $n Number of characters to retrieve from the start + * @return Stringy Object with its $str being the first $n chars + */ + public function first($n) + { + $stringy = static::create($this->str, $this->encoding); + + if ($n < 0) { + $stringy->str = ''; + } else { + return $stringy->substr(0, $n); + } + + return $stringy; + } + + /** + * Returns the last $n characters of the string. + * + * @param int $n Number of characters to retrieve from the end + * @return Stringy Object with its $str being the last $n chars + */ + public function last($n) + { + $stringy = static::create($this->str, $this->encoding); + + if ($n <= 0) { + $stringy->str = ''; + } else { + return $stringy->substr(-$n); + } + + return $stringy; + } + + /** + * Ensures that the string begins with $substring. If it doesn't, it's + * prepended. + * + * @param string $substring The substring to add if not present + * @return Stringy Object with its $str prefixed by the $substring + */ + public function ensureLeft($substring) + { + $stringy = static::create($this->str, $this->encoding); + + if (!$stringy->startsWith($substring)) { + $stringy->str = $substring . $stringy->str; + } + + return $stringy; + } + + /** + * Ensures that the string begins with $substring. If it doesn't, it's + * appended. + * + * @param string $substring The substring to add if not present + * @return Stringy Object with its $str suffixed by the $substring + */ + public function ensureRight($substring) + { + $stringy = static::create($this->str, $this->encoding); + + if (!$stringy->endsWith($substring)) { + $stringy->str .= $substring; + } + + return $stringy; + } + + /** + * Returns a new string with the prefix $substring removed, if present. + * + * @param string $substring The prefix to remove + * @return Stringy Object having a $str without the prefix $substring + */ + public function removeLeft($substring) + { + $stringy = static::create($this->str, $this->encoding); + + if ($stringy->startsWith($substring)) { + $substringLength = mb_strlen($substring, $stringy->encoding); + return $stringy->substr($substringLength); + } + + return $stringy; + } + + /** + * Returns a new string with the suffix $substring removed, if present. + * + * @param string $substring The suffix to remove + * @return Stringy Object having a $str without the suffix $substring + */ + public function removeRight($substring) + { + $stringy = static::create($this->str, $this->encoding); + + if ($stringy->endsWith($substring)) { + $substringLength = mb_strlen($substring, $stringy->encoding); + return $stringy->substr(0, $stringy->length() - $substringLength); + } + + return $stringy; + } + + /** + * Returns true if $str matches the supplied pattern, false otherwise. + * + * @param string $pattern Regex pattern to match against + * @return bool Whether or not $str matches the pattern + */ + private function matchesPattern($pattern) + { + $regexEncoding = mb_regex_encoding(); + mb_regex_encoding($this->encoding); + + $match = mb_ereg_match($pattern, $this->str); + mb_regex_encoding($regexEncoding); + + return $match; + } + + /** + * Returns true if the string contains a lower case char, false + * otherwise. + * + * @return bool Whether or not the string contains a lower case character. + */ + public function hasLowerCase() + { + return $this->matchesPattern('.*[[:lower:]]'); + } + + /** + * Returns true if the string contains an upper case char, false + * otherwise. + * + * @return bool Whether or not the string contains an upper case character. + */ + public function hasUpperCase() + { + return $this->matchesPattern('.*[[:upper:]]'); + } + + /** + * Returns true if the string contains only alphabetic chars, false + * otherwise. + * + * @return bool Whether or not $str contains only alphabetic chars + */ + public function isAlpha() + { + return $this->matchesPattern('^[[:alpha:]]*$'); + } + + /** + * Returns true if the string contains only alphabetic and numeric chars, + * false otherwise. + * + * @return bool Whether or not $str contains only alphanumeric chars + */ + public function isAlphanumeric() + { + return $this->matchesPattern('^[[:alnum:]]*$'); + } + + /** + * Returns true if the string contains only hexadecimal chars, false + * otherwise. + * + * @return bool Whether or not $str contains only hexadecimal chars + */ + public function isHexadecimal() + { + return $this->matchesPattern('^[[:xdigit:]]*$'); + } + + /** + * Returns true if the string contains only whitespace chars, false + * otherwise. + * + * @return bool Whether or not $str contains only whitespace characters + */ + public function isBlank() + { + return $this->matchesPattern('^[[:space:]]*$'); + } + + /** + * Returns true if the string is JSON, false otherwise. + * + * @return bool Whether or not $str is JSON + */ + public function isJson() + { + json_decode($this->str); + + return (json_last_error() === JSON_ERROR_NONE); + } + + /** + * Returns true if the string contains only lower case chars, false + * otherwise. + * + * @return bool Whether or not $str contains only lower case characters + */ + public function isLowerCase() + { + return $this->matchesPattern('^[[:lower:]]*$'); + } + + /** + * Returns true if the string contains only lower case chars, false + * otherwise. + * + * @return bool Whether or not $str contains only lower case characters + */ + public function isUpperCase() + { + return $this->matchesPattern('^[[:upper:]]*$'); + } + + /** + * Returns true if the string is serialized, false otherwise. + * + * @return bool Whether or not $str is serialized + */ + public function isSerialized() + { + return $this->str === 'b:0;' || @unserialize($this->str) !== false; + } + + /** + * Returns the number of occurrences of $substring in the given string. + * By default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $substring The substring to search for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return int The number of $substring occurrences + */ + public function countSubstr($substring, $caseSensitive = true) + { + if ($caseSensitive) { + return mb_substr_count($this->str, $substring, $this->encoding); + } + + $str = mb_strtoupper($this->str, $this->encoding); + $substring = mb_strtoupper($substring, $this->encoding); + + return mb_substr_count($str, $substring, $this->encoding); + } + + /** + * Replaces all occurrences of $search in $str by $replacement. + * + * @param string $search The needle to search for + * @param string $replacement The string to replace with + * @return Stringy Object with the resulting $str after the replacements + */ + public function replace($search, $replacement) + { + return $this->regexReplace(preg_quote($search), $replacement); + } + + /** + * Replaces all occurrences of $pattern in $str by $replacement. An alias + * for mb_ereg_replace(). Note that the 'i' option with multibyte patterns + * in mb_ereg_replace() requires PHP 5.4+. This is due to a lack of support + * in the bundled version of Oniguruma in PHP 5.3. + * + * @param string $pattern The regular expression pattern + * @param string $replacement The string to replace with + * @param string $options Matching conditions to be used + * @return Stringy Object with the resulting $str after the replacements + */ + public function regexReplace($pattern, $replacement, $options = 'msr') + { + $regexEncoding = mb_regex_encoding(); + mb_regex_encoding($this->encoding); + + $str = mb_ereg_replace($pattern, $replacement, $this->str, $options); + mb_regex_encoding($regexEncoding); + + return static::create($str, $this->encoding); + } + + /** + * Convert all applicable characters to HTML entities. + * + * @param int|null $flags See http://php.net/manual/en/function.htmlentities.php + * @return Stringy Object with the resulting $str after being html encoded. + */ + public function htmlEncode($flags = ENT_COMPAT) + { + $str = htmlentities($this->str, $flags, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Convert all HTML entities to their applicable characters. + * + * @param int|null $flags See http://php.net/manual/en/function.html-entity-decode.php + * @return Stringy Object with the resulting $str after being html decoded. + */ + public function htmlDecode($flags = ENT_COMPAT) + { + $str = html_entity_decode($this->str, $flags, $this->encoding); + + return static::create($str, $this->encoding); + } +} diff --git a/core/vendor/danielstjules/stringy/tests/CommonTest.php b/core/vendor/danielstjules/stringy/tests/CommonTest.php new file mode 100644 index 0000000..285deb5 --- /dev/null +++ b/core/vendor/danielstjules/stringy/tests/CommonTest.php @@ -0,0 +1,1130 @@ +assertInstanceOf('Stringy\Stringy', $actual); + } + + public function indexOfProvider() + { + return array( + array(2, 'This is the string', 'is'), + array(2, 'This is the string', 'is', 0, 'UTF-8'), + array(false, 'This is the string', 'not-found', 0, 'UTF-8'), + array(32, 'This is the string... and there is another thing', 'is', 10, 'UTF-8'), + ); + } + + public function indexOfLastProvider() + { + return array( + array(5, 'This is the string', 'is'), + array(5, 'This is the string', 'is', 0, 'UTF-8'), + array(false, 'This is the string', 'not-found', 0, 'UTF-8'), + array(32, 'This is the string... and there is another thing', 'is', 0, 'UTF-8'), + ); + } + + public function charsProvider() + { + return array( + array(array(), ''), + array(array('T', 'e', 's', 't'), 'Test'), + array(array('F', 'ò', 'ô', ' ', 'B', 'à', 'ř'), 'Fòô Bàř', 'UTF-8') + ); + } + + public function upperCaseFirstProvider() + { + return array( + array('Test', 'Test'), + array('Test', 'test'), + array('1a', '1a'), + array('Σ test', 'σ test', 'UTF-8'), + array(' σ test', ' σ test', 'UTF-8') + ); + } + + public function lowerCaseFirstProvider() + { + return array( + array('test', 'Test'), + array('test', 'test'), + array('1a', '1a'), + array('σ test', 'Σ test', 'UTF-8'), + array(' Σ test', ' Σ test', 'UTF-8') + ); + } + + public function camelizeProvider() + { + return array( + array('camelCase', 'CamelCase'), + array('camelCase', 'Camel-Case'), + array('camelCase', 'camel case'), + array('camelCase', 'camel -case'), + array('camelCase', 'camel - case'), + array('camelCase', 'camel_case'), + array('camelCTest', 'camel c test'), + array('stringWith1Number', 'string_with1number'), + array('stringWith22Numbers', 'string-with-2-2 numbers'), + array('1Camel2Case', '1camel2case'), + array('camelΣase', 'camel σase', 'UTF-8'), + array('στανιλCase', 'Στανιλ case', 'UTF-8'), + array('σamelCase', 'σamel Case', 'UTF-8') + ); + } + + public function upperCamelizeProvider() + { + return array( + array('CamelCase', 'camelCase'), + array('CamelCase', 'Camel-Case'), + array('CamelCase', 'camel case'), + array('CamelCase', 'camel -case'), + array('CamelCase', 'camel - case'), + array('CamelCase', 'camel_case'), + array('CamelCTest', 'camel c test'), + array('StringWith1Number', 'string_with1number'), + array('StringWith22Numbers', 'string-with-2-2 numbers'), + array('1Camel2Case', '1camel2case'), + array('CamelΣase', 'camel σase', 'UTF-8'), + array('ΣτανιλCase', 'στανιλ case', 'UTF-8'), + array('ΣamelCase', 'Σamel Case', 'UTF-8') + ); + } + + public function dasherizeProvider() + { + return array( + array('test-case', 'testCase'), + array('test-case', 'Test-Case'), + array('test-case', 'test case'), + array('-test-case', '-test -case'), + array('test-case', 'test - case'), + array('test-case', 'test_case'), + array('test-c-test', 'test c test'), + array('test-d-case', 'TestDCase'), + array('test-c-c-test', 'TestCCTest'), + array('string-with1number', 'string_with1number'), + array('string-with-2-2-numbers', 'String-with_2_2 numbers'), + array('1test2case', '1test2case'), + array('dash-σase', 'dash Σase', 'UTF-8'), + array('στανιλ-case', 'Στανιλ case', 'UTF-8'), + array('σash-case', 'Σash Case', 'UTF-8') + ); + } + + public function underscoredProvider() + { + return array( + array('test_case', 'testCase'), + array('test_case', 'Test-Case'), + array('test_case', 'test case'), + array('test_case', 'test -case'), + array('_test_case', '-test - case'), + array('test_case', 'test_case'), + array('test_c_test', ' test c test'), + array('test_u_case', 'TestUCase'), + array('test_c_c_test', 'TestCCTest'), + array('string_with1number', 'string_with1number'), + array('string_with_2_2_numbers', 'String-with_2_2 numbers'), + array('1test2case', '1test2case'), + array('test_σase', 'test Σase', 'UTF-8'), + array('στανιλ_case', 'Στανιλ case', 'UTF-8'), + array('σash_case', 'Σash Case', 'UTF-8') + ); + } + + public function delimitProvider() + { + return array( + array('test*case', 'testCase', '*'), + array('test&case', 'Test-Case', '&'), + array('test#case', 'test case', '#'), + array('test**case', 'test -case', '**'), + array('~!~test~!~case', '-test - case', '~!~'), + array('test*case', 'test_case', '*'), + array('test%c%test', ' test c test', '%'), + array('test+u+case', 'TestUCase', '+'), + array('test=c=c=test', 'TestCCTest', '='), + array('string#>with1number', 'string_with1number', '#>'), + array('1test2case', '1test2case', '*'), + array('test ύα σase', 'test Σase', ' ύα ', 'UTF-8',), + array('στανιλαcase', 'Στανιλ case', 'α', 'UTF-8',), + array('σashΘcase', 'Σash Case', 'Θ', 'UTF-8') + ); + } + + public function swapCaseProvider() + { + return array( + array('TESTcASE', 'testCase'), + array('tEST-cASE', 'Test-Case'), + array(' - σASH cASE', ' - Σash Case', 'UTF-8'), + array('νΤΑΝΙΛ', 'Ντανιλ', 'UTF-8') + ); + } + + public function titleizeProvider() + { + $ignore = array('at', 'by', 'for', 'in', 'of', 'on', 'out', 'to', 'the'); + + return array( + array('Testing The Method', 'testing the method'), + array('Testing the Method', 'testing the method', $ignore, 'UTF-8'), + array('I Like to Watch DVDs at Home', 'i like to watch DVDs at home', + $ignore, 'UTF-8'), + array('Θα Ήθελα Να Φύγει', ' Θα ήθελα να φύγει ', null, 'UTF-8') + ); + } + + public function humanizeProvider() + { + return array( + array('Author', 'author_id'), + array('Test user', ' _test_user_'), + array('Συγγραφέας', ' συγγραφέας_id ', 'UTF-8') + ); + } + + public function tidyProvider() + { + return array( + array('"I see..."', '“I see…”'), + array("'This too'", "‘This too’"), + array('test-dash', 'test—dash'), + array('Ο συγγραφέας είπε...', 'Ο συγγραφέας είπε…') + ); + } + + public function collapseWhitespaceProvider() + { + return array( + array('foo bar', ' foo bar '), + array('test string', 'test string'), + array('Ο συγγραφέας', ' Ο συγγραφέας '), + array('123', ' 123 '), + array('', ' ', 'UTF-8'), // no-break space (U+00A0) + array('', '           ', 'UTF-8'), // spaces U+2000 to U+200A + array('', ' ', 'UTF-8'), // narrow no-break space (U+202F) + array('', ' ', 'UTF-8'), // medium mathematical space (U+205F) + array('', ' ', 'UTF-8'), // ideographic space (U+3000) + array('1 2 3', '  1  2  3  ', 'UTF-8'), + array('', ' '), + array('', ''), + ); + } + + public function toAsciiProvider() + { + return array( + array('foo bar', 'fòô bàř'), + array(' TEST ', ' ŤÉŚŢ '), + array('f = z = 3', 'φ = ź = 3'), + array('perevirka', 'перевірка'), + array('lysaya gora', 'лысая гора'), + array('shchuka', 'щука'), + array('', '漢字'), + array('xin chao the gioi', 'xin chào thế giới'), + array('XIN CHAO THE GIOI', 'XIN CHÀO THẾ GIỚI'), + array('dam phat chet luon', 'đấm phát chết luôn'), + array(' ', ' '), // no-break space (U+00A0) + array(' ', '           '), // spaces U+2000 to U+200A + array(' ', ' '), // narrow no-break space (U+202F) + array(' ', ' '), // medium mathematical space (U+205F) + array(' ', ' '), // ideographic space (U+3000) + array('', '𐍉'), // some uncommon, unsupported character (U+10349) + array('𐍉', '𐍉', false), + ); + } + + public function padProvider() + { + return array( + // length <= str + array('foo bar', 'foo bar', -1), + array('foo bar', 'foo bar', 7), + array('fòô bàř', 'fòô bàř', 7, ' ', 'right', 'UTF-8'), + + // right + array('foo bar ', 'foo bar', 9), + array('foo bar_*', 'foo bar', 9, '_*', 'right'), + array('fòô bàř¬ø¬', 'fòô bàř', 10, '¬ø', 'right', 'UTF-8'), + + // left + array(' foo bar', 'foo bar', 9, ' ', 'left'), + array('_*foo bar', 'foo bar', 9, '_*', 'left'), + array('¬ø¬fòô bàř', 'fòô bàř', 10, '¬ø', 'left', 'UTF-8'), + + // both + array('foo bar ', 'foo bar', 8, ' ', 'both'), + array('¬fòô bàř¬ø', 'fòô bàř', 10, '¬ø', 'both', 'UTF-8'), + array('¬øfòô bàř¬øÿ', 'fòô bàř', 12, '¬øÿ', 'both', 'UTF-8') + ); + } + + public function padLeftProvider() + { + return array( + array(' foo bar', 'foo bar', 9), + array('_*foo bar', 'foo bar', 9, '_*'), + array('_*_foo bar', 'foo bar', 10, '_*'), + array(' fòô bàř', 'fòô bàř', 9, ' ', 'UTF-8'), + array('¬øfòô bàř', 'fòô bàř', 9, '¬ø', 'UTF-8'), + array('¬ø¬fòô bàř', 'fòô bàř', 10, '¬ø', 'UTF-8'), + array('¬ø¬øfòô bàř', 'fòô bàř', 11, '¬ø', 'UTF-8'), + ); + } + + public function padRightProvider() + { + return array( + array('foo bar ', 'foo bar', 9), + array('foo bar_*', 'foo bar', 9, '_*'), + array('foo bar_*_', 'foo bar', 10, '_*'), + array('fòô bàř ', 'fòô bàř', 9, ' ', 'UTF-8'), + array('fòô bàř¬ø', 'fòô bàř', 9, '¬ø', 'UTF-8'), + array('fòô bàř¬ø¬', 'fòô bàř', 10, '¬ø', 'UTF-8'), + array('fòô bàř¬ø¬ø', 'fòô bàř', 11, '¬ø', 'UTF-8'), + ); + } + + public function padBothProvider() + { + return array( + array('foo bar ', 'foo bar', 8), + array(' foo bar ', 'foo bar', 9, ' '), + array('fòô bàř ', 'fòô bàř', 8, ' ', 'UTF-8'), + array(' fòô bàř ', 'fòô bàř', 9, ' ', 'UTF-8'), + array('fòô bàř¬', 'fòô bàř', 8, '¬ø', 'UTF-8'), + array('¬fòô bàř¬', 'fòô bàř', 9, '¬ø', 'UTF-8'), + array('¬fòô bàř¬ø', 'fòô bàř', 10, '¬ø', 'UTF-8'), + array('¬øfòô bàř¬ø', 'fòô bàř', 11, '¬ø', 'UTF-8'), + array('¬fòô bàř¬ø', 'fòô bàř', 10, '¬øÿ', 'UTF-8'), + array('¬øfòô bàř¬ø', 'fòô bàř', 11, '¬øÿ', 'UTF-8'), + array('¬øfòô bàř¬øÿ', 'fòô bàř', 12, '¬øÿ', 'UTF-8') + ); + } + + public function startsWithProvider() + { + return array( + array(true, 'foo bars', 'foo bar'), + array(true, 'FOO bars', 'foo bar', false), + array(true, 'FOO bars', 'foo BAR', false), + array(true, 'FÒÔ bàřs', 'fòô bàř', false, 'UTF-8'), + array(true, 'fòô bàřs', 'fòô BÀŘ', false, 'UTF-8'), + array(false, 'foo bar', 'bar'), + array(false, 'foo bar', 'foo bars'), + array(false, 'FOO bar', 'foo bars'), + array(false, 'FOO bars', 'foo BAR'), + array(false, 'FÒÔ bàřs', 'fòô bàř', true, 'UTF-8'), + array(false, 'fòô bàřs', 'fòô BÀŘ', true, 'UTF-8'), + ); + } + + public function endsWithProvider() + { + return array( + array(true, 'foo bars', 'o bars'), + array(true, 'FOO bars', 'o bars', false), + array(true, 'FOO bars', 'o BARs', false), + array(true, 'FÒÔ bàřs', 'ô bàřs', false, 'UTF-8'), + array(true, 'fòô bàřs', 'ô BÀŘs', false, 'UTF-8'), + array(false, 'foo bar', 'foo'), + array(false, 'foo bar', 'foo bars'), + array(false, 'FOO bar', 'foo bars'), + array(false, 'FOO bars', 'foo BARS'), + array(false, 'FÒÔ bàřs', 'fòô bàřs', true, 'UTF-8'), + array(false, 'fòô bàřs', 'fòô BÀŘS', true, 'UTF-8'), + ); + } + + public function toSpacesProvider() + { + return array( + array(' foo bar ', ' foo bar '), + array(' foo bar ', ' foo bar ', 5), + array(' foo bar ', ' foo bar ', 2), + array('foobar', ' foo bar ', 0), + array(" foo\n bar", " foo\n bar"), + array(" fòô\n bàř", " fòô\n bàř") + ); + } + + public function toTabsProvider() + { + return array( + array(' foo bar ', ' foo bar '), + array(' foo bar ', ' foo bar ', 5), + array(' foo bar ', ' foo bar ', 2), + array(" foo\n bar", " foo\n bar"), + array(" fòô\n bàř", " fòô\n bàř") + ); + } + + public function toLowerCaseProvider() + { + return array( + array('foo bar', 'FOO BAR'), + array(' foo_bar ', ' FOO_bar '), + array('fòô bàř', 'FÒÔ BÀŘ', 'UTF-8'), + array(' fòô_bàř ', ' FÒÔ_bàř ', 'UTF-8'), + array('αυτοκίνητο', 'ΑΥΤΟΚΊΝΗΤΟ', 'UTF-8'), + ); + } + + public function toTitleCaseProvider() + { + return array( + array('Foo Bar', 'foo bar'), + array(' Foo_Bar ', ' foo_bar '), + array('Fòô Bàř', 'fòô bàř', 'UTF-8'), + array(' Fòô_Bàř ', ' fòô_bàř ', 'UTF-8'), + array('Αυτοκίνητο Αυτοκίνητο', 'αυτοκίνητο αυτοκίνητο', 'UTF-8'), + ); + } + + public function toUpperCaseProvider() + { + return array( + array('FOO BAR', 'foo bar'), + array(' FOO_BAR ', ' FOO_bar '), + array('FÒÔ BÀŘ', 'fòô bàř', 'UTF-8'), + array(' FÒÔ_BÀŘ ', ' FÒÔ_bàř ', 'UTF-8'), + array('ΑΥΤΟΚΊΝΗΤΟ', 'αυτοκίνητο', 'UTF-8'), + ); + } + + public function slugifyProvider() + { + return array( + array('foo-bar', ' foo bar '), + array('foo-bar', 'foo -.-"-...bar'), + array('another-foo-bar', 'another..& foo -.-"-...bar'), + array('foo-dbar', " Foo d'Bar "), + array('a-string-with-dashes', 'A string-with-dashes'), + array('using-strings-like-foo-bar', 'Using strings like fòô bàř'), + array('numbers-1234', 'numbers 1234'), + array('perevirka-ryadka', 'перевірка рядка'), + array('bukvar-s-bukvoy-y', 'букварь с буквой ы'), + array('podekhal-k-podezdu-moego-doma', 'подъехал к подъезду моего дома'), + array('foo:bar:baz', 'Foo bar baz', ':'), + array('a_string_with_underscores', 'A_string with_underscores', '_'), + array('a_string_with_dashes', 'A string-with-dashes', '_'), + array('a\string\with\dashes', 'A string-with-dashes', '\\'), + array('an_odd_string', '-- An odd__ string-_', '_') + ); + } + + public function containsProvider() + { + return array( + array(true, 'Str contains foo bar', 'foo bar'), + array(true, '12398!@(*%!@# @!%#*&^%', ' @!%#*&^%'), + array(true, 'Ο συγγραφέας είπε', 'συγγραφέας', 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', 'å´¥©', true, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', 'å˚ ∆', true, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', 'øœ¬', true, 'UTF-8'), + array(false, 'Str contains foo bar', 'Foo bar'), + array(false, 'Str contains foo bar', 'foobar'), + array(false, 'Str contains foo bar', 'foo bar '), + array(false, 'Ο συγγραφέας είπε', ' συγγραφέας ', true, 'UTF-8'), + array(false, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', ' ßå˚', true, 'UTF-8'), + array(true, 'Str contains foo bar', 'Foo bar', false), + array(true, '12398!@(*%!@# @!%#*&^%', ' @!%#*&^%', false), + array(true, 'Ο συγγραφέας είπε', 'ΣΥΓΓΡΑΦΈΑΣ', false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', 'Å´¥©', false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', 'Å˚ ∆', false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', 'ØŒ¬', false, 'UTF-8'), + array(false, 'Str contains foo bar', 'foobar', false), + array(false, 'Str contains foo bar', 'foo bar ', false), + array(false, 'Ο συγγραφέας είπε', ' συγγραφέας ', false, 'UTF-8'), + array(false, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', ' ßÅ˚', false, 'UTF-8') + ); + } + + public function containsAnyProvider() + { + // One needle + $singleNeedle = array_map(function ($array) { + $array[2] = array($array[2]); + return $array; + }, $this->containsProvider()); + + $provider = array( + // No needles + array(false, 'Str contains foo bar', array()), + // Multiple needles + array(true, 'Str contains foo bar', array('foo', 'bar')), + array(true, '12398!@(*%!@# @!%#*&^%', array(' @!%#*', '&^%')), + array(true, 'Ο συγγραφέας είπε', array('συγγρ', 'αφέας'), 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('å´¥', '©'), true, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('å˚ ', '∆'), true, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('øœ', '¬'), true, 'UTF-8'), + array(false, 'Str contains foo bar', array('Foo', 'Bar')), + array(false, 'Str contains foo bar', array('foobar', 'bar ')), + array(false, 'Str contains foo bar', array('foo bar ', ' foo')), + array(false, 'Ο συγγραφέας είπε', array(' συγγραφέας ', ' συγγραφ '), true, 'UTF-8'), + array(false, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array(' ßå˚', ' ß '), true, 'UTF-8'), + array(true, 'Str contains foo bar', array('Foo bar', 'bar'), false), + array(true, '12398!@(*%!@# @!%#*&^%', array(' @!%#*&^%', '*&^%'), false), + array(true, 'Ο συγγραφέας είπε', array('ΣΥΓΓΡΑΦΈΑΣ', 'ΑΦΈΑ'), false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('Å´¥©', '¥©'), false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('Å˚ ∆', ' ∆'), false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('ØŒ¬', 'Œ'), false, 'UTF-8'), + array(false, 'Str contains foo bar', array('foobar', 'none'), false), + array(false, 'Str contains foo bar', array('foo bar ', ' ba '), false), + array(false, 'Ο συγγραφέας είπε', array(' συγγραφέας ', ' ραφέ '), false, 'UTF-8'), + array(false, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array(' ßÅ˚', ' Å˚ '), false, 'UTF-8'), + ); + + return array_merge($singleNeedle, $provider); + } + + public function containsAllProvider() + { + // One needle + $singleNeedle = array_map(function ($array) { + $array[2] = array($array[2]); + return $array; + }, $this->containsProvider()); + + $provider = array( + // One needle + array(false, 'Str contains foo bar', array()), + // Multiple needles + array(true, 'Str contains foo bar', array('foo', 'bar')), + array(true, '12398!@(*%!@# @!%#*&^%', array(' @!%#*', '&^%')), + array(true, 'Ο συγγραφέας είπε', array('συγγρ', 'αφέας'), 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('å´¥', '©'), true, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('å˚ ', '∆'), true, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('øœ', '¬'), true, 'UTF-8'), + array(false, 'Str contains foo bar', array('Foo', 'bar')), + array(false, 'Str contains foo bar', array('foobar', 'bar')), + array(false, 'Str contains foo bar', array('foo bar ', 'bar')), + array(false, 'Ο συγγραφέας είπε', array(' συγγραφέας ', ' συγγραφ '), true, 'UTF-8'), + array(false, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array(' ßå˚', ' ß '), true, 'UTF-8'), + array(true, 'Str contains foo bar', array('Foo bar', 'bar'), false), + array(true, '12398!@(*%!@# @!%#*&^%', array(' @!%#*&^%', '*&^%'), false), + array(true, 'Ο συγγραφέας είπε', array('ΣΥΓΓΡΑΦΈΑΣ', 'ΑΦΈΑ'), false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('Å´¥©', '¥©'), false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('Å˚ ∆', ' ∆'), false, 'UTF-8'), + array(true, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array('ØŒ¬', 'Œ'), false, 'UTF-8'), + array(false, 'Str contains foo bar', array('foobar', 'none'), false), + array(false, 'Str contains foo bar', array('foo bar ', ' ba'), false), + array(false, 'Ο συγγραφέας είπε', array(' συγγραφέας ', ' ραφέ '), false, 'UTF-8'), + array(false, 'å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', array(' ßÅ˚', ' Å˚ '), false, 'UTF-8'), + ); + + return array_merge($singleNeedle, $provider); + } + + public function surroundProvider() + { + return array( + array('__foobar__', 'foobar', '__'), + array('test', 'test', ''), + array('**', '', '*'), + array('¬fòô bàř¬', 'fòô bàř', '¬'), + array('ßå∆˚ test ßå∆˚', ' test ', 'ßå∆˚') + ); + } + + public function insertProvider() + { + return array( + array('foo bar', 'oo bar', 'f', 0), + array('foo bar', 'f bar', 'oo', 1), + array('f bar', 'f bar', 'oo', 20), + array('foo bar', 'foo ba', 'r', 6), + array('fòô bàř', 'òô bàř', 'f', 0, 'UTF-8'), + array('fòô bàř', 'f bàř', 'òô', 1, 'UTF-8'), + array('fòô bàř', 'fòô bà', 'ř', 6, 'UTF-8') + ); + } + + public function truncateProvider() + { + return array( + array('Test foo bar', 'Test foo bar', 12), + array('Test foo ba', 'Test foo bar', 11), + array('Test foo', 'Test foo bar', 8), + array('Test fo', 'Test foo bar', 7), + array('Test', 'Test foo bar', 4), + array('Test foo bar', 'Test foo bar', 12, '...'), + array('Test foo...', 'Test foo bar', 11, '...'), + array('Test ...', 'Test foo bar', 8, '...'), + array('Test...', 'Test foo bar', 7, '...'), + array('T...', 'Test foo bar', 4, '...'), + array('Test fo....', 'Test foo bar', 11, '....'), + array('Test fòô bàř', 'Test fòô bàř', 12, '', 'UTF-8'), + array('Test fòô bà', 'Test fòô bàř', 11, '', 'UTF-8'), + array('Test fòô', 'Test fòô bàř', 8, '', 'UTF-8'), + array('Test fò', 'Test fòô bàř', 7, '', 'UTF-8'), + array('Test', 'Test fòô bàř', 4, '', 'UTF-8'), + array('Test fòô bàř', 'Test fòô bàř', 12, 'ϰϰ', 'UTF-8'), + array('Test fòô ϰϰ', 'Test fòô bàř', 11, 'ϰϰ', 'UTF-8'), + array('Test fϰϰ', 'Test fòô bàř', 8, 'ϰϰ', 'UTF-8'), + array('Test ϰϰ', 'Test fòô bàř', 7, 'ϰϰ', 'UTF-8'), + array('Teϰϰ', 'Test fòô bàř', 4, 'ϰϰ', 'UTF-8'), + array('What are your pl...', 'What are your plans today?', 19, '...') + ); + } + + public function safeTruncateProvider() + { + return array( + array('Test foo bar', 'Test foo bar', 12), + array('Test foo', 'Test foo bar', 11), + array('Test foo', 'Test foo bar', 8), + array('Test', 'Test foo bar', 7), + array('Test', 'Test foo bar', 4), + array('Test foo bar', 'Test foo bar', 12, '...'), + array('Test foo...', 'Test foo bar', 11, '...'), + array('Test...', 'Test foo bar', 8, '...'), + array('Test...', 'Test foo bar', 7, '...'), + array('...', 'Test foo bar', 4, '...'), + array('Test....', 'Test foo bar', 11, '....'), + array('Test fòô bàř', 'Test fòô bàř', 12, '', 'UTF-8'), + array('Test fòô', 'Test fòô bàř', 11, '', 'UTF-8'), + array('Test fòô', 'Test fòô bàř', 8, '', 'UTF-8'), + array('Test', 'Test fòô bàř', 7, '', 'UTF-8'), + array('Test', 'Test fòô bàř', 4, '', 'UTF-8'), + array('Test fòô bàř', 'Test fòô bàř', 12, 'ϰϰ', 'UTF-8'), + array('Test fòôϰϰ', 'Test fòô bàř', 11, 'ϰϰ', 'UTF-8'), + array('Testϰϰ', 'Test fòô bàř', 8, 'ϰϰ', 'UTF-8'), + array('Testϰϰ', 'Test fòô bàř', 7, 'ϰϰ', 'UTF-8'), + array('ϰϰ', 'Test fòô bàř', 4, 'ϰϰ', 'UTF-8'), + array('What are your plans...', 'What are your plans today?', 22, '...') + ); + } + + public function reverseProvider() + { + return array( + array('', ''), + array('raboof', 'foobar'), + array('řàbôòf', 'fòôbàř', 'UTF-8'), + array('řàb ôòf', 'fòô bàř', 'UTF-8'), + array('∂∆ ˚åß', 'ßå˚ ∆∂', 'UTF-8') + ); + } + + public function shuffleProvider() + { + return array( + array('foo bar'), + array('∂∆ ˚åß', 'UTF-8'), + array('å´¥©¨ˆßå˚ ∆∂˙©å∑¥øœ¬', 'UTF-8') + ); + } + + public function trimProvider() + { + return array( + array('foo bar', ' foo bar '), + array('foo bar', ' foo bar'), + array('foo bar', 'foo bar '), + array('foo bar', "\n\t foo bar \n\t"), + array('fòô bàř', ' fòô bàř '), + array('fòô bàř', ' fòô bàř'), + array('fòô bàř', 'fòô bàř '), + array(' foo bar ', "\n\t foo bar \n\t", "\n\t"), + array('fòô bàř', "\n\t fòô bàř \n\t", null, 'UTF-8'), + array('fòô', ' fòô ', null, 'UTF-8'), // narrow no-break space (U+202F) + array('fòô', '  fòô  ', null, 'UTF-8'), // medium mathematical space (U+205F) + array('fòô', '           fòô', null, 'UTF-8') // spaces U+2000 to U+200A + ); + } + + public function trimLeftProvider() + { + return array( + array('foo bar ', ' foo bar '), + array('foo bar', ' foo bar'), + array('foo bar ', 'foo bar '), + array("foo bar \n\t", "\n\t foo bar \n\t"), + array('fòô bàř ', ' fòô bàř '), + array('fòô bàř', ' fòô bàř'), + array('fòô bàř ', 'fòô bàř '), + array('foo bar', '--foo bar', '-'), + array('fòô bàř', 'òòfòô bàř', 'ò', 'UTF-8'), + array("fòô bàř \n\t", "\n\t fòô bàř \n\t", null, 'UTF-8'), + array('fòô ', ' fòô ', null, 'UTF-8'), // narrow no-break space (U+202F) + array('fòô  ', '  fòô  ', null, 'UTF-8'), // medium mathematical space (U+205F) + array('fòô', '           fòô', null, 'UTF-8') // spaces U+2000 to U+200A + ); + } + + public function trimRightProvider() + { + return array( + array(' foo bar', ' foo bar '), + array('foo bar', 'foo bar '), + array(' foo bar', ' foo bar'), + array("\n\t foo bar", "\n\t foo bar \n\t"), + array(' fòô bàř', ' fòô bàř '), + array('fòô bàř', 'fòô bàř '), + array(' fòô bàř', ' fòô bàř'), + array('foo bar', 'foo bar--', '-'), + array('fòô bàř', 'fòô bàřòò', 'ò', 'UTF-8'), + array("\n\t fòô bàř", "\n\t fòô bàř \n\t", null, 'UTF-8'), + array(' fòô', ' fòô ', null, 'UTF-8'), // narrow no-break space (U+202F) + array('  fòô', '  fòô  ', null, 'UTF-8'), // medium mathematical space (U+205F) + array('fòô', 'fòô           ', null, 'UTF-8') // spaces U+2000 to U+200A + ); + } + + public function longestCommonPrefixProvider() + { + return array( + array('foo', 'foobar', 'foo bar'), + array('foo bar', 'foo bar', 'foo bar'), + array('f', 'foo bar', 'far boo'), + array('', 'toy car', 'foo bar'), + array('', 'foo bar', ''), + array('fòô', 'fòôbar', 'fòô bar', 'UTF-8'), + array('fòô bar', 'fòô bar', 'fòô bar', 'UTF-8'), + array('fò', 'fòô bar', 'fòr bar', 'UTF-8'), + array('', 'toy car', 'fòô bar', 'UTF-8'), + array('', 'fòô bar', '', 'UTF-8'), + ); + } + + public function longestCommonSuffixProvider() + { + return array( + array('bar', 'foobar', 'foo bar'), + array('foo bar', 'foo bar', 'foo bar'), + array('ar', 'foo bar', 'boo far'), + array('', 'foo bad', 'foo bar'), + array('', 'foo bar', ''), + array('bàř', 'fòôbàř', 'fòô bàř', 'UTF-8'), + array('fòô bàř', 'fòô bàř', 'fòô bàř', 'UTF-8'), + array(' bàř', 'fòô bàř', 'fòr bàř', 'UTF-8'), + array('', 'toy car', 'fòô bàř', 'UTF-8'), + array('', 'fòô bàř', '', 'UTF-8'), + ); + } + + public function longestCommonSubstringProvider() + { + return array( + array('foo', 'foobar', 'foo bar'), + array('foo bar', 'foo bar', 'foo bar'), + array('oo ', 'foo bar', 'boo far'), + array('foo ba', 'foo bad', 'foo bar'), + array('', 'foo bar', ''), + array('fòô', 'fòôbàř', 'fòô bàř', 'UTF-8'), + array('fòô bàř', 'fòô bàř', 'fòô bàř', 'UTF-8'), + array(' bàř', 'fòô bàř', 'fòr bàř', 'UTF-8'), + array(' ', 'toy car', 'fòô bàř', 'UTF-8'), + array('', 'fòô bàř', '', 'UTF-8'), + ); + } + + public function lengthProvider() + { + return array( + array(11, ' foo bar '), + array(1, 'f'), + array(0, ''), + array(7, 'fòô bàř', 'UTF-8') + ); + } + + public function substrProvider() + { + return array( + array('foo bar', 'foo bar', 0), + array('bar', 'foo bar', 4), + array('bar', 'foo bar', 4, null), + array('o b', 'foo bar', 2, 3), + array('', 'foo bar', 4, 0), + array('fòô bàř', 'fòô bàř', 0, null, 'UTF-8'), + array('bàř', 'fòô bàř', 4, null, 'UTF-8'), + array('ô b', 'fòô bàř', 2, 3, 'UTF-8'), + array('', 'fòô bàř', 4, 0, 'UTF-8') + ); + } + + public function atProvider() + { + return array( + array('f', 'foo bar', 0), + array('o', 'foo bar', 1), + array('r', 'foo bar', 6), + array('', 'foo bar', 7), + array('f', 'fòô bàř', 0, 'UTF-8'), + array('ò', 'fòô bàř', 1, 'UTF-8'), + array('ř', 'fòô bàř', 6, 'UTF-8'), + array('', 'fòô bàř', 7, 'UTF-8'), + ); + } + + public function firstProvider() + { + return array( + array('', 'foo bar', -5), + array('', 'foo bar', 0), + array('f', 'foo bar', 1), + array('foo', 'foo bar', 3), + array('foo bar', 'foo bar', 7), + array('foo bar', 'foo bar', 8), + array('', 'fòô bàř', -5, 'UTF-8'), + array('', 'fòô bàř', 0, 'UTF-8'), + array('f', 'fòô bàř', 1, 'UTF-8'), + array('fòô', 'fòô bàř', 3, 'UTF-8'), + array('fòô bàř', 'fòô bàř', 7, 'UTF-8'), + array('fòô bàř', 'fòô bàř', 8, 'UTF-8'), + ); + } + + public function lastProvider() + { + return array( + array('', 'foo bar', -5), + array('', 'foo bar', 0), + array('r', 'foo bar', 1), + array('bar', 'foo bar', 3), + array('foo bar', 'foo bar', 7), + array('foo bar', 'foo bar', 8), + array('', 'fòô bàř', -5, 'UTF-8'), + array('', 'fòô bàř', 0, 'UTF-8'), + array('ř', 'fòô bàř', 1, 'UTF-8'), + array('bàř', 'fòô bàř', 3, 'UTF-8'), + array('fòô bàř', 'fòô bàř', 7, 'UTF-8'), + array('fòô bàř', 'fòô bàř', 8, 'UTF-8'), + ); + } + + public function ensureLeftProvider() + { + return array( + array('foobar', 'foobar', 'f'), + array('foobar', 'foobar', 'foo'), + array('foo/foobar', 'foobar', 'foo/'), + array('http://foobar', 'foobar', 'http://'), + array('http://foobar', 'http://foobar', 'http://'), + array('fòôbàř', 'fòôbàř', 'f', 'UTF-8'), + array('fòôbàř', 'fòôbàř', 'fòô', 'UTF-8'), + array('fòô/fòôbàř', 'fòôbàř', 'fòô/', 'UTF-8'), + array('http://fòôbàř', 'fòôbàř', 'http://', 'UTF-8'), + array('http://fòôbàř', 'http://fòôbàř', 'http://', 'UTF-8'), + ); + } + + public function ensureRightProvider() + { + return array( + array('foobar', 'foobar', 'r'), + array('foobar', 'foobar', 'bar'), + array('foobar/bar', 'foobar', '/bar'), + array('foobar.com/', 'foobar', '.com/'), + array('foobar.com/', 'foobar.com/', '.com/'), + array('fòôbàř', 'fòôbàř', 'ř', 'UTF-8'), + array('fòôbàř', 'fòôbàř', 'bàř', 'UTF-8'), + array('fòôbàř/bàř', 'fòôbàř', '/bàř', 'UTF-8'), + array('fòôbàř.com/', 'fòôbàř', '.com/', 'UTF-8'), + array('fòôbàř.com/', 'fòôbàř.com/', '.com/', 'UTF-8'), + ); + } + + public function removeLeftProvider() + { + return array( + array('foo bar', 'foo bar', ''), + array('oo bar', 'foo bar', 'f'), + array('bar', 'foo bar', 'foo '), + array('foo bar', 'foo bar', 'oo'), + array('foo bar', 'foo bar', 'oo bar'), + array('oo bar', 'foo bar', Stringy::create('foo bar')->first(1), 'UTF-8'), + array('oo bar', 'foo bar', Stringy::create('foo bar')->at(0), 'UTF-8'), + array('fòô bàř', 'fòô bàř', '', 'UTF-8'), + array('òô bàř', 'fòô bàř', 'f', 'UTF-8'), + array('bàř', 'fòô bàř', 'fòô ', 'UTF-8'), + array('fòô bàř', 'fòô bàř', 'òô', 'UTF-8'), + array('fòô bàř', 'fòô bàř', 'òô bàř', 'UTF-8') + ); + } + + public function removeRightProvider() + { + return array( + array('foo bar', 'foo bar', ''), + array('foo ba', 'foo bar', 'r'), + array('foo', 'foo bar', ' bar'), + array('foo bar', 'foo bar', 'ba'), + array('foo bar', 'foo bar', 'foo ba'), + array('foo ba', 'foo bar', Stringy::create('foo bar')->last(1), 'UTF-8'), + array('foo ba', 'foo bar', Stringy::create('foo bar')->at(6), 'UTF-8'), + array('fòô bàř', 'fòô bàř', '', 'UTF-8'), + array('fòô bà', 'fòô bàř', 'ř', 'UTF-8'), + array('fòô', 'fòô bàř', ' bàř', 'UTF-8'), + array('fòô bàř', 'fòô bàř', 'bà', 'UTF-8'), + array('fòô bàř', 'fòô bàř', 'fòô bà', 'UTF-8') + ); + } + + public function isAlphaProvider() + { + return array( + array(true, ''), + array(true, 'foobar'), + array(false, 'foo bar'), + array(false, 'foobar2'), + array(true, 'fòôbàř', 'UTF-8'), + array(false, 'fòô bàř', 'UTF-8'), + array(false, 'fòôbàř2', 'UTF-8'), + array(true, 'ҠѨњфгШ', 'UTF-8'), + array(false, 'ҠѨњ¨ˆфгШ', 'UTF-8'), + array(true, '丹尼爾', 'UTF-8') + ); + } + + public function isAlphanumericProvider() + { + return array( + array(true, ''), + array(true, 'foobar1'), + array(false, 'foo bar'), + array(false, 'foobar2"'), + array(false, "\nfoobar\n"), + array(true, 'fòôbàř1', 'UTF-8'), + array(false, 'fòô bàř', 'UTF-8'), + array(false, 'fòôbàř2"', 'UTF-8'), + array(true, 'ҠѨњфгШ', 'UTF-8'), + array(false, 'ҠѨњ¨ˆфгШ', 'UTF-8'), + array(true, '丹尼爾111', 'UTF-8'), + array(true, 'دانيال1', 'UTF-8'), + array(false, 'دانيال1 ', 'UTF-8') + ); + } + + public function isBlankProvider() + { + return array( + array(true, ''), + array(true, ' '), + array(true, "\n\t "), + array(true, "\n\t \v\f"), + array(false, "\n\t a \v\f"), + array(false, "\n\t ' \v\f"), + array(false, "\n\t 2 \v\f"), + array(true, '', 'UTF-8'), + array(true, ' ', 'UTF-8'), // no-break space (U+00A0) + array(true, '           ', 'UTF-8'), // spaces U+2000 to U+200A + array(true, ' ', 'UTF-8'), // narrow no-break space (U+202F) + array(true, ' ', 'UTF-8'), // medium mathematical space (U+205F) + array(true, ' ', 'UTF-8'), // ideographic space (U+3000) + array(false, ' z', 'UTF-8'), + array(false, ' 1', 'UTF-8'), + ); + } + + public function isJsonProvider() + { + return array( + array(true, ''), + array(true, '123'), + array(true, '{"foo": "bar"}'), + array(false, '{"foo":"bar",}'), + array(false, '{"foo"}'), + array(true, '["foo"]'), + array(false, '{"foo": "bar"]'), + array(true, '123', 'UTF-8'), + array(true, '{"fòô": "bàř"}', 'UTF-8'), + array(false, '{"fòô":"bàř",}', 'UTF-8'), + array(false, '{"fòô"}', 'UTF-8'), + array(false, '["fòô": "bàř"]', 'UTF-8'), + array(true, '["fòô"]', 'UTF-8'), + array(false, '{"fòô": "bàř"]', 'UTF-8'), + ); + } + + public function isLowerCaseProvider() + { + return array( + array(true, ''), + array(true, 'foobar'), + array(false, 'foo bar'), + array(false, 'Foobar'), + array(true, 'fòôbàř', 'UTF-8'), + array(false, 'fòôbàř2', 'UTF-8'), + array(false, 'fòô bàř', 'UTF-8'), + array(false, 'fòôbÀŘ', 'UTF-8'), + ); + } + + public function hasLowerCaseProvider() + { + return array( + array(false, ''), + array(true, 'foobar'), + array(false, 'FOO BAR'), + array(true, 'fOO BAR'), + array(true, 'foO BAR'), + array(true, 'FOO BAr'), + array(true, 'Foobar'), + array(false, 'FÒÔBÀŘ', 'UTF-8'), + array(true, 'fòôbàř', 'UTF-8'), + array(true, 'fòôbàř2', 'UTF-8'), + array(true, 'Fòô bàř', 'UTF-8'), + array(true, 'fòôbÀŘ', 'UTF-8'), + ); + } + + public function isSerializedProvider() + { + return array( + array(false, ''), + array(true, 'a:1:{s:3:"foo";s:3:"bar";}'), + array(false, 'a:1:{s:3:"foo";s:3:"bar"}'), + array(true, serialize(array('foo' => 'bar'))), + array(true, 'a:1:{s:5:"fòô";s:5:"bàř";}', 'UTF-8'), + array(false, 'a:1:{s:5:"fòô";s:5:"bàř"}', 'UTF-8'), + array(true, serialize(array('fòô' => 'bár')), 'UTF-8'), + ); + } + + public function isUpperCaseProvider() + { + return array( + array(true, ''), + array(true, 'FOOBAR'), + array(false, 'FOO BAR'), + array(false, 'fOOBAR'), + array(true, 'FÒÔBÀŘ', 'UTF-8'), + array(false, 'FÒÔBÀŘ2', 'UTF-8'), + array(false, 'FÒÔ BÀŘ', 'UTF-8'), + array(false, 'FÒÔBàř', 'UTF-8'), + ); + } + + public function hasUpperCaseProvider() + { + return array( + array(false, ''), + array(true, 'FOOBAR'), + array(false, 'foo bar'), + array(true, 'Foo bar'), + array(true, 'FOo bar'), + array(true, 'foo baR'), + array(true, 'fOOBAR'), + array(false, 'fòôbàř', 'UTF-8'), + array(true, 'FÒÔBÀŘ', 'UTF-8'), + array(true, 'FÒÔBÀŘ2', 'UTF-8'), + array(true, 'fÒÔ BÀŘ', 'UTF-8'), + array(true, 'FÒÔBàř', 'UTF-8'), + ); + } + + public function isHexadecimalProvider() + { + return array( + array(true, ''), + array(true, 'abcdef'), + array(true, 'ABCDEF'), + array(true, '0123456789'), + array(true, '0123456789AbCdEf'), + array(false, '0123456789x'), + array(false, 'ABCDEFx'), + array(true, 'abcdef', 'UTF-8'), + array(true, 'ABCDEF', 'UTF-8'), + array(true, '0123456789', 'UTF-8'), + array(true, '0123456789AbCdEf', 'UTF-8'), + array(false, '0123456789x', 'UTF-8'), + array(false, 'ABCDEFx', 'UTF-8'), + ); + } + + public function countSubstrProvider() + { + return array( + array(0, '', 'foo'), + array(0, 'foo', 'bar'), + array(1, 'foo bar', 'foo'), + array(2, 'foo bar', 'o'), + array(0, '', 'fòô', 'UTF-8'), + array(0, 'fòô', 'bàř', 'UTF-8'), + array(1, 'fòô bàř', 'fòô', 'UTF-8'), + array(2, 'fôòô bàř', 'ô', 'UTF-8'), + array(0, 'fÔÒÔ bàř', 'ô', 'UTF-8'), + array(0, 'foo', 'BAR', false), + array(1, 'foo bar', 'FOo', false), + array(2, 'foo bar', 'O', false), + array(1, 'fòô bàř', 'fÒÔ', false, 'UTF-8'), + array(2, 'fôòô bàř', 'Ô', false, 'UTF-8'), + array(2, 'συγγραφέας', 'Σ', false, 'UTF-8') + ); + } + + public function replaceProvider() + { + return array( + array('', '', '', ''), + array('foo', '', '', 'foo'), + array('foo', '\s', '\s', 'foo'), + array('foo bar', 'foo bar', '', ''), + array('foo bar', 'foo bar', 'f(o)o', '\1'), + array('\1 bar', 'foo bar', 'foo', '\1'), + array('bar', 'foo bar', 'foo ', ''), + array('far bar', 'foo bar', 'foo', 'far'), + array('bar bar', 'foo bar foo bar', 'foo ', ''), + array('', '', '', '', 'UTF-8'), + array('fòô', '', '', 'fòô', 'UTF-8'), + array('fòô', '\s', '\s', 'fòô', 'UTF-8'), + array('fòô bàř', 'fòô bàř', '', '', 'UTF-8'), + array('bàř', 'fòô bàř', 'fòô ', '', 'UTF-8'), + array('far bàř', 'fòô bàř', 'fòô', 'far', 'UTF-8'), + array('bàř bàř', 'fòô bàř fòô bàř', 'fòô ', '', 'UTF-8'), + ); + } + + public function regexReplaceProvider() + { + return array( + array('', '', '', ''), + array('bar', 'foo', 'f[o]+', 'bar'), + array('o bar', 'foo bar', 'f(o)o', '\1'), + array('bar', 'foo bar', 'f[O]+\s', '', 'i'), + array('foo', 'bar', '[[:alpha:]]{3}', 'foo'), + array('', '', '', '', 'msr', 'UTF-8'), + array('bàř', 'fòô ', 'f[òô]+\s', 'bàř', 'msr', 'UTF-8'), + array('fòô', 'bàř', '[[:alpha:]]{3}', 'fòô', 'msr', 'UTF-8') + ); + } + + public function htmlEncodeProvider() + { + return array( + array('&', '&'), + array('"', '"'), + array(''', "'", ENT_QUOTES), + array('<', '<'), + array('>', '>'), + ); + } + + public function htmlDecodeProvider() + { + return array( + array('&', '&'), + array('"', '"'), + array("'", ''', ENT_QUOTES), + array('<', '<'), + array('>', '>'), + ); + } +} diff --git a/core/vendor/danielstjules/stringy/tests/CreateTest.php b/core/vendor/danielstjules/stringy/tests/CreateTest.php new file mode 100644 index 0000000..aef9c9f --- /dev/null +++ b/core/vendor/danielstjules/stringy/tests/CreateTest.php @@ -0,0 +1,16 @@ +assertInstanceOf('Stringy\Stringy', $stringy); + $this->assertEquals('foo bar', (string) $stringy); + $this->assertEquals('UTF-8', $stringy->getEncoding()); + } +} diff --git a/core/vendor/danielstjules/stringy/tests/StaticStringyTest.php b/core/vendor/danielstjules/stringy/tests/StaticStringyTest.php new file mode 100644 index 0000000..5d17bc6 --- /dev/null +++ b/core/vendor/danielstjules/stringy/tests/StaticStringyTest.php @@ -0,0 +1,710 @@ +assertEquals($expected, $result); + } + + /** + * @dataProvider indexOfLastProvider() + */ + public function testIndexOfLast($expected, $str, $subStr, $offset = 0, $encoding = null) + { + $result = S::indexOfLast($str, $subStr, $offset, $encoding); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider charsProvider() + */ + public function testChars($expected, $str, $encoding = null) + { + $result = S::chars($str, $encoding); + $this->assertInternalType('array', $result); + foreach ($result as $char) { + $this->assertInternalType('string', $char); + } + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider upperCaseFirstProvider() + */ + public function testUpperCaseFirst($expected, $str, $encoding = null) + { + $result = S::upperCaseFirst($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider lowerCaseFirstProvider() + */ + public function testLowerCaseFirst($expected, $str, $encoding = null) + { + $result = S::lowerCaseFirst($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider camelizeProvider() + */ + public function testCamelize($expected, $str, $encoding = null) + { + $result = S::camelize($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider upperCamelizeProvider() + */ + public function testUpperCamelize($expected, $str, $encoding = null) + { + $result = S::upperCamelize($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider dasherizeProvider() + */ + public function testDasherize($expected, $str, $encoding = null) + { + $result = S::dasherize($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider underscoredProvider() + */ + public function testUnderscored($expected, $str, $encoding = null) + { + $result = S::underscored($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider swapCaseProvider() + */ + public function testSwapCase($expected, $str, $encoding = null) + { + $result = S::swapCase($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider titleizeProvider() + */ + public function testTitleize($expected, $str, $ignore = null, + $encoding = null) + { + $result = S::titleize($str, $ignore, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider humanizeProvider() + */ + public function testHumanize($expected, $str, $encoding = null) + { + $result = S::humanize($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider tidyProvider() + */ + public function testTidy($expected, $str) + { + $result = S::tidy($str); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider collapseWhitespaceProvider() + */ + public function testCollapseWhitespace($expected, $str, $encoding = null) + { + $result = S::collapseWhitespace($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider toAsciiProvider() + */ + public function testToAscii($expected, $str, $removeUnsupported = true) + { + $result = S::toAscii($str, $removeUnsupported); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider padProvider() + */ + public function testPad($expected, $str, $length, $padStr = ' ', + $padType = 'right', $encoding = null) + { + $result = S::pad($str, $length, $padStr, $padType, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testPadException() + { + $result = S::pad('string', 5, 'foo', 'bar'); + } + + /** + * @dataProvider padLeftProvider() + */ + public function testPadLeft($expected, $str, $length, $padStr = ' ', + $encoding = null) + { + $result = S::padLeft($str, $length, $padStr, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider padRightProvider() + */ + public function testPadRight($expected, $str, $length, $padStr = ' ', + $encoding = null) + { + $result = S::padRight($str, $length, $padStr, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider padBothProvider() + */ + public function testPadBoth($expected, $str, $length, $padStr = ' ', + $encoding = null) + { + $result = S::padBoth($str, $length, $padStr, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider startsWithProvider() + */ + public function testStartsWith($expected, $str, $substring, + $caseSensitive = true, $encoding = null) + { + $result = S::startsWith($str, $substring, $caseSensitive, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider endsWithProvider() + */ + public function testEndsWith($expected, $str, $substring, + $caseSensitive = true, $encoding = null) + { + $result = S::endsWith($str, $substring, $caseSensitive, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider toSpacesProvider() + */ + public function testToSpaces($expected, $str, $tabLength = 4) + { + $result = S::toSpaces($str, $tabLength); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider toTabsProvider() + */ + public function testToTabs($expected, $str, $tabLength = 4) + { + $result = S::toTabs($str, $tabLength); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider toLowerCaseProvider() + */ + public function testToLowerCase($expected, $str, $encoding = null) + { + $result = S::toLowerCase($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider toTitleCaseProvider() + */ + public function testToTitleCase($expected, $str, $encoding = null) + { + $result = S::toTitleCase($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider toUpperCaseProvider() + */ + public function testToUpperCase($expected, $str, $encoding = null) + { + $result = S::toUpperCase($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider slugifyProvider() + */ + public function testSlugify($expected, $str, $replacement = '-') + { + $result = S::slugify($str, $replacement); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider containsProvider() + */ + public function testContains($expected, $haystack, $needle, + $caseSensitive = true, $encoding = null) + { + $result = S::contains($haystack, $needle, $caseSensitive, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider containsAnyProvider() + */ + public function testcontainsAny($expected, $haystack, $needles, + $caseSensitive = true, $encoding = null) + { + $result = S::containsAny($haystack, $needles, $caseSensitive, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider containsAllProvider() + */ + public function testContainsAll($expected, $haystack, $needles, + $caseSensitive = true, $encoding = null) + { + $result = S::containsAll($haystack, $needles, $caseSensitive, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider surroundProvider() + */ + public function testSurround($expected, $str, $substring) + { + $result = S::surround($str, $substring); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider insertProvider() + */ + public function testInsert($expected, $str, $substring, $index, + $encoding = null) + { + $result = S::insert($str, $substring, $index, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider truncateProvider() + */ + public function testTruncate($expected, $str, $length, $substring = '', + $encoding = null) + { + $result = S::truncate($str, $length, $substring, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider safeTruncateProvider() + */ + public function testSafeTruncate($expected, $str, $length, $substring = '', + $encoding = null) + { + $result = S::safeTruncate($str, $length, $substring, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider reverseProvider() + */ + public function testReverse($expected, $str, $encoding = null) + { + $result = S::reverse($str, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider shuffleProvider() + */ + public function testShuffle($str, $encoding = null) + { + $result = S::shuffle($str, $encoding); + $encoding = $encoding ?: mb_internal_encoding(); + + $this->assertInternalType('string', $result); + $this->assertEquals(mb_strlen($str, $encoding), + mb_strlen($result, $encoding)); + + // We'll make sure that the chars are present after shuffle + for ($i = 0; $i < mb_strlen($str, $encoding); $i++) { + $char = mb_substr($str, $i, 1, $encoding); + $countBefore = mb_substr_count($str, $char, $encoding); + $countAfter = mb_substr_count($result, $char, $encoding); + $this->assertEquals($countBefore, $countAfter); + } + } + + /** + * @dataProvider trimProvider() + */ + public function testTrim($expected, $str, $chars = null, $encoding = null) + { + $result = S::trim($str, $chars, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider trimLeftProvider() + */ + public function testTrimLeft($expected, $str, $chars = null, + $encoding = null) + { + $result = S::trimLeft($str, $chars, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider trimRightProvider() + */ + public function testTrimRight($expected, $str, $chars = null, + $encoding = null) + { + $result = S::trimRight($str, $chars, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider longestCommonPrefixProvider() + */ + public function testLongestCommonPrefix($expected, $str, $otherStr, + $encoding = null) + { + $result = S::longestCommonPrefix($str, $otherStr, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider longestCommonSuffixProvider() + */ + public function testLongestCommonSuffix($expected, $str, $otherStr, + $encoding = null) + { + $result = S::longestCommonSuffix($str, $otherStr, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider longestCommonSubstringProvider() + */ + public function testLongestCommonSubstring($expected, $str, $otherStr, + $encoding = null) + { + $result = S::longestCommonSubstring($str, $otherStr, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider lengthProvider() + */ + public function testLength($expected, $str, $encoding = null) + { + $result = S::length($str, $encoding); + $this->assertEquals($expected, $result); + $this->assertInternalType('int', $result); + } + + /** + * @dataProvider substrProvider() + */ + public function testSubstr($expected, $str, $start, $length = null, + $encoding = null) + { + $result = S::substr($str, $start, $length, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider atProvider() + */ + public function testAt($expected, $str, $index, $encoding = null) + { + $result = S::at($str, $index, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider firstProvider() + */ + public function testFirst($expected, $str, $n, $encoding = null) + { + $result = S::first($str, $n, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider lastProvider() + */ + public function testLast($expected, $str, $n, $encoding = null) + { + $result = S::last($str, $n, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider ensureLeftProvider() + */ + public function testEnsureLeft($expected, $str, $substring, $encoding = null) + { + $result = S::ensureLeft($str, $substring, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider ensureRightProvider() + */ + public function testEnsureRight($expected, $str, $substring, $encoding = null) + { + $result = S::ensureRight($str, $substring, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider removeLeftProvider() + */ + public function testRemoveLeft($expected, $str, $substring, $encoding = null) + { + $result = S::removeLeft($str, $substring, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider removeRightProvider() + */ + public function testRemoveRight($expected, $str, $substring, $encoding = null) + { + $result = S::removeRight($str, $substring, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider isAlphaProvider() + */ + public function testIsAlpha($expected, $str, $encoding = null) + { + $result = S::isAlpha($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider isAlphanumericProvider() + */ + public function testIsAlphanumeric($expected, $str, $encoding = null) + { + $result = S::isAlphanumeric($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider isBlankProvider() + */ + public function testIsBlank($expected, $str, $encoding = null) + { + $result = S::isBlank($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider isJsonProvider() + */ + public function testIsJson($expected, $str, $encoding = null) + { + $result = S::isJson($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider isLowerCaseProvider() + */ + public function testIsLowerCase($expected, $str, $encoding = null) + { + $result = S::isLowerCase($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider hasLowerCaseProvider() + */ + public function testHasLowerCase($expected, $str, $encoding = null) + { + $result = S::hasLowerCase($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider isSerializedProvider() + */ + public function testIsSerialized($expected, $str, $encoding = null) + { + $result = S::isSerialized($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider isUpperCaseProvider() + */ + public function testIsUpperCase($expected, $str, $encoding = null) + { + $result = S::isUpperCase($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider hasUpperCaseProvider() + */ + public function testHasUpperCase($expected, $str, $encoding = null) + { + $result = S::hasUpperCase($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider isHexadecimalProvider() + */ + public function testIsHexadecimal($expected, $str, $encoding = null) + { + $result = S::isHexadecimal($str, $encoding); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider countSubstrProvider() + */ + public function testCountSubstr($expected, $str, $substring, + $caseSensitive = true, $encoding = null) + { + $result = S::countSubstr($str, $substring, $caseSensitive, $encoding); + $this->assertInternalType('int', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider replaceProvider() + */ + public function testReplace($expected, $str, $search, $replacement, + $encoding = null) + { + $result = S::replace($str, $search, $replacement, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider regexReplaceProvider() + */ + public function testRegexReplace($expected, $str, $pattern, $replacement, + $options = 'msr', $encoding = null) + { + $result = S::regexReplace($str, $pattern, $replacement, $options, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider htmlEncodeProvider() + */ + public function testHtmlEncode($expected, $str, $flags = ENT_COMPAT, $encoding = null) + { + $result = S::htmlEncode($str, $flags, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider htmlDecodeProvider() + */ + public function testHtmlDecode($expected, $str, $flags = ENT_COMPAT, $encoding = null) + { + $result = S::htmlDecode($str, $flags, $encoding); + $this->assertInternalType('string', $result); + $this->assertEquals($expected, $result); + } +} diff --git a/core/vendor/danielstjules/stringy/tests/StringyTest.php b/core/vendor/danielstjules/stringy/tests/StringyTest.php new file mode 100644 index 0000000..b5edc43 --- /dev/null +++ b/core/vendor/danielstjules/stringy/tests/StringyTest.php @@ -0,0 +1,994 @@ +assertStringy($stringy); + $this->assertEquals('foo bar', (string) $stringy); + $this->assertEquals('UTF-8', $stringy->getEncoding()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructWithArray() + { + (string) new S(array()); + $this->fail('Expecting exception when the constructor is passed an array'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testMissingToString() + { + (string) new S(new stdClass()); + $this->fail('Expecting exception when the constructor is passed an ' . + 'object without a __toString method'); + } + + /** + * @dataProvider toStringProvider() + */ + public function testToString($expected, $str) + { + $this->assertEquals($expected, (string) new S($str)); + } + + public function toStringProvider() + { + return array( + array('', null), + array('', false), + array('1', true), + array('-9', -9), + array('1.18', 1.18), + array(' string ', ' string ') + ); + } + + public function testCreate() + { + $stringy = S::create('foo bar', 'UTF-8'); + $this->assertStringy($stringy); + $this->assertEquals('foo bar', (string) $stringy); + $this->assertEquals('UTF-8', $stringy->getEncoding()); + } + + public function testChaining() + { + $stringy = S::create("Fòô Bàř", 'UTF-8'); + $this->assertStringy($stringy); + $result = $stringy->collapseWhitespace()->swapCase()->upperCaseFirst(); + $this->assertEquals('FÒÔ bÀŘ', $result); + } + + public function testCount() + { + $stringy = S::create('Fòô', 'UTF-8'); + $this->assertEquals(3, $stringy->count()); + $this->assertEquals(3, count($stringy)); + } + + public function testGetIterator() + { + $stringy = S::create('Fòô Bàř', 'UTF-8'); + + $valResult = array(); + foreach ($stringy as $char) { + $valResult[] = $char; + } + + $keyValResult = array(); + foreach ($stringy as $pos => $char) { + $keyValResult[$pos] = $char; + } + + $this->assertEquals(array('F', 'ò', 'ô', ' ', 'B', 'à', 'ř'), $valResult); + $this->assertEquals(array('F', 'ò', 'ô', ' ', 'B', 'à', 'ř'), $keyValResult); + } + + /** + * @dataProvider offsetExistsProvider() + */ + public function testOffsetExists($expected, $offset) + { + $stringy = S::create('fòô', 'UTF-8'); + $this->assertEquals($expected, $stringy->offsetExists($offset)); + $this->assertEquals($expected, isset($stringy[$offset])); + } + + public function offsetExistsProvider() + { + return array( + array(true, 0), + array(true, 2), + array(false, 3), + array(true, -1), + array(true, -3), + array(false, -4) + ); + } + + public function testOffsetGet() + { + $stringy = S::create('fòô', 'UTF-8'); + + $this->assertEquals('f', $stringy->offsetGet(0)); + $this->assertEquals('ô', $stringy->offsetGet(2)); + + $this->assertEquals('ô', $stringy[2]); + } + + /** + * @expectedException \OutOfBoundsException + */ + public function testOffsetGetOutOfBounds() + { + $stringy = S::create('fòô', 'UTF-8'); + $test = $stringy[3]; + } + + /** + * @expectedException \Exception + */ + public function testOffsetSet() + { + $stringy = S::create('fòô', 'UTF-8'); + $stringy[1] = 'invalid'; + } + + /** + * @expectedException \Exception + */ + public function testOffsetUnset() + { + $stringy = S::create('fòô', 'UTF-8'); + unset($stringy[1]); + } + + /** + * @dataProvider indexOfProvider() + */ + public function testIndexOf($expected, $str, $subStr, $offset = 0, $encoding = null) + { + $result = S::create($str, $encoding)->indexOf($subStr, $offset); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider indexOfLastProvider() + */ + public function testIndexOfLast($expected, $str, $subStr, $offset = 0, $encoding = null) + { + $result = S::create($str, $encoding)->indexOfLast($subStr, $offset); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider charsProvider() + */ + public function testChars($expected, $str, $encoding = null) + { + $result = S::create($str, $encoding)->chars(); + $this->assertInternalType('array', $result); + foreach ($result as $char) { + $this->assertInternalType('string', $char); + } + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider upperCaseFirstProvider() + */ + public function testUpperCaseFirst($expected, $str, $encoding = null) + { + $result = S::create($str, $encoding)->upperCaseFirst(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider lowerCaseFirstProvider() + */ + public function testLowerCaseFirst($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->lowerCaseFirst(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider camelizeProvider() + */ + public function testCamelize($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->camelize(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider upperCamelizeProvider() + */ + public function testUpperCamelize($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->upperCamelize(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider dasherizeProvider() + */ + public function testDasherize($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->dasherize(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider underscoredProvider() + */ + public function testUnderscored($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->underscored(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider delimitProvider() + */ + public function testDelimit($expected, $str, $delimiter, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->delimit($delimiter); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider swapCaseProvider() + */ + public function testSwapCase($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->swapCase(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider titleizeProvider() + */ + public function testTitleize($expected, $str, $ignore = null, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->titleize($ignore); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider humanizeProvider() + */ + public function testHumanize($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->humanize(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider tidyProvider() + */ + public function testTidy($expected, $str) + { + $stringy = S::create($str); + $result = $stringy->tidy(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider collapseWhitespaceProvider() + */ + public function testCollapseWhitespace($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->collapseWhitespace(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider toAsciiProvider() + */ + public function testToAscii($expected, $str, $removeUnsupported = true) + { + $stringy = S::create($str); + $result = $stringy->toAscii($removeUnsupported); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider padProvider() + */ + public function testPad($expected, $str, $length, $padStr = ' ', + $padType = 'right', $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->pad($length, $padStr, $padType); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testPadException() + { + $stringy = S::create('foo'); + $result = $stringy->pad(5, 'foo', 'bar'); + } + + /** + * @dataProvider padLeftProvider() + */ + public function testPadLeft($expected, $str, $length, $padStr = ' ', + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->padLeft($length, $padStr); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider padRightProvider() + */ + public function testPadRight($expected, $str, $length, $padStr = ' ', + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->padRight($length, $padStr); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider padBothProvider() + */ + public function testPadBoth($expected, $str, $length, $padStr = ' ', + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->padBoth($length, $padStr); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider startsWithProvider() + */ + public function testStartsWith($expected, $str, $substring, + $caseSensitive = true, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->startsWith($substring, $caseSensitive); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider endsWithProvider() + */ + public function testEndsWith($expected, $str, $substring, + $caseSensitive = true, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->endsWith($substring, $caseSensitive); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider toSpacesProvider() + */ + public function testToSpaces($expected, $str, $tabLength = 4) + { + $stringy = S::create($str); + $result = $stringy->toSpaces($tabLength); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider toTabsProvider() + */ + public function testToTabs($expected, $str, $tabLength = 4) + { + $stringy = S::create($str); + $result = $stringy->toTabs($tabLength); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider toLowerCaseProvider() + */ + public function testToLowerCase($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->toLowerCase(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider toTitleCaseProvider() + */ + public function testToTitleCase($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->toTitleCase(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider toUpperCaseProvider() + */ + public function testToUpperCase($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->toUpperCase(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider slugifyProvider() + */ + public function testSlugify($expected, $str, $replacement = '-') + { + $stringy = S::create($str); + $result = $stringy->slugify($replacement); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider containsProvider() + */ + public function testContains($expected, $haystack, $needle, + $caseSensitive = true, $encoding = null) + { + $stringy = S::create($haystack, $encoding); + $result = $stringy->contains($needle, $caseSensitive); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($haystack, $stringy); + } + + /** + * @dataProvider containsAnyProvider() + */ + public function testcontainsAny($expected, $haystack, $needles, + $caseSensitive = true, $encoding = null) + { + $stringy = S::create($haystack, $encoding); + $result = $stringy->containsAny($needles, $caseSensitive); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($haystack, $stringy); + } + + /** + * @dataProvider containsAllProvider() + */ + public function testContainsAll($expected, $haystack, $needles, + $caseSensitive = true, $encoding = null) + { + $stringy = S::create($haystack, $encoding); + $result = $stringy->containsAll($needles, $caseSensitive); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($haystack, $stringy); + } + + /** + * @dataProvider surroundProvider() + */ + public function testSurround($expected, $str, $substring) + { + $stringy = S::create($str); + $result = $stringy->surround($substring); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider insertProvider() + */ + public function testInsert($expected, $str, $substring, $index, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->insert($substring, $index); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider truncateProvider() + */ + public function testTruncate($expected, $str, $length, $substring = '', + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->truncate($length, $substring); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider safeTruncateProvider() + */ + public function testSafeTruncate($expected, $str, $length, $substring = '', + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->safeTruncate($length, $substring); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider reverseProvider() + */ + public function testReverse($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->reverse(); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider shuffleProvider() + */ + public function testShuffle($str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $encoding = $encoding ?: mb_internal_encoding(); + $result = $stringy->shuffle(); + + $this->assertStringy($result); + $this->assertEquals($str, $stringy); + $this->assertEquals(mb_strlen($str, $encoding), + mb_strlen($result, $encoding)); + + // We'll make sure that the chars are present after shuffle + for ($i = 0; $i < mb_strlen($str, $encoding); $i++) { + $char = mb_substr($str, $i, 1, $encoding); + $countBefore = mb_substr_count($str, $char, $encoding); + $countAfter = mb_substr_count($result, $char, $encoding); + $this->assertEquals($countBefore, $countAfter); + } + } + + /** + * @dataProvider trimProvider() + */ + public function testTrim($expected, $str, $chars = null, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->trim($chars); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider trimLeftProvider() + */ + public function testTrimLeft($expected, $str, $chars = null, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->trimLeft($chars); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider trimRightProvider() + */ + public function testTrimRight($expected, $str, $chars = null, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->trimRight($chars); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider longestCommonPrefixProvider() + */ + public function testLongestCommonPrefix($expected, $str, $otherStr, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->longestCommonPrefix($otherStr); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider longestCommonSuffixProvider() + */ + public function testLongestCommonSuffix($expected, $str, $otherStr, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->longestCommonSuffix($otherStr); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider longestCommonSubstringProvider() + */ + public function testLongestCommonSubstring($expected, $str, $otherStr, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->longestCommonSubstring($otherStr); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider lengthProvider() + */ + public function testLength($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->length(); + $this->assertInternalType('int', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider substrProvider() + */ + public function testSubstr($expected, $str, $start, $length = null, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->substr($start, $length); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider atProvider() + */ + public function testAt($expected, $str, $index, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->at($index); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider firstProvider() + */ + public function testFirst($expected, $str, $n, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->first($n); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider lastProvider() + */ + public function testLast($expected, $str, $n, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->last($n); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider ensureLeftProvider() + */ + public function testEnsureLeft($expected, $str, $substring, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->ensureLeft($substring); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider ensureRightProvider() + */ + public function testEnsureRight($expected, $str, $substring, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->ensureRight($substring); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider removeLeftProvider() + */ + public function testRemoveLeft($expected, $str, $substring, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->removeLeft($substring); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider removeRightProvider() + */ + public function testRemoveRight($expected, $str, $substring, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->removeRight($substring); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider isAlphaProvider() + */ + public function testIsAlpha($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->isAlpha(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider isAlphanumericProvider() + */ + public function testIsAlphanumeric($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->isAlphanumeric(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider isBlankProvider() + */ + public function testIsBlank($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->isBlank(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider isJsonProvider() + */ + public function testIsJson($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->isJson(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider isLowerCaseProvider() + */ + public function testIsLowerCase($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->isLowerCase(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider hasLowerCaseProvider() + */ + public function testHasLowerCase($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->hasLowerCase(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider isSerializedProvider() + */ + public function testIsSerialized($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->isSerialized(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider isUpperCaseProvider() + */ + public function testIsUpperCase($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->isUpperCase(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider hasUpperCaseProvider() + */ + public function testHasUpperCase($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->hasUpperCase(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider isHexadecimalProvider() + */ + public function testIsHexadecimal($expected, $str, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->isHexadecimal(); + $this->assertInternalType('boolean', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider countSubstrProvider() + */ + public function testCountSubstr($expected, $str, $substring, + $caseSensitive = true, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->countSubstr($substring, $caseSensitive); + $this->assertInternalType('int', $result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider replaceProvider() + */ + public function testReplace($expected, $str, $search, $replacement, + $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->replace($search, $replacement); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider regexReplaceProvider() + */ + public function testregexReplace($expected, $str, $pattern, $replacement, + $options = 'msr', $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->regexReplace($pattern, $replacement, $options); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider htmlEncodeProvider() + */ + public function testHtmlEncode($expected, $str, $flags = ENT_COMPAT, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->htmlEncode($flags); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } + + /** + * @dataProvider htmlDecodeProvider() + */ + public function testHtmlDecode($expected, $str, $flags = ENT_COMPAT, $encoding = null) + { + $stringy = S::create($str, $encoding); + $result = $stringy->htmlDecode($flags); + $this->assertStringy($result); + $this->assertEquals($expected, $result); + $this->assertEquals($str, $stringy); + } +} diff --git a/core/vendor/dnoegel/php-xdg-base-dir/.gitignore b/core/vendor/dnoegel/php-xdg-base-dir/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/core/vendor/dnoegel/php-xdg-base-dir/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/core/vendor/dnoegel/php-xdg-base-dir/LICENSE b/core/vendor/dnoegel/php-xdg-base-dir/LICENSE new file mode 100644 index 0000000..029a00a --- /dev/null +++ b/core/vendor/dnoegel/php-xdg-base-dir/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Daniel Nögel + +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/core/vendor/dnoegel/php-xdg-base-dir/README.md b/core/vendor/dnoegel/php-xdg-base-dir/README.md new file mode 100644 index 0000000..9e51bbb --- /dev/null +++ b/core/vendor/dnoegel/php-xdg-base-dir/README.md @@ -0,0 +1,38 @@ +# XDG Base Directory + +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) + +Implementation of XDG Base Directory specification for php + +## Install + +Via Composer + +``` bash +$ composer require dnoegel/php-xdg-base-dir +``` + +## Usage + +``` php +$xdg = \XdgBaseDir\Xdg(); + +echo $xdg->getHomeDir(); +echo $xdg->getHomeConfigDir() +echo $xdg->getHomeDataDir() +echo $xdg->getHomeCacheDir() +echo $xdg->getRuntimeDir() + +$xdg->getDataDirs() // returns array +$xdg->getConfigDirs() // returns array +``` + +## Testing + +``` bash +$ phpunit +``` + +## License + +The MIT License (MIT). Please see [License File](https://github.com/dnoegel/php-xdg-base-dir/blob/master/LICENSE) for more information. diff --git a/core/vendor/dnoegel/php-xdg-base-dir/composer.json b/core/vendor/dnoegel/php-xdg-base-dir/composer.json new file mode 100644 index 0000000..f6caf31 --- /dev/null +++ b/core/vendor/dnoegel/php-xdg-base-dir/composer.json @@ -0,0 +1,17 @@ +{ + "name": "dnoegel/php-xdg-base-dir", + "description": "implementation of xdg base directory specification for php", + "type": "project", + "license": "MIT", + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + }, + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + } +} diff --git a/core/vendor/dnoegel/php-xdg-base-dir/phpunit.xml.dist b/core/vendor/dnoegel/php-xdg-base-dir/phpunit.xml.dist new file mode 100644 index 0000000..4000c01 --- /dev/null +++ b/core/vendor/dnoegel/php-xdg-base-dir/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/core/vendor/dnoegel/php-xdg-base-dir/src/Xdg.php b/core/vendor/dnoegel/php-xdg-base-dir/src/Xdg.php new file mode 100644 index 0000000..e2acda1 --- /dev/null +++ b/core/vendor/dnoegel/php-xdg-base-dir/src/Xdg.php @@ -0,0 +1,121 @@ +getHomeDir() . DIRECTORY_SEPARATOR . '.config'; + + return $path; + } + + /** + * @return string + */ + public function getHomeDataDir() + { + $path = getenv('XDG_DATA_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.local' . DIRECTORY_SEPARATOR . 'share'; + + return $path; + } + + /** + * @return array + */ + public function getConfigDirs() + { + $configDirs = getenv('XDG_CONFIG_DIRS') ? explode(':', getenv('XDG_CONFIG_DIRS')) : array('/etc/xdg'); + + $paths = array_merge(array($this->getHomeConfigDir()), $configDirs); + + return $paths; + } + + /** + * @return array + */ + public function getDataDirs() + { + $dataDirs = getenv('XDG_DATA_DIRS') ? explode(':', getenv('XDG_DATA_DIRS')) : array('/usr/local/share', '/usr/share'); + + $paths = array_merge(array($this->getHomeDataDir()), $dataDirs); + + return $paths; + } + + /** + * @return string + */ + public function getHomeCacheDir() + { + $path = getenv('XDG_CACHE_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.cache'; + + return $path; + + } + + public function getRuntimeDir($strict=true) + { + if ($runtimeDir = getenv('XDG_RUNTIME_DIR')) { + return $runtimeDir; + } + + if ($strict) { + throw new \RuntimeException('XDG_RUNTIME_DIR was not set'); + } + + $fallback = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::RUNTIME_DIR_FALLBACK . getenv('USER'); + + $create = false; + + if (!is_dir($fallback)) { + mkdir($fallback, 0700, true); + } + + $st = lstat($fallback); + + # The fallback must be a directory + if (!$st['mode'] & self::S_IFDIR) { + rmdir($fallback); + $create = true; + } elseif ($st['uid'] != getmyuid() || + $st['mode'] & (self::S_IRWXG | self::S_IRWXO) + ) { + rmdir($fallback); + $create = true; + } + + if ($create) { + mkdir($fallback, 0700, true); + } + + return $fallback; + } + +} diff --git a/core/vendor/dnoegel/php-xdg-base-dir/tests/XdgTest.php b/core/vendor/dnoegel/php-xdg-base-dir/tests/XdgTest.php new file mode 100644 index 0000000..92c2e07 --- /dev/null +++ b/core/vendor/dnoegel/php-xdg-base-dir/tests/XdgTest.php @@ -0,0 +1,116 @@ +assertEquals('/fake-dir', $this->getXdg()->getHomeDir()); + } + + public function testGetFallbackHomeDir() + { + putenv('HOME='); + putenv('HOMEDRIVE=C:'); + putenv('HOMEPATH=fake-dir'); + $this->assertEquals('C:/fake-dir', $this->getXdg()->getHomeDir()); + } + + public function testXdgPutCache() + { + putenv('XDG_DATA_HOME=tmp/'); + putenv('XDG_CONFIG_HOME=tmp/'); + putenv('XDG_CACHE_HOME=tmp/'); + $this->assertEquals('tmp/', $this->getXdg()->getHomeCacheDir()); + } + + public function testXdgPutData() + { + putenv('XDG_DATA_HOME=tmp/'); + $this->assertEquals('tmp/', $this->getXdg()->getHomeDataDir()); + } + + public function testXdgPutConfig() + { + putenv('XDG_CONFIG_HOME=tmp/'); + $this->assertEquals('tmp/', $this->getXdg()->getHomeConfigDir()); + } + + public function testXdgDataDirsShouldIncludeHomeDataDir() + { + putenv('XDG_DATA_HOME=tmp/'); + putenv('XDG_CONFIG_HOME=tmp/'); + + $this->assertArrayHasKey('tmp/', array_flip($this->getXdg()->getDataDirs())); + } + + public function testXdgConfigDirsShouldIncludeHomeConfigDir() + { + putenv('XDG_CONFIG_HOME=tmp/'); + + $this->assertArrayHasKey('tmp/', array_flip($this->getXdg()->getConfigDirs())); + } + + /** + * If XDG_RUNTIME_DIR is set, it should be returned + */ + public function testGetRuntimeDir() + { + putenv('XDG_RUNTIME_DIR=/tmp/'); + $runtimeDir = $this->getXdg()->getRuntimeDir(); + + $this->assertEquals(is_dir($runtimeDir), true); + } + + /** + * In strict mode, an exception should be shown if XDG_RUNTIME_DIR does not exist + * + * @expectedException \RuntimeException + */ + public function testGetRuntimeDirShouldThrowException() + { + putenv('XDG_RUNTIME_DIR='); + $this->getXdg()->getRuntimeDir(true); + } + + /** + * In fallback mode a directory should be created + */ + public function testGetRuntimeDirShouldCreateDirectory() + { + putenv('XDG_RUNTIME_DIR='); + $dir = $this->getXdg()->getRuntimeDir(false); + $permission = decoct(fileperms($dir) & 0777); + $this->assertEquals(700, $permission); + } + + /** + * Ensure, that the fallback directories are created with correct permission + */ + public function testGetRuntimeShouldDeleteDirsWithWrongPermission() + { + $runtimeDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . XdgBaseDir\Xdg::RUNTIME_DIR_FALLBACK . getenv('USER'); + + rmdir($runtimeDir); + mkdir($runtimeDir, 0764, true); + + // Permission should be wrong now + $permission = decoct(fileperms($runtimeDir) & 0777); + $this->assertEquals(764, $permission); + + putenv('XDG_RUNTIME_DIR='); + $dir = $this->getXdg()->getRuntimeDir(false); + + // Permission should be fixed + $permission = decoct(fileperms($dir) & 0777); + $this->assertEquals(700, $permission); + } +} diff --git a/core/vendor/doctrine/inflector/.gitignore b/core/vendor/doctrine/inflector/.gitignore new file mode 100644 index 0000000..f2cb7f8 --- /dev/null +++ b/core/vendor/doctrine/inflector/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +composer.phar +phpunit.xml diff --git a/core/vendor/doctrine/inflector/.travis.yml b/core/vendor/doctrine/inflector/.travis.yml new file mode 100644 index 0000000..9ec68f7 --- /dev/null +++ b/core/vendor/doctrine/inflector/.travis.yml @@ -0,0 +1,21 @@ +language: php + +sudo: false + +cache: + directory: + - $HOME/.composer/cache + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +install: + - composer install -n + +script: + - phpunit diff --git a/core/vendor/doctrine/inflector/LICENSE b/core/vendor/doctrine/inflector/LICENSE new file mode 100644 index 0000000..8c38cc1 --- /dev/null +++ b/core/vendor/doctrine/inflector/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +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/core/vendor/doctrine/inflector/README.md b/core/vendor/doctrine/inflector/README.md new file mode 100644 index 0000000..acb55a0 --- /dev/null +++ b/core/vendor/doctrine/inflector/README.md @@ -0,0 +1,6 @@ +# Doctrine Inflector + +Doctrine Inflector is a small library that can perform string manipulations +with regard to upper-/lowercase and singular/plural forms of words. + +[![Build Status](https://travis-ci.org/doctrine/inflector.svg?branch=master)](https://travis-ci.org/doctrine/inflector) diff --git a/core/vendor/doctrine/inflector/composer.json b/core/vendor/doctrine/inflector/composer.json new file mode 100644 index 0000000..7e5b2ef --- /dev/null +++ b/core/vendor/doctrine/inflector/composer.json @@ -0,0 +1,29 @@ +{ + "name": "doctrine/inflector", + "type": "library", + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "keywords": ["string", "inflection", "singularize", "pluralize"], + "homepage": "http://www.doctrine-project.org", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Inflector\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/core/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php b/core/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php new file mode 100644 index 0000000..a53828a --- /dev/null +++ b/core/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php @@ -0,0 +1,482 @@ +. + */ + +namespace Doctrine\Common\Inflector; + +/** + * Doctrine inflector has static methods for inflecting text. + * + * The methods in these classes are from several different sources collected + * across several different php projects and several different authors. The + * original author names and emails are not known. + * + * Pluralize & Singularize implementation are borrowed from CakePHP with some modifications. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Konsta Vesterinen + * @author Jonathan H. Wage + */ +class Inflector +{ + /** + * Plural inflector rules. + * + * @var array + */ + private static $plural = array( + 'rules' => array( + '/(s)tatus$/i' => '\1\2tatuses', + '/(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', + '/(f)oot$/i' => '\1eet', + '/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(analys|ax|cris|test|thes)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ), + 'uninflected' => array( + '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie' + ), + 'irregular' => array( + 'atlas' => 'atlases', + 'axe' => 'axes', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'chateau' => 'chateaux', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'criterion' => 'criteria', + 'curriculum' => 'curricula', + 'demo' => 'demos', + 'domino' => 'dominoes', + 'echo' => 'echoes', + 'foot' => 'feet', + 'fungus' => 'fungi', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hippopotamus' => 'hippopotami', + 'hoof' => 'hoofs', + 'human' => 'humans', + 'iris' => 'irises', + 'leaf' => 'leaves', + 'loaf' => 'loaves', + 'man' => 'men', + 'medium' => 'media', + 'memorandum' => 'memoranda', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'motto' => 'mottoes', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'nucleus' => 'nuclei', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'person' => 'people', + 'plateau' => 'plateaux', + 'runner-up' => 'runners-up', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'son-in-law' => 'sons-in-law', + 'syllabus' => 'syllabi', + 'testis' => 'testes', + 'thief' => 'thieves', + 'tooth' => 'teeth', + 'tornado' => 'tornadoes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'volcano' => 'volcanoes', + ) + ); + + /** + * Singular inflector rules. + * + * @var array + */ + private static $singular = array( + 'rules' => array( + '/(s)tatuses$/i' => '\1\2tatus', + '/^(.*)(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', + '/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(analys|ax|cris|test|thes)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', + '/(f)eet$/i' => '\1oot', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ), + 'uninflected' => array( + '.*[nrlm]ese', + '.*deer', + '.*fish', + '.*measles', + '.*ois', + '.*pox', + '.*sheep', + '.*ss', + ), + 'irregular' => array( + 'criteria' => 'criterion', + 'curves' => 'curve', + 'emphases' => 'emphasis', + 'foes' => 'foe', + 'hoaxes' => 'hoax', + 'media' => 'medium', + 'neuroses' => 'neurosis', + 'waves' => 'wave', + 'oases' => 'oasis', + ) + ); + + /** + * Words that should not be inflected. + * + * @var array + */ + private static $uninflected = array( + 'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus', + 'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps', + 'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder', + 'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti', + 'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings', + 'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media', + 'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese', + 'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese', + 'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors', + 'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'staff', 'swine', + 'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting', + 'wildebeest', 'Yengeese' + ); + + /** + * Method cache array. + * + * @var array + */ + private static $cache = array(); + + /** + * The initial state of Inflector so reset() works. + * + * @var array + */ + private static $initialState = array(); + + /** + * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. + * + * @param string $word The word to tableize. + * + * @return string The tableized word. + */ + public static function tableize($word) + { + return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word)); + } + + /** + * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. + * + * @param string $word The word to classify. + * + * @return string The classified word. + */ + public static function classify($word) + { + return str_replace(" ", "", ucwords(strtr($word, "_-", " "))); + } + + /** + * Camelizes a word. This uses the classify() method and turns the first character to lowercase. + * + * @param string $word The word to camelize. + * + * @return string The camelized word. + */ + public static function camelize($word) + { + return lcfirst(self::classify($word)); + } + + /** + * Uppercases words with configurable delimeters between words. + * + * Takes a string and capitalizes all of the words, like PHP's built-in + * ucwords function. This extends that behavior, however, by allowing the + * word delimeters to be configured, rather than only separating on + * whitespace. + * + * Here is an example: + * + * + * + * + * @param string $string The string to operate on. + * @param string $delimiters A list of word separators. + * + * @return string The string with all delimeter-separated words capitalized. + */ + public static function ucwords($string, $delimiters = " \n\t\r\0\x0B-") + { + return preg_replace_callback( + '/[^' . preg_quote($delimiters, '/') . ']+/', + function($matches) { + return ucfirst($matches[0]); + }, + $string + ); + } + + /** + * Clears Inflectors inflected value caches, and resets the inflection + * rules to the initial values. + * + * @return void + */ + public static function reset() + { + if (empty(self::$initialState)) { + self::$initialState = get_class_vars('Inflector'); + + return; + } + + foreach (self::$initialState as $key => $val) { + if ($key != 'initialState') { + self::${$key} = $val; + } + } + } + + /** + * Adds custom inflection $rules, of either 'plural' or 'singular' $type. + * + * ### Usage: + * + * {{{ + * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables')); + * Inflector::rules('plural', array( + * 'rules' => array('/^(inflect)ors$/i' => '\1ables'), + * 'uninflected' => array('dontinflectme'), + * 'irregular' => array('red' => 'redlings') + * )); + * }}} + * + * @param string $type The type of inflection, either 'plural' or 'singular' + * @param array $rules An array of rules to be added. + * @param boolean $reset If true, will unset default inflections for all + * new rules that are being defined in $rules. + * + * @return void + */ + public static function rules($type, $rules, $reset = false) + { + foreach ($rules as $rule => $pattern) { + if ( ! is_array($pattern)) { + continue; + } + + if ($reset) { + self::${$type}[$rule] = $pattern; + } else { + self::${$type}[$rule] = ($rule === 'uninflected') + ? array_merge($pattern, self::${$type}[$rule]) + : $pattern + self::${$type}[$rule]; + } + + unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]); + + if (isset(self::${$type}['merged'][$rule])) { + unset(self::${$type}['merged'][$rule]); + } + + if ($type === 'plural') { + self::$cache['pluralize'] = self::$cache['tableize'] = array(); + } elseif ($type === 'singular') { + self::$cache['singularize'] = array(); + } + } + + self::${$type}['rules'] = $rules + self::${$type}['rules']; + } + + /** + * Returns a word in plural form. + * + * @param string $word The word in singular form. + * + * @return string The word in plural form. + */ + public static function pluralize($word) + { + if (isset(self::$cache['pluralize'][$word])) { + return self::$cache['pluralize'][$word]; + } + + if (!isset(self::$plural['merged']['irregular'])) { + self::$plural['merged']['irregular'] = self::$plural['irregular']; + } + + if (!isset(self::$plural['merged']['uninflected'])) { + self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected); + } + + if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) { + self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')'; + self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')'; + } + + if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) { + self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1); + + return self::$cache['pluralize'][$word]; + } + + if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) { + self::$cache['pluralize'][$word] = $word; + + return $word; + } + + foreach (self::$plural['rules'] as $rule => $replacement) { + if (preg_match($rule, $word)) { + self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word); + + return self::$cache['pluralize'][$word]; + } + } + } + + /** + * Returns a word in singular form. + * + * @param string $word The word in plural form. + * + * @return string The word in singular form. + */ + public static function singularize($word) + { + if (isset(self::$cache['singularize'][$word])) { + return self::$cache['singularize'][$word]; + } + + if (!isset(self::$singular['merged']['uninflected'])) { + self::$singular['merged']['uninflected'] = array_merge( + self::$singular['uninflected'], + self::$uninflected + ); + } + + if (!isset(self::$singular['merged']['irregular'])) { + self::$singular['merged']['irregular'] = array_merge( + self::$singular['irregular'], + array_flip(self::$plural['irregular']) + ); + } + + if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) { + self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')'; + self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')'; + } + + if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) { + self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1); + + return self::$cache['singularize'][$word]; + } + + if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) { + self::$cache['singularize'][$word] = $word; + + return $word; + } + + foreach (self::$singular['rules'] as $rule => $replacement) { + if (preg_match($rule, $word)) { + self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word); + + return self::$cache['singularize'][$word]; + } + } + + self::$cache['singularize'][$word] = $word; + + return $word; + } +} diff --git a/core/vendor/doctrine/inflector/phpunit.xml.dist b/core/vendor/doctrine/inflector/phpunit.xml.dist new file mode 100644 index 0000000..ef07faa --- /dev/null +++ b/core/vendor/doctrine/inflector/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + + + + performance + + + diff --git a/core/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php b/core/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php new file mode 100644 index 0000000..4198d22 --- /dev/null +++ b/core/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php @@ -0,0 +1,309 @@ +assertEquals( + $singular, + Inflector::singularize($plural), + "'$plural' should be singularized to '$singular'" + ); + } + + /** + * testInflectingPlurals method + * + * @dataProvider dataSampleWords + * @return void + */ + public function testInflectingPlurals($singular, $plural) + { + $this->assertEquals( + $plural, + Inflector::pluralize($singular), + "'$singular' should be pluralized to '$plural'" + ); + } + + /** + * testCustomPluralRule method + * + * @return void + */ + public function testCustomPluralRule() + { + Inflector::reset(); + Inflector::rules('plural', array('/^(custom)$/i' => '\1izables')); + + $this->assertEquals(Inflector::pluralize('custom'), 'customizables'); + + Inflector::rules('plural', array('uninflected' => array('uninflectable'))); + + $this->assertEquals(Inflector::pluralize('uninflectable'), 'uninflectable'); + + Inflector::rules('plural', array( + 'rules' => array('/^(alert)$/i' => '\1ables'), + 'uninflected' => array('noflect', 'abtuse'), + 'irregular' => array('amaze' => 'amazable', 'phone' => 'phonezes') + )); + + $this->assertEquals(Inflector::pluralize('noflect'), 'noflect'); + $this->assertEquals(Inflector::pluralize('abtuse'), 'abtuse'); + $this->assertEquals(Inflector::pluralize('alert'), 'alertables'); + $this->assertEquals(Inflector::pluralize('amaze'), 'amazable'); + $this->assertEquals(Inflector::pluralize('phone'), 'phonezes'); + } + + /** + * testCustomSingularRule method + * + * @return void + */ + public function testCustomSingularRule() + { + Inflector::reset(); + Inflector::rules('singular', array('/(eple)r$/i' => '\1', '/(jente)r$/i' => '\1')); + + $this->assertEquals(Inflector::singularize('epler'), 'eple'); + $this->assertEquals(Inflector::singularize('jenter'), 'jente'); + + Inflector::rules('singular', array( + 'rules' => array('/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta'), + 'uninflected' => array('singulars'), + 'irregular' => array('spins' => 'spinor') + )); + + $this->assertEquals(Inflector::singularize('inflectors'), 'inflecta'); + $this->assertEquals(Inflector::singularize('contributors'), 'contributa'); + $this->assertEquals(Inflector::singularize('spins'), 'spinor'); + $this->assertEquals(Inflector::singularize('singulars'), 'singulars'); + } + + /** + * test that setting new rules clears the inflector caches. + * + * @return void + */ + public function testRulesClearsCaches() + { + Inflector::reset(); + + $this->assertEquals(Inflector::singularize('Bananas'), 'Banana'); + $this->assertEquals(Inflector::pluralize('Banana'), 'Bananas'); + + Inflector::rules('singular', array( + 'rules' => array('/(.*)nas$/i' => '\1zzz') + )); + + $this->assertEquals('Banazzz', Inflector::singularize('Bananas'), 'Was inflected with old rules.'); + + Inflector::rules('plural', array( + 'rules' => array('/(.*)na$/i' => '\1zzz'), + 'irregular' => array('corpus' => 'corpora') + )); + + $this->assertEquals(Inflector::pluralize('Banana'), 'Banazzz', 'Was inflected with old rules.'); + $this->assertEquals(Inflector::pluralize('corpus'), 'corpora', 'Was inflected with old irregular form.'); + } + + /** + * Test resetting inflection rules. + * + * @return void + */ + public function testCustomRuleWithReset() + { + Inflector::reset(); + + $uninflected = array('atlas', 'lapis', 'onibus', 'pires', 'virus', '.*x'); + $pluralIrregular = array('as' => 'ases'); + + Inflector::rules('singular', array( + 'rules' => array('/^(.*)(a|e|o|u)is$/i' => '\1\2l'), + 'uninflected' => $uninflected, + ), true); + + Inflector::rules('plural', array( + 'rules' => array( + '/^(.*)(a|e|o|u)l$/i' => '\1\2is', + ), + 'uninflected' => $uninflected, + 'irregular' => $pluralIrregular + ), true); + + $this->assertEquals(Inflector::pluralize('Alcool'), 'Alcoois'); + $this->assertEquals(Inflector::pluralize('Atlas'), 'Atlas'); + $this->assertEquals(Inflector::singularize('Alcoois'), 'Alcool'); + $this->assertEquals(Inflector::singularize('Atlas'), 'Atlas'); + } + + /** + * Test basic ucwords functionality. + * + * @return void + */ + public function testUcwords() + { + $this->assertSame('Top-O-The-Morning To All_of_you!', Inflector::ucwords( 'top-o-the-morning to all_of_you!')); + } + + /** + * Test ucwords functionality with custom delimeters. + * + * @return void + */ + public function testUcwordsWithCustomDelimeters() + { + $this->assertSame('Top-O-The-Morning To All_Of_You!', Inflector::ucwords( 'top-o-the-morning to all_of_you!', '-_ ')); + } +} + diff --git a/core/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php b/core/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php new file mode 100644 index 0000000..e8323d2 --- /dev/null +++ b/core/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php @@ -0,0 +1,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-color/composer.json b/core/vendor/jakub-onderka/php-console-color/composer.json new file mode 100644 index 0000000..6481cb5 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-color/composer.json @@ -0,0 +1,23 @@ +{ + "name": "jakub-onderka/php-console-color", + "license": "BSD-2-Clause", + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "autoload": { + "psr-4": {"JakubOnderka\\PhpConsoleColor\\": "src/"} + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.3", + "jakub-onderka/php-parallel-lint": "1.0", + "jakub-onderka/php-var-dump-check": "0.*", + "squizlabs/php_codesniffer": "1.*", + "jakub-onderka/php-code-style": "1.0" + } +} diff --git a/core/vendor/jakub-onderka/php-console-color/example.php b/core/vendor/jakub-onderka/php-console-color/example.php new file mode 100644 index 0000000..5e698a2 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-color/example.php @@ -0,0 +1,38 @@ +isSupported() ? 'Yes' : 'No') . "\n"; +echo "256 colors are supported: " . ($consoleColor->are256ColorsSupported() ? 'Yes' : 'No') . "\n\n"; + +if ($consoleColor->isSupported()) { + foreach ($consoleColor->getPossibleStyles() as $style) { + echo $consoleColor->apply($style, $style) . "\n"; + } +} + +echo "\n"; + +if ($consoleColor->are256ColorsSupported()) { + echo "Foreground colors:\n"; + for ($i = 1; $i <= 255; $i++) { + echo $consoleColor->apply("color_$i", str_pad($i, 6, ' ', STR_PAD_BOTH)); + + if ($i % 15 === 0) { + echo "\n"; + } + } + + echo "\nBackground colors:\n"; + + for ($i = 1; $i <= 255; $i++) { + echo $consoleColor->apply("bg_color_$i", str_pad($i, 6, ' ', STR_PAD_BOTH)); + + if ($i % 15 === 0) { + echo "\n"; + } + } + + echo "\n"; +} diff --git a/core/vendor/jakub-onderka/php-console-color/phpunit.xml b/core/vendor/jakub-onderka/php-console-color/phpunit.xml new file mode 100644 index 0000000..f1105cc --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-color/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + + vendor + + + diff --git a/core/vendor/jakub-onderka/php-console-color/src/ConsoleColor.php b/core/vendor/jakub-onderka/php-console-color/src/ConsoleColor.php new file mode 100644 index 0000000..90fd125 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-color/src/ConsoleColor.php @@ -0,0 +1,287 @@ + null, + 'bold' => '1', + 'dark' => '2', + 'italic' => '3', + 'underline' => '4', + 'blink' => '5', + 'reverse' => '7', + 'concealed' => '8', + + 'default' => '39', + 'black' => '30', + 'red' => '31', + 'green' => '32', + 'yellow' => '33', + 'blue' => '34', + 'magenta' => '35', + 'cyan' => '36', + 'light_gray' => '37', + + 'dark_gray' => '90', + 'light_red' => '91', + 'light_green' => '92', + 'light_yellow' => '93', + 'light_blue' => '94', + 'light_magenta' => '95', + 'light_cyan' => '96', + 'white' => '97', + + 'bg_default' => '49', + 'bg_black' => '40', + 'bg_red' => '41', + 'bg_green' => '42', + 'bg_yellow' => '43', + 'bg_blue' => '44', + 'bg_magenta' => '45', + 'bg_cyan' => '46', + 'bg_light_gray' => '47', + + 'bg_dark_gray' => '100', + 'bg_light_red' => '101', + 'bg_light_green' => '102', + 'bg_light_yellow' => '103', + 'bg_light_blue' => '104', + 'bg_light_magenta' => '105', + 'bg_light_cyan' => '106', + 'bg_white' => '107', + ); + + /** @var array */ + private $themes = array(); + + public function __construct() + { + $this->isSupported = $this->isSupported(); + } + + /** + * @param string|array $style + * @param string $text + * @return string + * @throws InvalidStyleException + * @throws \InvalidArgumentException + */ + public function apply($style, $text) + { + if (!$this->isStyleForced() && !$this->isSupported()) { + return $text; + } + + if (is_string($style)) { + $style = array($style); + } + if (!is_array($style)) { + throw new \InvalidArgumentException("Style must be string or array."); + } + + $sequences = array(); + + foreach ($style as $s) { + if (isset($this->themes[$s])) { + $sequences = array_merge($sequences, $this->themeSequence($s)); + } else if ($this->isValidStyle($s)) { + $sequences[] = $this->styleSequence($s); + } else { + throw new InvalidStyleException($s); + } + } + + $sequences = array_filter($sequences, function ($val) { + return $val !== null; + }); + + if (empty($sequences)) { + return $text; + } + + return $this->escSequence(implode(';', $sequences)) . $text . $this->escSequence(self::RESET_STYLE); + } + + /** + * @param bool $forceStyle + */ + public function setForceStyle($forceStyle) + { + $this->forceStyle = (bool) $forceStyle; + } + + /** + * @return bool + */ + public function isStyleForced() + { + return $this->forceStyle; + } + + /** + * @param array $themes + * @throws InvalidStyleException + * @throws \InvalidArgumentException + */ + public function setThemes(array $themes) + { + $this->themes = array(); + foreach ($themes as $name => $styles) { + $this->addTheme($name, $styles); + } + } + + /** + * @param string $name + * @param array|string $styles + * @throws \InvalidArgumentException + * @throws InvalidStyleException + */ + public function addTheme($name, $styles) + { + if (is_string($styles)) { + $styles = array($styles); + } + if (!is_array($styles)) { + throw new \InvalidArgumentException("Style must be string or array."); + } + + foreach ($styles as $style) { + if (!$this->isValidStyle($style)) { + throw new InvalidStyleException($style); + } + } + + $this->themes[$name] = $styles; + } + + /** + * @return array + */ + public function getThemes() + { + return $this->themes; + } + + /** + * @param string $name + * @return bool + */ + public function hasTheme($name) + { + return isset($this->themes[$name]); + } + + /** + * @param string $name + */ + public function removeTheme($name) + { + unset($this->themes[$name]); + } + + /** + * @return bool + */ + public function isSupported() + { + if (DIRECTORY_SEPARATOR === '\\') { + if (function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT)) { + return true; + } elseif (getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON') { + return true; + } + return false; + } else { + return function_exists('posix_isatty') && @posix_isatty(STDOUT); + } + } + + /** + * @return bool + */ + public function are256ColorsSupported() + { + if (DIRECTORY_SEPARATOR === '\\') { + return function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT); + } else { + return strpos(getenv('TERM'), '256color') !== false; + } + } + + /** + * @return array + */ + public function getPossibleStyles() + { + return array_keys($this->styles); + } + + /** + * @param string $name + * @return string[] + */ + private function themeSequence($name) + { + $sequences = array(); + foreach ($this->themes[$name] as $style) { + $sequences[] = $this->styleSequence($style); + } + return $sequences; + } + + /** + * @param string $style + * @return string + */ + private function styleSequence($style) + { + if (array_key_exists($style, $this->styles)) { + return $this->styles[$style]; + } + + if (!$this->are256ColorsSupported()) { + return null; + } + + preg_match(self::COLOR256_REGEXP, $style, $matches); + + $type = $matches[1] === 'bg_' ? self::BACKGROUND : self::FOREGROUND; + $value = $matches[2]; + + return "$type;5;$value"; + } + + /** + * @param string $style + * @return bool + */ + private function isValidStyle($style) + { + return array_key_exists($style, $this->styles) || preg_match(self::COLOR256_REGEXP, $style); + } + + /** + * @param string|int $value + * @return string + */ + private function escSequence($value) + { + return "\033[{$value}m"; + } +} diff --git a/core/vendor/jakub-onderka/php-console-color/src/InvalidStyleException.php b/core/vendor/jakub-onderka/php-console-color/src/InvalidStyleException.php new file mode 100644 index 0000000..6f1586f --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-color/src/InvalidStyleException.php @@ -0,0 +1,10 @@ +isSupportedForce = $isSupported; + } + + public function isSupported() + { + return $this->isSupportedForce; + } + + public function setAre256ColorsSupported($are256ColorsSupported) + { + $this->are256ColorsSupportedForce = $are256ColorsSupported; + } + + public function are256ColorsSupported() + { + return $this->are256ColorsSupportedForce; + } +} + +class ConsoleColorTest extends \PHPUnit_Framework_TestCase +{ + /** @var ConsoleColorWithForceSupport */ + private $uut; + + protected function setUp() + { + $this->uut = new ConsoleColorWithForceSupport(); + } + + public function testNone() + { + $output = $this->uut->apply('none', 'text'); + $this->assertEquals("text", $output); + } + + public function testBold() + { + $output = $this->uut->apply('bold', 'text'); + $this->assertEquals("\033[1mtext\033[0m", $output); + } + + public function testBoldColorsAreNotSupported() + { + $this->uut->setIsSupported(false); + + $output = $this->uut->apply('bold', 'text'); + $this->assertEquals("text", $output); + } + + public function testBoldColorsAreNotSupportedButAreForced() + { + $this->uut->setIsSupported(false); + $this->uut->setForceStyle(true); + + $output = $this->uut->apply('bold', 'text'); + $this->assertEquals("\033[1mtext\033[0m", $output); + } + + public function testDark() + { + $output = $this->uut->apply('dark', 'text'); + $this->assertEquals("\033[2mtext\033[0m", $output); + } + + public function testBoldAndDark() + { + $output = $this->uut->apply(array('bold', 'dark'), 'text'); + $this->assertEquals("\033[1;2mtext\033[0m", $output); + } + + public function test256ColorForeground() + { + $output = $this->uut->apply('color_255', 'text'); + $this->assertEquals("\033[38;5;255mtext\033[0m", $output); + } + + public function test256ColorWithoutSupport() + { + $this->uut->setAre256ColorsSupported(false); + + $output = $this->uut->apply('color_255', 'text'); + $this->assertEquals("text", $output); + } + + public function test256ColorBackground() + { + $output = $this->uut->apply('bg_color_255', 'text'); + $this->assertEquals("\033[48;5;255mtext\033[0m", $output); + } + + public function test256ColorForegroundAndBackground() + { + $output = $this->uut->apply(array('color_200', 'bg_color_255'), 'text'); + $this->assertEquals("\033[38;5;200;48;5;255mtext\033[0m", $output); + } + + public function testSetOwnTheme() + { + $this->uut->setThemes(array('bold_dark' => array('bold', 'dark'))); + $output = $this->uut->apply(array('bold_dark'), 'text'); + $this->assertEquals("\033[1;2mtext\033[0m", $output); + } + + public function testAddOwnTheme() + { + $this->uut->addTheme('bold_own', 'bold'); + $output = $this->uut->apply(array('bold_own'), 'text'); + $this->assertEquals("\033[1mtext\033[0m", $output); + } + + public function testAddOwnThemeArray() + { + $this->uut->addTheme('bold_dark', array('bold', 'dark')); + $output = $this->uut->apply(array('bold_dark'), 'text'); + $this->assertEquals("\033[1;2mtext\033[0m", $output); + } + + public function testOwnWithStyle() + { + $this->uut->addTheme('bold_dark', array('bold', 'dark')); + $output = $this->uut->apply(array('bold_dark', 'italic'), 'text'); + $this->assertEquals("\033[1;2;3mtext\033[0m", $output); + } + + public function testHasAndRemoveTheme() + { + $this->assertFalse($this->uut->hasTheme('bold_dark')); + + $this->uut->addTheme('bold_dark', array('bold', 'dark')); + $this->assertTrue($this->uut->hasTheme('bold_dark')); + + $this->uut->removeTheme('bold_dark'); + $this->assertFalse($this->uut->hasTheme('bold_dark')); + } + + public function testApplyInvalidArgument() + { + $this->setExpectedException('\InvalidArgumentException'); + $this->uut->apply(new stdClass(), 'text'); + } + + public function testApplyInvalidStyleName() + { + $this->setExpectedException('\JakubOnderka\PhpConsoleColor\InvalidStyleException'); + $this->uut->apply('invalid', 'text'); + } + + public function testApplyInvalid256Color() + { + $this->setExpectedException('\JakubOnderka\PhpConsoleColor\InvalidStyleException'); + $this->uut->apply('color_2134', 'text'); + } + + public function testThemeInvalidStyle() + { + $this->setExpectedException('\JakubOnderka\PhpConsoleColor\InvalidStyleException'); + $this->uut->addTheme('invalid', array('invalid')); + } + + public function testForceStyle() + { + $this->assertFalse($this->uut->isStyleForced()); + $this->uut->setForceStyle(true); + $this->assertTrue($this->uut->isStyleForced()); + } + + public function testGetPossibleStyles() + { + $this->assertInternalType('array', $this->uut->getPossibleStyles()); + $this->assertNotEmpty($this->uut->getPossibleStyles()); + } +} + diff --git a/core/vendor/jakub-onderka/php-console-highlighter/.gitignore b/core/vendor/jakub-onderka/php-console-highlighter/.gitignore new file mode 100644 index 0000000..70f6ec0 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/.gitignore @@ -0,0 +1,4 @@ +/.idea/ +/build/ +/vendor/ +/composer.lock \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/.travis.yml b/core/vendor/jakub-onderka/php-console-highlighter/.travis.yml new file mode 100644 index 0000000..2f7e8c8 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/.travis.yml @@ -0,0 +1,21 @@ +language: php + +php: + - 5.3.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + - hhvm-nightly + +matrix: + allowed_failures: + - php: 7.0 + - php: hhvm-nightly + +before_script: + - composer install --no-interaction --prefer-source + +script: + - ant \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/LICENSE b/core/vendor/jakub-onderka/php-console-highlighter/LICENSE new file mode 100644 index 0000000..1a8317f --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Jakub Onderka + +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/core/vendor/jakub-onderka/php-console-highlighter/README.md b/core/vendor/jakub-onderka/php-console-highlighter/README.md new file mode 100644 index 0000000..432ea0a --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/README.md @@ -0,0 +1,40 @@ +PHP Console Highlighter +======================= + +Highlight PHP code in console (terminal). + +Example +------- +![Example](http://jakubonderka.github.io/php-console-highlight-example.png) + +Install +------- + +Just create a `composer.json` file and run the `php composer.phar install` command to install it: + +```json +{ + "require": { + "jakub-onderka/php-console-highlighter": "0.*" + } +} +``` + +Usage +------- +```php +getWholeFile($fileContent); +``` + +------ + +[![Build Status](https://travis-ci.org/JakubOnderka/PHP-Console-Highlighter.svg?branch=master)](https://travis-ci.org/JakubOnderka/PHP-Console-Highlighter) diff --git a/core/vendor/jakub-onderka/php-console-highlighter/build.xml b/core/vendor/jakub-onderka/php-console-highlighter/build.xml new file mode 100644 index 0000000..d656ea9 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/build.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/composer.json b/core/vendor/jakub-onderka/php-console-highlighter/composer.json new file mode 100644 index 0000000..bd2f47a --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/composer.json @@ -0,0 +1,26 @@ +{ + "name": "jakub-onderka/php-console-highlighter", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "autoload": { + "psr-0": {"JakubOnderka\\PhpConsoleHighlighter": "src/"} + }, + "require": { + "php": ">=5.3.0", + "jakub-onderka/php-console-color": "~0.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "jakub-onderka/php-parallel-lint": "~0.5", + "jakub-onderka/php-var-dump-check": "~0.1", + "squizlabs/php_codesniffer": "~1.5", + "jakub-onderka/php-code-style": "~1.0" + } +} diff --git a/core/vendor/jakub-onderka/php-console-highlighter/examples/snippet.php b/core/vendor/jakub-onderka/php-console-highlighter/examples/snippet.php new file mode 100644 index 0000000..1bf6ac3 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/examples/snippet.php @@ -0,0 +1,10 @@ +getCodeSnippet($fileContent, 3); \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/examples/whole_file.php b/core/vendor/jakub-onderka/php-console-highlighter/examples/whole_file.php new file mode 100644 index 0000000..2a023d8 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/examples/whole_file.php @@ -0,0 +1,10 @@ +getWholeFile($fileContent); \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/examples/whole_file_line_numbers.php b/core/vendor/jakub-onderka/php-console-highlighter/examples/whole_file_line_numbers.php new file mode 100644 index 0000000..f9178f2 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/examples/whole_file_line_numbers.php @@ -0,0 +1,10 @@ +getWholeFileWithLineNumbers($fileContent); \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/phpunit.xml b/core/vendor/jakub-onderka/php-console-highlighter/phpunit.xml new file mode 100644 index 0000000..74011d9 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/phpunit.xml @@ -0,0 +1,15 @@ + + + + + tests/* + + + + + + + vendor + + + \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/src/JakubOnderka/PhpConsoleHighlighter/Highlighter.php b/core/vendor/jakub-onderka/php-console-highlighter/src/JakubOnderka/PhpConsoleHighlighter/Highlighter.php new file mode 100644 index 0000000..b908e93 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/src/JakubOnderka/PhpConsoleHighlighter/Highlighter.php @@ -0,0 +1,267 @@ + 'red', + self::TOKEN_COMMENT => 'yellow', + self::TOKEN_KEYWORD => 'green', + self::TOKEN_DEFAULT => 'default', + self::TOKEN_HTML => 'cyan', + + self::ACTUAL_LINE_MARK => 'red', + self::LINE_NUMBER => 'dark_gray', + ); + + /** + * @param ConsoleColor $color + */ + public function __construct(ConsoleColor $color) + { + $this->color = $color; + + foreach ($this->defaultTheme as $name => $styles) { + if (!$this->color->hasTheme($name)) { + $this->color->addTheme($name, $styles); + } + } + } + + /** + * @param string $source + * @param int $lineNumber + * @param int $linesBefore + * @param int $linesAfter + * @return string + * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException + * @throws \InvalidArgumentException + */ + public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2) + { + $tokenLines = $this->getHighlightedLines($source); + + $offset = $lineNumber - $linesBefore - 1; + $offset = max($offset, 0); + $length = $linesAfter + $linesBefore + 1; + $tokenLines = array_slice($tokenLines, $offset, $length, $preserveKeys = true); + + $lines = $this->colorLines($tokenLines); + + return $this->lineNumbers($lines, $lineNumber); + } + + /** + * @param string $source + * @return string + * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException + * @throws \InvalidArgumentException + */ + public function getWholeFile($source) + { + $tokenLines = $this->getHighlightedLines($source); + $lines = $this->colorLines($tokenLines); + return implode(PHP_EOL, $lines); + } + + /** + * @param string $source + * @return string + * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException + * @throws \InvalidArgumentException + */ + public function getWholeFileWithLineNumbers($source) + { + $tokenLines = $this->getHighlightedLines($source); + $lines = $this->colorLines($tokenLines); + return $this->lineNumbers($lines); + } + + /** + * @param string $source + * @return array + */ + private function getHighlightedLines($source) + { + $source = str_replace(array("\r\n", "\r"), "\n", $source); + $tokens = $this->tokenize($source); + return $this->splitToLines($tokens); + } + + /** + * @param string $source + * @return array + */ + private function tokenize($source) + { + $tokens = token_get_all($source); + + $output = array(); + $currentType = null; + $buffer = ''; + + foreach ($tokens as $token) { + if (is_array($token)) { + switch ($token[0]) { + case T_INLINE_HTML: + $newType = self::TOKEN_HTML; + break; + + case T_COMMENT: + case T_DOC_COMMENT: + $newType = self::TOKEN_COMMENT; + break; + + case T_ENCAPSED_AND_WHITESPACE: + case T_CONSTANT_ENCAPSED_STRING: + $newType = self::TOKEN_STRING; + break; + + case T_WHITESPACE: + break; + + case T_OPEN_TAG: + case T_OPEN_TAG_WITH_ECHO: + case T_CLOSE_TAG: + case T_STRING: + case T_VARIABLE: + + // Constants + case T_DIR: + case T_FILE: + case T_METHOD_C: + case T_DNUMBER: + case T_LNUMBER: + case T_NS_C: + case T_LINE: + case T_CLASS_C: + case T_FUNC_C: + //case T_TRAIT_C: + $newType = self::TOKEN_DEFAULT; + break; + + default: + // Compatibility with PHP 5.3 + if (defined('T_TRAIT_C') && $token[0] === T_TRAIT_C) { + $newType = self::TOKEN_DEFAULT; + } else { + $newType = self::TOKEN_KEYWORD; + } + } + } else { + $newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD; + } + + if ($currentType === null) { + $currentType = $newType; + } + + if ($currentType != $newType) { + $output[] = array($currentType, $buffer); + $buffer = ''; + $currentType = $newType; + } + + $buffer .= is_array($token) ? $token[1] : $token; + } + + if (isset($newType)) { + $output[] = array($newType, $buffer); + } + + return $output; + } + + /** + * @param array $tokens + * @return array + */ + private function splitToLines(array $tokens) + { + $lines = array(); + + $line = array(); + foreach ($tokens as $token) { + foreach (explode("\n", $token[1]) as $count => $tokenLine) { + if ($count > 0) { + $lines[] = $line; + $line = array(); + } + + if ($tokenLine === '') { + continue; + } + + $line[] = array($token[0], $tokenLine); + } + } + + $lines[] = $line; + + return $lines; + } + + /** + * @param array $tokenLines + * @return array + * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException + * @throws \InvalidArgumentException + */ + private function colorLines(array $tokenLines) + { + $lines = array(); + foreach ($tokenLines as $lineCount => $tokenLine) { + $line = ''; + foreach ($tokenLine as $token) { + list($tokenType, $tokenValue) = $token; + if ($this->color->hasTheme($tokenType)) { + $line .= $this->color->apply($tokenType, $tokenValue); + } else { + $line .= $tokenValue; + } + } + $lines[$lineCount] = $line; + } + + return $lines; + } + + /** + * @param array $lines + * @param null|int $markLine + * @return string + * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException + */ + private function lineNumbers(array $lines, $markLine = null) + { + end($lines); + $lineStrlen = strlen(key($lines) + 1); + + $snippet = ''; + foreach ($lines as $i => $line) { + if ($markLine !== null) { + $snippet .= ($markLine === $i + 1 ? $this->color->apply(self::ACTUAL_LINE_MARK, ' > ') : ' '); + } + + $snippet .= $this->color->apply(self::LINE_NUMBER, str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| '); + $snippet .= $line . PHP_EOL; + } + + return $snippet; + } +} \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/tests/JakubOnderka/PhpConsoleHighligter/HigligterTest.php b/core/vendor/jakub-onderka/php-console-highlighter/tests/JakubOnderka/PhpConsoleHighligter/HigligterTest.php new file mode 100644 index 0000000..269d03d --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/tests/JakubOnderka/PhpConsoleHighligter/HigligterTest.php @@ -0,0 +1,263 @@ +getMock('\JakubOnderka\PhpConsoleColor\ConsoleColor'); + + $mock->expects($this->any()) + ->method('apply') + ->will($this->returnCallback(function ($style, $text) { + return "<$style>$text"; + })); + + $mock->expects($this->any()) + ->method('hasTheme') + ->will($this->returnValue(true)); + + return $mock; + } + + protected function setUp() + { + $this->uut = new Highlighter($this->getConsoleColorMock()); + } + + protected function compare($original, $expected) + { + $output = $this->uut->getWholeFile($original); + $this->assertEquals($expected, $output); + } + + public function testVariable() + { + $this->compare( + << +echo \$a; +EOL + ); + } + + public function testInteger() + { + $this->compare( + << +echo 43; +EOL + ); + } + + public function testFloat() + { + $this->compare( + << +echo 43.3; +EOL + ); + } + + public function testHex() + { + $this->compare( + << +echo 0x43; +EOL + ); + } + + public function testBasicFunction() + { + $this->compare( + << +function plus(\$a, \$b) { + return \$a + \$b; +} +EOL + ); + } + + public function testStringNormal() + { + $this->compare( + << +echo 'Ahoj světe'; +EOL + ); + } + + public function testStringDouble() + { + $this->compare( + << +echo "Ahoj světe"; +EOL + ); + } + + public function testInstanceof() + { + $this->compare( + << +\$a instanceof stdClass; +EOL + ); + } + + /* + * Constants + */ + public function testConstant() + { + $constants = array( + '__FILE__', + '__LINE__', + '__CLASS__', + '__FUNCTION__', + '__METHOD__', + '__TRAIT__', + '__DIR__', + '__NAMESPACE__' + ); + + foreach ($constants as $constant) { + $this->compare( + << +$constant; +EOL + ); + } + } + + /* + * Comments + */ + public function testComment() + { + $this->compare( + << +/* Ahoj */ +EOL + ); + } + + public function testDocComment() + { + $this->compare( + << +/** Ahoj */ +EOL + ); + } + + public function testInlineComment() + { + $this->compare( + << +// Ahoj +EOL + ); + } + + public function testHashComment() + { + $this->compare( + << +# Ahoj +EOL + ); + } + + public function testEmpty() + { + $this->compare( + '' + , + '' + ); + } +} \ No newline at end of file diff --git a/core/vendor/jakub-onderka/php-console-highlighter/tests/bootstrap.php b/core/vendor/jakub-onderka/php-console-highlighter/tests/bootstrap.php new file mode 100644 index 0000000..7500417 --- /dev/null +++ b/core/vendor/jakub-onderka/php-console-highlighter/tests/bootstrap.php @@ -0,0 +1,2 @@ +=5.4", + "nikic/php-parser": "^1.2|^2.0|^3.0|^4.0", + "symfony/polyfill-php56": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0" + }, + "autoload": { + "psr-4": { + "SuperClosure\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "SuperClosure\\Test\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/Analyzer/AstAnalyzer.php b/core/vendor/jeremeamia/superclosure/src/Analyzer/AstAnalyzer.php new file mode 100644 index 0000000..c15f83c --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Analyzer/AstAnalyzer.php @@ -0,0 +1,148 @@ +locateClosure($data); + + // Make a second pass through the AST, but only through the closure's + // nodes, to resolve any magic constants to literal values. + $traverser = new NodeTraverser; + $traverser->addVisitor(new MagicConstantVisitor($data['location'])); + $traverser->addVisitor($thisDetector = new ThisDetectorVisitor); + $data['ast'] = $traverser->traverse([$data['ast']])[0]; + $data['hasThis'] = $thisDetector->detected; + + // Bounce the updated AST down to a string representation of the code. + $data['code'] = (new NodePrinter)->prettyPrint([$data['ast']]); + } + + /** + * Parses the closure's code and produces an abstract syntax tree (AST). + * + * @param array $data + * + * @throws ClosureAnalysisException if there is an issue finding the closure + */ + private function locateClosure(array &$data) + { + try { + $locator = new ClosureLocatorVisitor($data['reflection']); + $fileAst = $this->getFileAst($data['reflection']); + + $fileTraverser = new NodeTraverser; + $fileTraverser->addVisitor(new NameResolver); + $fileTraverser->addVisitor($locator); + $fileTraverser->traverse($fileAst); + } catch (ParserError $e) { + // @codeCoverageIgnoreStart + throw new ClosureAnalysisException( + 'There was an error analyzing the closure code.', 0, $e + ); + // @codeCoverageIgnoreEnd + } + + $data['ast'] = $locator->closureNode; + if (!$data['ast']) { + // @codeCoverageIgnoreStart + throw new ClosureAnalysisException( + 'The closure was not found within the abstract syntax tree.' + ); + // @codeCoverageIgnoreEnd + } + + $data['location'] = $locator->location; + } + + /** + * Returns the variables that in the "use" clause of the closure definition. + * These are referred to as the "used variables", "static variables", or + * "closed upon variables", "context" of the closure. + * + * @param array $data + */ + protected function determineContext(array &$data) + { + // Get the variable names defined in the AST + $refs = 0; + $vars = array_map(function ($node) use (&$refs) { + if ($node->byRef) { + $refs++; + } + if ($node->var instanceof VariableNode) { + // For PHP-Parser >=4.0 + return $node->var->name; + } else { + // For PHP-Parser <4.0 + return $node->var; + } + }, $data['ast']->uses); + $data['hasRefs'] = ($refs > 0); + + // Get the variable names and values using reflection + $values = $data['reflection']->getStaticVariables(); + + // Combine the names and values to create the canonical context. + foreach ($vars as $name) { + if (isset($values[$name])) { + $data['context'][$name] = $values[$name]; + } + } + } + + /** + * @param \ReflectionFunction $reflection + * + * @throws ClosureAnalysisException + * + * @return \PhpParser\Node[] + */ + private function getFileAst(\ReflectionFunction $reflection) + { + $fileName = $reflection->getFileName(); + if (!file_exists($fileName)) { + throw new ClosureAnalysisException( + "The file containing the closure, \"{$fileName}\" did not exist." + ); + } + + + return $this->getParser()->parse(file_get_contents($fileName)); + } + + /** + * @return CodeParser + */ + private function getParser() + { + if (class_exists('PhpParser\ParserFactory')) { + return (new ParserFactory)->create(ParserFactory::PREFER_PHP7); + } + + return new CodeParser(new EmulativeLexer); + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/Analyzer/ClosureAnalyzer.php b/core/vendor/jeremeamia/superclosure/src/Analyzer/ClosureAnalyzer.php new file mode 100644 index 0000000..6ba92a3 --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Analyzer/ClosureAnalyzer.php @@ -0,0 +1,68 @@ + new \ReflectionFunction($closure), + 'code' => null, + 'hasThis' => false, + 'context' => [], + 'hasRefs' => false, + 'binding' => null, + 'scope' => null, + 'isStatic' => $this->isClosureStatic($closure), + ]; + + $this->determineCode($data); + $this->determineContext($data); + $this->determineBinding($data); + + return $data; + } + + abstract protected function determineCode(array &$data); + + /** + * Returns the variables that are in the "use" clause of the closure. + * + * These variables are referred to as the "used variables", "static + * variables", "closed upon variables", or "context" of the closure. + * + * @param array $data + */ + abstract protected function determineContext(array &$data); + + private function determineBinding(array &$data) + { + $data['binding'] = $data['reflection']->getClosureThis(); + if ($scope = $data['reflection']->getClosureScopeClass()) { + $data['scope'] = $scope->getName(); + } + } + + private function isClosureStatic(\Closure $closure) + { + $closure = @$closure->bindTo(new \stdClass); + + if ($closure === null) { + return true; + } + + $rebound = new \ReflectionFunction($closure); + + return $rebound->getClosureThis() === null; + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/Analyzer/Token.php b/core/vendor/jeremeamia/superclosure/src/Analyzer/Token.php new file mode 100644 index 0000000..cab8efa --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Analyzer/Token.php @@ -0,0 +1,70 @@ +code = $code; + $this->value = $value; + $this->line = $line; + $this->name = $value ? token_name($value) : null; + } + + /** + * Determines if the token's value/code is equal to the specified value. + * + * @param mixed $value The value to check. + * + * @return bool True if the token is equal to the value. + */ + public function is($value) + { + return ($this->code === $value || $this->value === $value); + } + + public function __toString() + { + return $this->code; + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/Analyzer/TokenAnalyzer.php b/core/vendor/jeremeamia/superclosure/src/Analyzer/TokenAnalyzer.php new file mode 100644 index 0000000..2928ba0 --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Analyzer/TokenAnalyzer.php @@ -0,0 +1,118 @@ +determineTokens($data); + $data['code'] = implode('', $data['tokens']); + $data['hasThis'] = (strpos($data['code'], '$this') !== false); + } + + private function determineTokens(array &$data) + { + $potential = $this->determinePotentialTokens($data['reflection']); + $braceLevel = $index = $step = $insideUse = 0; + $data['tokens'] = $data['context'] = []; + + foreach ($potential as $token) { + $token = new Token($token); + switch ($step) { + // Handle tokens before the function declaration. + case 0: + if ($token->is(T_FUNCTION)) { + $data['tokens'][] = $token; + $step++; + } + break; + // Handle tokens inside the function signature. + case 1: + $data['tokens'][] = $token; + if ($insideUse) { + if ($token->is(T_VARIABLE)) { + $varName = trim($token, '$ '); + $data['context'][$varName] = null; + } elseif ($token->is('&')) { + $data['hasRefs'] = true; + } + } elseif ($token->is(T_USE)) { + $insideUse++; + } + if ($token->is('{')) { + $step++; + $braceLevel++; + } + break; + // Handle tokens inside the function body. + case 2: + $data['tokens'][] = $token; + if ($token->is('{')) { + $braceLevel++; + } elseif ($token->is('}')) { + $braceLevel--; + if ($braceLevel === 0) { + $step++; + } + } + break; + // Handle tokens after the function declaration. + case 3: + if ($token->is(T_FUNCTION)) { + throw new ClosureAnalysisException('Multiple closures ' + . 'were declared on the same line of code. Could not ' + . 'determine which closure was the intended target.' + ); + } + break; + } + } + } + + private function determinePotentialTokens(\ReflectionFunction $reflection) + { + // Load the file containing the code for the function. + $fileName = $reflection->getFileName(); + if (!is_readable($fileName)) { + throw new ClosureAnalysisException( + "Cannot read the file containing the closure: \"{$fileName}\"." + ); + } + + $code = ''; + $file = new \SplFileObject($fileName); + $file->seek($reflection->getStartLine() - 1); + while ($file->key() < $reflection->getEndLine()) { + $code .= $file->current(); + $file->next(); + } + + $code = trim($code); + if (strpos($code, 'getStaticVariables(); + + // Construct the context by combining the variable names and values. + foreach ($data['context'] as $name => &$value) { + if (isset($values[$name])) { + $value = $values[$name]; + } + } + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ClosureLocatorVisitor.php b/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ClosureLocatorVisitor.php new file mode 100644 index 0000000..69f92ed --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ClosureLocatorVisitor.php @@ -0,0 +1,120 @@ +reflection = $reflection; + $this->location = [ + 'class' => null, + 'directory' => dirname($this->reflection->getFileName()), + 'file' => $this->reflection->getFileName(), + 'function' => $this->reflection->getName(), + 'line' => $this->reflection->getStartLine(), + 'method' => null, + 'namespace' => null, + 'trait' => null, + ]; + } + + public function enterNode(AstNode $node) + { + // Determine information about the closure's location + if (!$this->closureNode) { + if ($node instanceof NamespaceNode) { + $namespace = $node->name !== null + ? $node->name->toString() + : null; + $this->location['namespace'] = $namespace; + } + if ($node instanceof TraitNode) { + $this->location['trait'] = (string) $node->name; + $this->location['class'] = null; + } elseif ($node instanceof ClassNode) { + $this->location['class'] = (string) $node->name; + $this->location['trait'] = null; + } + } + + // Locate the node of the closure + if ($node instanceof ClosureNode) { + if ($node->getAttribute('startLine') == $this->location['line']) { + if ($this->closureNode) { + $line = $this->location['file'] . ':' . $node->getAttribute('startLine'); + throw new ClosureAnalysisException("Two closures were " + . "declared on the same line ({$line}) of code. Cannot " + . "determine which closure was the intended target."); + } else { + $this->closureNode = $node; + } + } + } + } + + public function leaveNode(AstNode $node) + { + // Determine information about the closure's location + if (!$this->closureNode) { + if ($node instanceof NamespaceNode) { + $this->location['namespace'] = null; + } + if ($node instanceof TraitNode) { + $this->location['trait'] = null; + } elseif ($node instanceof ClassNode) { + $this->location['class'] = null; + } + } + } + + public function afterTraverse(array $nodes) + { + if ($this->location['class']) { + $this->location['class'] = $this->location['namespace'] . '\\' . $this->location['class']; + $this->location['method'] = "{$this->location['class']}::{$this->location['function']}"; + } elseif ($this->location['trait']) { + $this->location['trait'] = $this->location['namespace'] . '\\' . $this->location['trait']; + $this->location['method'] = "{$this->location['trait']}::{$this->location['function']}"; + + // If the closure was declared in a trait, then we will do a best + // effort guess on the name of the class that used the trait. It's + // actually impossible at this point to know for sure what it is. + if ($closureScope = $this->reflection->getClosureScopeClass()) { + $this->location['class'] = $closureScope ? $closureScope->getName() : null; + } elseif ($closureThis = $this->reflection->getClosureThis()) { + $this->location['class'] = get_class($closureThis); + } + } + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/MagicConstantVisitor.php b/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/MagicConstantVisitor.php new file mode 100644 index 0000000..219654f --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/MagicConstantVisitor.php @@ -0,0 +1,50 @@ +location = $location; + } + + public function leaveNode(AstNode $node) + { + switch ($node->getType()) { + case 'Scalar_MagicConst_Class' : + return new StringNode($this->location['class'] ?: ''); + case 'Scalar_MagicConst_Dir' : + return new StringNode($this->location['directory'] ?: ''); + case 'Scalar_MagicConst_File' : + return new StringNode($this->location['file'] ?: ''); + case 'Scalar_MagicConst_Function' : + return new StringNode($this->location['function'] ?: ''); + case 'Scalar_MagicConst_Line' : + return new NumberNode($node->getAttribute('startLine') ?: 0); + case 'Scalar_MagicConst_Method' : + return new StringNode($this->location['method'] ?: ''); + case 'Scalar_MagicConst_Namespace' : + return new StringNode($this->location['namespace'] ?: ''); + case 'Scalar_MagicConst_Trait' : + return new StringNode($this->location['trait'] ?: ''); + } + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ThisDetectorVisitor.php b/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ThisDetectorVisitor.php new file mode 100644 index 0000000..e2997ff --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ThisDetectorVisitor.php @@ -0,0 +1,27 @@ +name === 'this') { + $this->detected = true; + } + } + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/Exception/ClosureAnalysisException.php b/core/vendor/jeremeamia/superclosure/src/Exception/ClosureAnalysisException.php new file mode 100644 index 0000000..d1b0b5c --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Exception/ClosureAnalysisException.php @@ -0,0 +1,9 @@ +closure = $closure; + $this->serializer = $serializer ?: new Serializer; + } + + /** + * Return the original closure object. + * + * @return Closure + */ + public function getClosure() + { + return $this->closure; + } + + /** + * Delegates the closure invocation to the actual closure object. + * + * Important Notes: + * + * - `ReflectionFunction::invokeArgs()` should not be used here, because it + * does not work with closure bindings. + * - Args passed-by-reference lose their references when proxied through + * `__invoke()`. This is an unfortunate, but understandable, limitation + * of PHP that will probably never change. + * + * @return mixed + */ + public function __invoke() + { + return call_user_func_array($this->closure, func_get_args()); + } + + /** + * Clones the SerializableClosure with a new bound object and class scope. + * + * The method is essentially a wrapped proxy to the Closure::bindTo method. + * + * @param mixed $newthis The object to which the closure should be bound, + * or NULL for the closure to be unbound. + * @param mixed $newscope The class scope to which the closure is to be + * associated, or 'static' to keep the current one. + * If an object is given, the type of the object will + * be used instead. This determines the visibility of + * protected and private methods of the bound object. + * + * @return SerializableClosure + * @link http://www.php.net/manual/en/closure.bindto.php + */ + public function bindTo($newthis, $newscope = 'static') + { + return new self( + $this->closure->bindTo($newthis, $newscope), + $this->serializer + ); + } + + /** + * Serializes the code, context, and binding of the closure. + * + * @return string|null + * @link http://php.net/manual/en/serializable.serialize.php + */ + public function serialize() + { + try { + $this->data = $this->data ?: $this->serializer->getData($this->closure, true); + return serialize($this->data); + } catch (\Exception $e) { + trigger_error( + 'Serialization of closure failed: ' . $e->getMessage(), + E_USER_NOTICE + ); + // Note: The serialize() method of Serializable must return a string + // or null and cannot throw exceptions. + return null; + } + } + + /** + * Unserializes the closure. + * + * Unserializes the closure's data and recreates the closure using a + * simulation of its original context. The used variables (context) are + * extracted into a fresh scope prior to redefining the closure. The + * closure is also rebound to its former object and scope. + * + * @param string $serialized + * + * @throws ClosureUnserializationException + * @link http://php.net/manual/en/serializable.unserialize.php + */ + public function unserialize($serialized) + { + // Unserialize the closure data and reconstruct the closure object. + $this->data = unserialize($serialized); + $this->closure = __reconstruct_closure($this->data); + + // Throw an exception if the closure could not be reconstructed. + if (!$this->closure instanceof Closure) { + throw new ClosureUnserializationException( + 'The closure is corrupted and cannot be unserialized.' + ); + } + + // Rebind the closure to its former binding and scope. + if ($this->data['binding'] || $this->data['isStatic']) { + $this->closure = $this->closure->bindTo( + $this->data['binding'], + $this->data['scope'] + ); + } + } + + /** + * Returns closure data for `var_dump()`. + * + * @return array + */ + public function __debugInfo() + { + return $this->data ?: $this->serializer->getData($this->closure, true); + } +} + +/** + * Reconstruct a closure. + * + * HERE BE DRAGONS! + * + * The infamous `eval()` is used in this method, along with the error + * suppression operator, and variable variables (i.e., double dollar signs) to + * perform the unserialization logic. I'm sorry, world! + * + * This is also done inside a plain function instead of a method so that the + * binding and scope of the closure are null. + * + * @param array $__data Unserialized closure data. + * + * @return Closure|null + * @internal + */ +function __reconstruct_closure(array $__data) +{ + // Simulate the original context the closure was created in. + foreach ($__data['context'] as $__var_name => &$__value) { + if ($__value instanceof SerializableClosure) { + // Unbox any SerializableClosures in the context. + $__value = $__value->getClosure(); + } elseif ($__value === Serializer::RECURSION) { + // Track recursive references (there should only be one). + $__recursive_reference = $__var_name; + } + + // Import the variable into this scope. + ${$__var_name} = $__value; + } + + // Evaluate the code to recreate the closure. + try { + if (isset($__recursive_reference)) { + // Special handling for recursive closures. + @eval("\${$__recursive_reference} = {$__data['code']};"); + $__closure = ${$__recursive_reference}; + } else { + @eval("\$__closure = {$__data['code']};"); + } + } catch (\ParseError $e) { + // Discard the parse error. + } + + return isset($__closure) ? $__closure : null; +} diff --git a/core/vendor/jeremeamia/superclosure/src/Serializer.php b/core/vendor/jeremeamia/superclosure/src/Serializer.php new file mode 100644 index 0000000..732ee81 --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/Serializer.php @@ -0,0 +1,221 @@ + true, + 'context' => true, + 'binding' => true, + 'scope' => true, + 'isStatic' => true, + ]; + + /** + * The closure analyzer instance. + * + * @var ClosureAnalyzer + */ + private $analyzer; + + /** + * The HMAC key to sign serialized closures. + * + * @var string + */ + private $signingKey; + + /** + * Create a new serializer instance. + * + * @param ClosureAnalyzer|null $analyzer Closure analyzer instance. + * @param string|null $signingKey HMAC key to sign closure data. + */ + public function __construct( + ClosureAnalyzer $analyzer = null, + $signingKey = null + ) { + $this->analyzer = $analyzer ?: new DefaultAnalyzer; + $this->signingKey = $signingKey; + } + + /** + * @inheritDoc + */ + public function serialize(\Closure $closure) + { + $serialized = serialize(new SerializableClosure($closure, $this)); + + if ($serialized === null) { + throw new ClosureSerializationException( + 'The closure could not be serialized.' + ); + } + + if ($this->signingKey) { + $signature = $this->calculateSignature($serialized); + $serialized = '%' . base64_encode($signature) . $serialized; + } + + return $serialized; + } + + /** + * @inheritDoc + */ + public function unserialize($serialized) + { + // Strip off the signature from the front of the string. + $signature = null; + if ($serialized[0] === '%') { + $signature = base64_decode(substr($serialized, 1, 44)); + $serialized = substr($serialized, 45); + } + + // If a key was provided, then verify the signature. + if ($this->signingKey) { + $this->verifySignature($signature, $serialized); + } + + set_error_handler(function () {}); + $unserialized = unserialize($serialized); + restore_error_handler(); + if ($unserialized === false) { + throw new ClosureUnserializationException( + 'The closure could not be unserialized.' + ); + } elseif (!$unserialized instanceof SerializableClosure) { + throw new ClosureUnserializationException( + 'The closure did not unserialize to a SuperClosure.' + ); + } + + return $unserialized->getClosure(); + } + + /** + * @inheritDoc + */ + public function getData(\Closure $closure, $forSerialization = false) + { + // Use the closure analyzer to get data about the closure. + $data = $this->analyzer->analyze($closure); + + // If the closure data is getting retrieved solely for the purpose of + // serializing the closure, then make some modifications to the data. + if ($forSerialization) { + // If there is no reference to the binding, don't serialize it. + if (!$data['hasThis']) { + $data['binding'] = null; + } + + // Remove data about the closure that does not get serialized. + $data = array_intersect_key($data, self::$dataToKeep); + + // Wrap any other closures within the context. + foreach ($data['context'] as &$value) { + if ($value instanceof \Closure) { + $value = ($value === $closure) + ? self::RECURSION + : new SerializableClosure($value, $this); + } + } + } + + return $data; + } + + /** + * Recursively traverses and wraps all Closure objects within the value. + * + * NOTE: THIS MAY NOT WORK IN ALL USE CASES, SO USE AT YOUR OWN RISK. + * + * @param mixed $data Any variable that contains closures. + * @param SerializerInterface $serializer The serializer to use. + */ + public static function wrapClosures(&$data, SerializerInterface $serializer) + { + if ($data instanceof \Closure) { + // Handle and wrap closure objects. + $reflection = new \ReflectionFunction($data); + if ($binding = $reflection->getClosureThis()) { + self::wrapClosures($binding, $serializer); + $scope = $reflection->getClosureScopeClass(); + $scope = $scope ? $scope->getName() : 'static'; + $data = $data->bindTo($binding, $scope); + } + $data = new SerializableClosure($data, $serializer); + } elseif (is_array($data) || $data instanceof \stdClass || $data instanceof \Traversable) { + // Handle members of traversable values. + foreach ($data as &$value) { + self::wrapClosures($value, $serializer); + } + } elseif (is_object($data) && !$data instanceof \Serializable) { + // Handle objects that are not already explicitly serializable. + $reflection = new \ReflectionObject($data); + if (!$reflection->hasMethod('__sleep')) { + foreach ($reflection->getProperties() as $property) { + if ($property->isPrivate() || $property->isProtected()) { + $property->setAccessible(true); + } + $value = $property->getValue($data); + self::wrapClosures($value, $serializer); + $property->setValue($data, $value); + } + } + } + } + + /** + * Calculates a signature for a closure's serialized data. + * + * @param string $data Serialized closure data. + * + * @return string Signature of the closure's data. + */ + private function calculateSignature($data) + { + return hash_hmac('sha256', $data, $this->signingKey, true); + } + + /** + * Verifies the signature for a closure's serialized data. + * + * @param string $signature The provided signature of the data. + * @param string $data The data for which to verify the signature. + * + * @throws ClosureUnserializationException if the signature is invalid. + */ + private function verifySignature($signature, $data) + { + // Verify that the provided signature matches the calculated signature. + if (!hash_equals($signature, $this->calculateSignature($data))) { + throw new ClosureUnserializationException('The signature of the' + . ' closure\'s data is invalid, which means the serialized ' + . 'closure has been modified and is unsafe to unserialize.' + ); + } + } +} diff --git a/core/vendor/jeremeamia/superclosure/src/SerializerInterface.php b/core/vendor/jeremeamia/superclosure/src/SerializerInterface.php new file mode 100644 index 0000000..2c1a300 --- /dev/null +++ b/core/vendor/jeremeamia/superclosure/src/SerializerInterface.php @@ -0,0 +1,45 @@ +=5.3.0", + "composer-plugin-api": "^1.1.0" + }, + "require-dev": { + "composer/composer": "^2.0.x-dev", + "phpunit/phpunit": ">=4.8.35 <6.0", + "codeclimate/php-test-reporter": "dev-master" + }, + "autoload": { + "psr-0": { + "UpdateHelper\\": "src/" + } + }, + "scripts": { + "post-install-cmd": [ + "UpdateHelper\\UpdateHelper::check" + ], + "post-update-cmd": [ + "UpdateHelper\\UpdateHelper::check" + ], + "post-package-install": [ + "UpdateHelper\\UpdateHelper::check" + ], + "post-package-update": [ + "UpdateHelper\\UpdateHelper::check" + ] + }, + "extra": { + "class": "UpdateHelper\\ComposerPlugin" + } +} diff --git a/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/ComposerPlugin.php b/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/ComposerPlugin.php new file mode 100644 index 0000000..f0e2313 --- /dev/null +++ b/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/ComposerPlugin.php @@ -0,0 +1,37 @@ +io = $io; + } + + public static function getSubscribedEvents() + { + return array( + 'post-autoload-dump' => array( + array('onAutoloadDump', 0), + ), + ); + } + + public function onAutoloadDump(Event $event) + { + if (!class_exists('UpdateHelper\\UpdateHelper')) { + return; + } + + UpdateHelper::check($event); + } +} diff --git a/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/UpdateHelper.php b/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/UpdateHelper.php new file mode 100644 index 0000000..2ed2d9e --- /dev/null +++ b/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/UpdateHelper.php @@ -0,0 +1,349 @@ +read(); + } catch (\RuntimeException $e) { + $dependencyConfig = null; + } + + if (is_array($dependencyConfig) && isset($dependencyConfig['extra'], $dependencyConfig['extra'][$key])) { + $classes[$file] = $dependencyConfig['extra'][$key]; + } + } + + protected static function getUpdateHelperConfig(Composer $composer, $key = null) + { + $vendorDir = $composer->getConfig()->get('vendor-dir'); + + $npm = array(); + + foreach (scandir($vendorDir) as $namespace) { + if ($namespace === '.' || $namespace === '..' || !is_dir($directory = $vendorDir.DIRECTORY_SEPARATOR.$namespace)) { + continue; + } + + foreach (scandir($directory) as $dependency) { + if ($dependency === '.' || $dependency === '..' || !is_dir($subDirectory = $directory.DIRECTORY_SEPARATOR.$dependency)) { + continue; + } + + static::appendConfig($npm, $subDirectory, $key); + } + } + + static::appendConfig($npm, dirname($vendorDir), $key); + + return $npm; + } + + public static function check(Event $event) + { + if ($event instanceof ScriptEvent || $event instanceof PackageEvent) { + $io = $event->getIO(); + $composer = $event->getComposer(); + $autoload = __DIR__.'/../../../../autoload.php'; + + if (file_exists($autoload)) { + include_once $autoload; + } + + $classes = static::getUpdateHelperConfig($composer); + + foreach ($classes as $file => $class) { + $error = null; + + if (is_string($class) && class_exists($class)) { + try { + $helper = new $class(); + } catch (\Exception $e) { + $error = $e->getMessage()."\nFile: ".$e->getFile()."\nLine:".$e->getLine()."\n\n".$e->getTraceAsString(); + } catch (\Throwable $e) { + $error = $e->getMessage()."\nFile: ".$e->getFile()."\nLine:".$e->getLine()."\n\n".$e->getTraceAsString(); + } + + if (!$error && $helper instanceof UpdateHelperInterface) { + $helper->check(new static($event, $io, $composer)); + + continue; + } + } + + if (!$error) { + $error = JsonFile::encode($class).' is not an instance of UpdateHelperInterface.'; + } + + $io->writeError('UpdateHelper error in '.$file.":\n".$error); + } + } + } + + public function __construct(Event $event, IOInterface $io = null, Composer $composer = null) + { + $this->event = $event; + $this->io = $io ?: (method_exists($event, 'getIO') ? $event->getIO() : null); + $this->composer = $composer ?: (method_exists($event, 'getComposer') ? $event->getComposer() : null); + + if ($this->composer && + ($directory = $this->composer->getConfig()->get('archive-dir')) && + file_exists($file = $directory.'/composer.json') + ) { + $this->composerFilePath = $file; + $this->file = new JsonFile($this->composerFilePath); + $this->dependencies = $this->file->read(); + } + } + + /** + * @return JsonFile + */ + public function getFile() + { + return $this->file; + } + + /** + * @return string + */ + public function getComposerFilePath() + { + return $this->composerFilePath; + } + + /** + * @return Composer + */ + public function getComposer() + { + return $this->composer; + } + + /** + * @return Event + */ + public function getEvent() + { + return $this->event; + } + + /** + * @return IOInterface + */ + public function getIo() + { + return $this->io; + } + + /** + * @return array + */ + public function getDependencies() + { + return $this->dependencies; + } + + /** + * @return array + */ + public function getDevDependencies() + { + return isset($this->dependencies['require-dev']) ? $this->dependencies['require-dev'] : array(); + } + + /** + * @return array + */ + public function getProdDependencies() + { + return isset($this->dependencies['require']) ? $this->dependencies['require'] : array(); + } + + /** + * @return array + */ + public function getFlattenDependencies() + { + return array_merge($this->getDevDependencies(), $this->getProdDependencies()); + } + + /** + * @param string $dependency + * + * @return bool + */ + public function hasAsDevDependency($dependency) + { + return isset($this->dependencies['require-dev'][$dependency]); + } + + /** + * @param string $dependency + * + * @return bool + */ + public function hasAsProdDependency($dependency) + { + return isset($this->dependencies['require'][$dependency]); + } + + /** + * @param string $dependency + * + * @return bool + */ + public function hasAsDependency($dependency) + { + return $this->hasAsDevDependency($dependency) || $this->hasAsProdDependency($dependency); + } + + /** + * @param string $dependency + * @param string $version + * + * @return bool + */ + public function isDependencyAtLeast($dependency, $version) + { + if ($this->hasAsProdDependency($dependency)) { + return Semver::satisfies($version, $this->dependencies['require'][$dependency]); + } + + if ($this->hasAsDevDependency($dependency)) { + return Semver::satisfies($version, $this->dependencies['require-dev'][$dependency]); + } + + return false; + } + + /** + * @param string $dependency + * @param string $version + * + * @return bool + */ + public function isDependencyLesserThan($dependency, $version) + { + return !$this->isDependencyAtLeast($dependency, $version); + } + + /** + * @param string $dependency + * @param string $version + * @param array $environments + * + * @throws \Exception + * + * @return $this + */ + public function setDependencyVersion($dependency, $version, $environments = array('require', 'require-dev')) + { + return $this->setDependencyVersions(array($dependency => $version), $environments); + } + + /** + * @param array $dependencies + * @param array $environments + * + * @throws \Exception + * + * @return $this + */ + public function setDependencyVersions($dependencies, $environments = array('require', 'require-dev')) + { + if (!$this->composerFilePath) { + throw new \RuntimeException('No composer instance detected.'); + } + + $touched = false; + + foreach ($environments as $environment) { + foreach ($dependencies as $dependency => $version) { + if (isset($this->dependencies[$environment], $this->dependencies[$environment][$dependency])) { + $this->dependencies[$environment][$dependency] = $version; + $touched = true; + } + } + } + + if ($touched) { + if (!$this->composerFilePath) { + throw new \RuntimeException('composer.json not found (custom vendor-dir are not yet supported).'); + } + + $file = new JsonFile($this->composerFilePath); + $file->write($this->dependencies); + } + + return $this; + } + + /** + * @return $this + */ + public function update() + { + $output = shell_exec('composer update --no-scripts'); + + if (!empty($output)) { + $this->write($output); + } + + return $this; + } + + /** + * @param string|array $text + */ + public function write($text) + { + if ($this->io) { + $this->io->write($text); + + return; + } + + if (is_array($text)) { + $text = implode("\n", $text); + } + + echo $text; + } + + /** + * @return bool + */ + public function isInteractive() + { + return $this->io && $this->io->isInteractive(); + } +} diff --git a/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/UpdateHelperInterface.php b/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/UpdateHelperInterface.php new file mode 100644 index 0000000..3809dc0 --- /dev/null +++ b/core/vendor/kylekatarnls/update-helper/src/UpdateHelper/UpdateHelperInterface.php @@ -0,0 +1,8 @@ + + +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/core/vendor/laravel/framework/composer.json b/core/vendor/laravel/framework/composer.json new file mode 100644 index 0000000..9fac93e --- /dev/null +++ b/core/vendor/laravel/framework/composer.json @@ -0,0 +1,114 @@ +{ + "name": "laravel/framework", + "description": "The Laravel Framework.", + "keywords": ["framework", "laravel"], + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "ext-mbstring": "*", + "ext-openssl": "*", + "classpreloader/classpreloader": "~2.0|~3.0", + "danielstjules/stringy": "~1.8", + "doctrine/inflector": "~1.0", + "jeremeamia/superclosure": "~2.0", + "league/flysystem": "~1.0", + "monolog/monolog": "~1.11", + "mtdowling/cron-expression": "~1.0", + "nesbot/carbon": "~1.19", + "paragonie/random_compat": "~1.4", + "psy/psysh": "0.7.*", + "swiftmailer/swiftmailer": "~5.1", + "symfony/console": "2.7.*", + "symfony/css-selector": "2.7.*|2.8.*", + "symfony/debug": "2.7.*", + "symfony/dom-crawler": "2.7.*", + "symfony/finder": "2.7.*", + "symfony/http-foundation": "2.7.*", + "symfony/http-kernel": "2.7.*", + "symfony/process": "2.7.*", + "symfony/routing": "2.7.*", + "symfony/translation": "2.7.*", + "symfony/var-dumper": "2.7.*", + "vlucas/phpdotenv": "~1.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/bus": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "~3.0", + "iron-io/iron_mq": "~2.0", + "mockery/mockery": "~0.9.4", + "pda/pheanstalk": "~3.0", + "phpunit/phpunit": "~4.0", + "predis/predis": "~1.0" + }, + "autoload": { + "classmap": [ + "src/Illuminate/Queue/IlluminateQueueClosure.php" + ], + "files": [ + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).", + "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~5.3|~6.0).", + "iron-io/iron_mq": "Required to use the iron queue driver (~2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", + "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/readme.md b/core/vendor/laravel/framework/readme.md new file mode 100644 index 0000000..4500a7b --- /dev/null +++ b/core/vendor/laravel/framework/readme.md @@ -0,0 +1,32 @@ +## Laravel Framework (Kernel) + +[![StyleCI](https://styleci.io/repos/7548986/shield?style=flat)](https://styleci.io/repos/7548986) +[![Build Status](https://travis-ci.org/laravel/framework.svg)](https://travis-ci.org/laravel/framework) +[![Total Downloads](https://poser.pugx.org/laravel/framework/d/total.svg)](https://packagist.org/packages/laravel/framework) +[![Latest Stable Version](https://poser.pugx.org/laravel/framework/v/stable.svg)](https://packagist.org/packages/laravel/framework) +[![Latest Unstable Version](https://poser.pugx.org/laravel/framework/v/unstable.svg)](https://packagist.org/packages/laravel/framework) +[![License](https://poser.pugx.org/laravel/framework/license.svg)](https://packagist.org/packages/laravel/framework) + +> **Note:** This repository contains the core code of the Laravel framework. If you want to build an application using Laravel 5, visit the main [Laravel repository](https://github.com/laravel/laravel). + +## Laravel PHP Framework + +Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable, creative experience to be truly fulfilling. Laravel attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as authentication, routing, sessions, queueing, and caching. + +Laravel is accessible, yet powerful, providing powerful tools needed for large, robust applications. A superb inversion of control container, expressive migration system, and tightly integrated unit testing support give you the tools you need to build any application with which you are tasked. + +## Official Documentation + +Documentation for the framework can be found on the [Laravel website](http://laravel.com/docs). + +## Contributing + +Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](http://laravel.com/docs/contributions). + +## Security Vulnerabilities + +If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell at taylor@laravel.com. All security vulnerabilities will be promptly addressed. + +### License + +The Laravel framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php new file mode 100644 index 0000000..de057d6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php @@ -0,0 +1,453 @@ +policies = $policies; + $this->container = $container; + $this->abilities = $abilities; + $this->userResolver = $userResolver; + $this->afterCallbacks = $afterCallbacks; + $this->beforeCallbacks = $beforeCallbacks; + } + + /** + * Determine if a given ability has been defined. + * + * @param string $ability + * @return bool + */ + public function has($ability) + { + return isset($this->abilities[$ability]); + } + + /** + * Define a new ability. + * + * @param string $ability + * @param callable|string $callback + * @return $this + * + * @throws \InvalidArgumentException + */ + public function define($ability, $callback) + { + if (is_callable($callback)) { + $this->abilities[$ability] = $callback; + } elseif (is_string($callback) && str_contains($callback, '@')) { + $this->abilities[$ability] = $this->buildAbilityCallback($callback); + } else { + throw new InvalidArgumentException("Callback must be a callable or a 'Class@method' string."); + } + + return $this; + } + + /** + * Create the ability callback for a callback string. + * + * @param string $callback + * @return \Closure + */ + protected function buildAbilityCallback($callback) + { + return function () use ($callback) { + list($class, $method) = explode('@', $callback); + + return call_user_func_array([$this->resolvePolicy($class), $method], func_get_args()); + }; + } + + /** + * Define a policy class for a given class type. + * + * @param string $class + * @param string $policy + * @return $this + */ + public function policy($class, $policy) + { + $this->policies[$class] = $policy; + + return $this; + } + + /** + * Register a callback to run before all Gate checks. + * + * @param callable $callback + * @return $this + */ + public function before(callable $callback) + { + $this->beforeCallbacks[] = $callback; + + return $this; + } + + /** + * Register a callback to run after all Gate checks. + * + * @param callable $callback + * @return $this + */ + public function after(callable $callback) + { + $this->afterCallbacks[] = $callback; + + return $this; + } + + /** + * Determine if the given ability should be granted for the current user. + * + * @param string $ability + * @param array|mixed $arguments + * @return bool + */ + public function allows($ability, $arguments = []) + { + return $this->check($ability, $arguments); + } + + /** + * Determine if the given ability should be denied for the current user. + * + * @param string $ability + * @param array|mixed $arguments + * @return bool + */ + public function denies($ability, $arguments = []) + { + return ! $this->allows($ability, $arguments); + } + + /** + * Determine if the given ability should be granted for the current user. + * + * @param string $ability + * @param array|mixed $arguments + * @return bool + */ + public function check($ability, $arguments = []) + { + try { + $result = $this->raw($ability, $arguments); + } catch (UnauthorizedException $e) { + return false; + } + + return (bool) $result; + } + + /** + * Determine if the given ability should be granted for the current user. + * + * @param string $ability + * @param array|mixed $arguments + * @return \Illuminate\Auth\Access\Response + * + * @throws \Illuminate\Auth\Access\UnauthorizedException + */ + public function authorize($ability, $arguments = []) + { + $result = $this->raw($ability, $arguments); + + if ($result instanceof Response) { + return $result; + } + + return $result ? $this->allow() : $this->deny(); + } + + /** + * Get the raw result for the given ability for the current user. + * + * @param string $ability + * @param array|mixed $arguments + * @return mixed + */ + protected function raw($ability, $arguments = []) + { + if (! $user = $this->resolveUser()) { + return false; + } + + $arguments = is_array($arguments) ? $arguments : [$arguments]; + + if (is_null($result = $this->callBeforeCallbacks($user, $ability, $arguments))) { + $result = $this->callAuthCallback($user, $ability, $arguments); + } + + $this->callAfterCallbacks( + $user, $ability, $arguments, $result + ); + + return $result; + } + + /** + * Resolve and call the appropriate authorization callback. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string $ability + * @param array $arguments + * @return bool + */ + protected function callAuthCallback($user, $ability, array $arguments) + { + $callback = $this->resolveAuthCallback( + $user, $ability, $arguments + ); + + return call_user_func_array( + $callback, array_merge([$user], $arguments) + ); + } + + /** + * Call all of the before callbacks and return if a result is given. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string $ability + * @param array $arguments + * @return bool|null + */ + protected function callBeforeCallbacks($user, $ability, array $arguments) + { + $arguments = array_merge([$user, $ability], $arguments); + + foreach ($this->beforeCallbacks as $before) { + if (! is_null($result = call_user_func_array($before, $arguments))) { + return $result; + } + } + } + + /** + * Call all of the after callbacks with check result. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string $ability + * @param array $arguments + * @param bool $result + * @return void + */ + protected function callAfterCallbacks($user, $ability, array $arguments, $result) + { + $arguments = array_merge([$user, $ability, $result], $arguments); + + foreach ($this->afterCallbacks as $after) { + call_user_func_array($after, $arguments); + } + } + + /** + * Resolve the callable for the given ability and arguments. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string $ability + * @param array $arguments + * @return callable + */ + protected function resolveAuthCallback($user, $ability, array $arguments) + { + if ($this->firstArgumentCorrespondsToPolicy($arguments)) { + return $this->resolvePolicyCallback($user, $ability, $arguments); + } elseif (isset($this->abilities[$ability])) { + return $this->abilities[$ability]; + } else { + return function () { + return false; + }; + } + } + + /** + * Determine if the first argument in the array corresponds to a policy. + * + * @param array $arguments + * @return bool + */ + protected function firstArgumentCorrespondsToPolicy(array $arguments) + { + if (! isset($arguments[0])) { + return false; + } + + if (is_object($arguments[0])) { + return isset($this->policies[get_class($arguments[0])]); + } + + return is_string($arguments[0]) && isset($this->policies[$arguments[0]]); + } + + /** + * Resolve the callback for a policy check. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string $ability + * @param array $arguments + * @return callable + */ + protected function resolvePolicyCallback($user, $ability, array $arguments) + { + return function () use ($user, $ability, $arguments) { + $instance = $this->getPolicyFor($arguments[0]); + + if (method_exists($instance, 'before')) { + // We will prepend the user and ability onto the arguments so that the before + // callback can determine which ability is being called. Then we will call + // into the policy before methods with the arguments and get the result. + $beforeArguments = array_merge([$user, $ability], $arguments); + + $result = call_user_func_array( + [$instance, 'before'], $beforeArguments + ); + + // If we recieved a non-null result from the before method, we will return it + // as the result of a check. This allows developers to override the checks + // in the policy and return a result for all rules defined in the class. + if (! is_null($result)) { + return $result; + } + } + + if (strpos($ability, '-') !== false) { + $ability = Str::camel($ability); + } + + if (! is_callable([$instance, $ability])) { + return false; + } + + return call_user_func_array( + [$instance, $ability], array_merge([$user], $arguments) + ); + }; + } + + /** + * Get a policy instance for a given class. + * + * @param object|string $class + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function getPolicyFor($class) + { + if (is_object($class)) { + $class = get_class($class); + } + + if (! isset($this->policies[$class])) { + throw new InvalidArgumentException("Policy not defined for [{$class}]."); + } + + return $this->resolvePolicy($this->policies[$class]); + } + + /** + * Build a policy class instance of the given type. + * + * @param object|string $class + * @return mixed + */ + public function resolvePolicy($class) + { + return $this->container->make($class); + } + + /** + * Get a guard instance for the given user. + * + * @param \Illuminate\Contracts\Auth\Authenticatable|mixed $user + * @return static + */ + public function forUser($user) + { + $callback = function () use ($user) { + return $user; + }; + + return new static( + $this->container, $callback, $this->abilities, + $this->policies, $this->beforeCallbacks, $this->afterCallbacks + ); + } + + /** + * Resolve the user from the user resolver. + * + * @return mixed + */ + protected function resolveUser() + { + return call_user_func($this->userResolver); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Access/HandlesAuthorization.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Access/HandlesAuthorization.php new file mode 100644 index 0000000..148b584 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Access/HandlesAuthorization.php @@ -0,0 +1,30 @@ +message = $message; + } + + /** + * Get the response message. + * + * @return string|null + */ + public function message() + { + return $this->message; + } + + /** + * Get the string representation of the message. + * + * @return string + */ + public function __toString() + { + return $this->message(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Access/UnauthorizedException.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Access/UnauthorizedException.php new file mode 100644 index 0000000..7488c5b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Access/UnauthorizedException.php @@ -0,0 +1,10 @@ +setCookieJar($this->app['cookie']); + } + + if (method_exists($guard, 'setDispatcher')) { + $guard->setDispatcher($this->app['events']); + } + + if (method_exists($guard, 'setRequest')) { + $guard->setRequest($this->app->refresh('request', $guard, 'setRequest')); + } + + return $guard; + } + + /** + * Call a custom driver creator. + * + * @param string $driver + * @return \Illuminate\Contracts\Auth\Guard + */ + protected function callCustomCreator($driver) + { + $custom = parent::callCustomCreator($driver); + + if ($custom instanceof GuardContract) { + return $custom; + } + + return new Guard($custom, $this->app['session.store']); + } + + /** + * Create an instance of the database driver. + * + * @return \Illuminate\Auth\Guard + */ + public function createDatabaseDriver() + { + $provider = $this->createDatabaseProvider(); + + return new Guard($provider, $this->app['session.store']); + } + + /** + * Create an instance of the database user provider. + * + * @return \Illuminate\Auth\DatabaseUserProvider + */ + protected function createDatabaseProvider() + { + $connection = $this->app['db']->connection(); + + // When using the basic database user provider, we need to inject the table we + // want to use, since this is not an Eloquent model we will have no way to + // know without telling the provider, so we'll inject the config value. + $table = $this->app['config']['auth.table']; + + return new DatabaseUserProvider($connection, $this->app['hash'], $table); + } + + /** + * Create an instance of the Eloquent driver. + * + * @return \Illuminate\Auth\Guard + */ + public function createEloquentDriver() + { + $provider = $this->createEloquentProvider(); + + return new Guard($provider, $this->app['session.store']); + } + + /** + * Create an instance of the Eloquent user provider. + * + * @return \Illuminate\Auth\EloquentUserProvider + */ + protected function createEloquentProvider() + { + $model = $this->app['config']['auth.model']; + + return new EloquentUserProvider($this->app['hash'], $model); + } + + /** + * Get the default authentication driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['auth.driver']; + } + + /** + * Set the default authentication driver name. + * + * @param string $name + * @return void + */ + public function setDefaultDriver($name) + { + $this->app['config']['auth.driver'] = $name; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php new file mode 100644 index 0000000..9ef8041 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php @@ -0,0 +1,88 @@ +registerAuthenticator(); + + $this->registerUserResolver(); + + $this->registerAccessGate(); + + $this->registerRequestRebindHandler(); + } + + /** + * Register the authenticator services. + * + * @return void + */ + protected function registerAuthenticator() + { + $this->app->singleton('auth', function ($app) { + // Once the authentication service has actually been requested by the developer + // we will set a variable in the application indicating such. This helps us + // know that we need to set any queued cookies in the after event later. + $app['auth.loaded'] = true; + + return new AuthManager($app); + }); + + $this->app->singleton('auth.driver', function ($app) { + return $app['auth']->driver(); + }); + } + + /** + * Register a resolver for the authenticated user. + * + * @return void + */ + protected function registerUserResolver() + { + $this->app->bind(AuthenticatableContract::class, function ($app) { + return $app['auth']->user(); + }); + } + + /** + * Register the access gate service. + * + * @return void + */ + protected function registerAccessGate() + { + $this->app->singleton(GateContract::class, function ($app) { + return new Gate($app, function () use ($app) { + return $app['auth']->user(); + }); + }); + } + + /** + * Register a resolver for the authenticated user. + * + * @return void + */ + protected function registerRequestRebindHandler() + { + $this->app->rebinding('request', function ($app, $request) { + $request->setUserResolver(function () use ($app) { + return $app['auth']->user(); + }); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Authenticatable.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Authenticatable.php new file mode 100644 index 0000000..46101d2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Authenticatable.php @@ -0,0 +1,57 @@ +getKey(); + } + + /** + * Get the password for the user. + * + * @return string + */ + public function getAuthPassword() + { + return $this->password; + } + + /** + * Get the token value for the "remember me" session. + * + * @return string + */ + public function getRememberToken() + { + return $this->{$this->getRememberTokenName()}; + } + + /** + * Set the token value for the "remember me" session. + * + * @param string $value + * @return void + */ + public function setRememberToken($value) + { + $this->{$this->getRememberTokenName()} = $value; + } + + /** + * Get the column name for the "remember me" token. + * + * @return string + */ + public function getRememberTokenName() + { + return 'remember_token'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Console/ClearResetsCommand.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Console/ClearResetsCommand.php new file mode 100644 index 0000000..88dcb8c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Console/ClearResetsCommand.php @@ -0,0 +1,34 @@ +laravel['auth.password.tokens']->deleteExpired(); + + $this->info('Expired reset tokens cleared!'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php b/core/vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php new file mode 100644 index 0000000..e23a63f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php @@ -0,0 +1,146 @@ +conn = $conn; + $this->table = $table; + $this->hasher = $hasher; + } + + /** + * Retrieve a user by their unique identifier. + * + * @param mixed $identifier + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveById($identifier) + { + $user = $this->conn->table($this->table)->find($identifier); + + return $this->getGenericUser($user); + } + + /** + * Retrieve a user by their unique identifier and "remember me" token. + * + * @param mixed $identifier + * @param string $token + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveByToken($identifier, $token) + { + $user = $this->conn->table($this->table) + ->where('id', $identifier) + ->where('remember_token', $token) + ->first(); + + return $this->getGenericUser($user); + } + + /** + * Update the "remember me" token for the given user in storage. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string $token + * @return void + */ + public function updateRememberToken(UserContract $user, $token) + { + $this->conn->table($this->table) + ->where('id', $user->getAuthIdentifier()) + ->update(['remember_token' => $token]); + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveByCredentials(array $credentials) + { + // First we will add each credential element to the query as a where clause. + // Then we can execute the query and, if we found a user, return it in a + // generic "user" object that will be utilized by the Guard instances. + $query = $this->conn->table($this->table); + + foreach ($credentials as $key => $value) { + if (! Str::contains($key, 'password')) { + $query->where($key, $value); + } + } + + // Now we are ready to execute the query to see if we have an user matching + // the given credentials. If not, we will just return nulls and indicate + // that there are no matching users for these given credential arrays. + $user = $query->first(); + + return $this->getGenericUser($user); + } + + /** + * Get the generic user. + * + * @param mixed $user + * @return \Illuminate\Auth\GenericUser|null + */ + protected function getGenericUser($user) + { + if ($user !== null) { + return new GenericUser((array) $user); + } + } + + /** + * Validate a user against the given credentials. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(UserContract $user, array $credentials) + { + $plain = $credentials['password']; + + return $this->hasher->check($plain, $user->getAuthPassword()); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php b/core/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php new file mode 100644 index 0000000..e2b6dc7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php @@ -0,0 +1,174 @@ +model = $model; + $this->hasher = $hasher; + } + + /** + * Retrieve a user by their unique identifier. + * + * @param mixed $identifier + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveById($identifier) + { + return $this->createModel()->newQuery()->find($identifier); + } + + /** + * Retrieve a user by their unique identifier and "remember me" token. + * + * @param mixed $identifier + * @param string $token + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveByToken($identifier, $token) + { + $model = $this->createModel(); + + return $model->newQuery() + ->where($model->getKeyName(), $identifier) + ->where($model->getRememberTokenName(), $token) + ->first(); + } + + /** + * Update the "remember me" token for the given user in storage. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string $token + * @return void + */ + public function updateRememberToken(UserContract $user, $token) + { + $user->setRememberToken($token); + + $user->save(); + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveByCredentials(array $credentials) + { + // First we will add each credential element to the query as a where clause. + // Then we can execute the query and, if we found a user, return it in a + // Eloquent User "model" that will be utilized by the Guard instances. + $query = $this->createModel()->newQuery(); + + foreach ($credentials as $key => $value) { + if (! Str::contains($key, 'password')) { + $query->where($key, $value); + } + } + + return $query->first(); + } + + /** + * Validate a user against the given credentials. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(UserContract $user, array $credentials) + { + $plain = $credentials['password']; + + return $this->hasher->check($plain, $user->getAuthPassword()); + } + + /** + * Create a new instance of the model. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function createModel() + { + $class = '\\'.ltrim($this->model, '\\'); + + return new $class; + } + + /** + * Gets the hasher implementation. + * + * @return \Illuminate\Contracts\Hashing\Hasher + */ + public function getHasher() + { + return $this->hasher; + } + + /** + * Sets the hasher implementation. + * + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + * @return $this + */ + public function setHasher(HasherContract $hasher) + { + $this->hasher = $hasher; + + return $this; + } + + /** + * Gets the name of the Eloquent user model. + * + * @return string + */ + public function getModel() + { + return $this->model; + } + + /** + * Sets the name of the Eloquent user model. + * + * @param string $model + * @return $this + */ + public function setModel($model) + { + $this->model = $model; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/GeneratorServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Auth/GeneratorServiceProvider.php new file mode 100644 index 0000000..1b9ce10 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/GeneratorServiceProvider.php @@ -0,0 +1,65 @@ +commands as $command) { + $this->{"register{$command}Command"}(); + } + + $this->commands( + 'command.auth.resets.clear' + ); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerClearResetsCommand() + { + $this->app->singleton('command.auth.resets.clear', function () { + return new ClearResetsCommand; + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'command.auth.resets.clear', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/GenericUser.php b/core/vendor/laravel/framework/src/Illuminate/Auth/GenericUser.php new file mode 100644 index 0000000..d349d49 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/GenericUser.php @@ -0,0 +1,122 @@ +attributes = $attributes; + } + + /** + * Get the unique identifier for the user. + * + * @return mixed + */ + public function getAuthIdentifier() + { + return $this->attributes['id']; + } + + /** + * Get the password for the user. + * + * @return string + */ + public function getAuthPassword() + { + return $this->attributes['password']; + } + + /** + * Get the "remember me" token value. + * + * @return string + */ + public function getRememberToken() + { + return $this->attributes[$this->getRememberTokenName()]; + } + + /** + * Set the "remember me" token value. + * + * @param string $value + * @return void + */ + public function setRememberToken($value) + { + $this->attributes[$this->getRememberTokenName()] = $value; + } + + /** + * Get the column name for the "remember me" token. + * + * @return string + */ + public function getRememberTokenName() + { + return 'remember_token'; + } + + /** + * Dynamically access the user's attributes. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->attributes[$key]; + } + + /** + * Dynamically set an attribute on the user. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->attributes[$key] = $value; + } + + /** + * Dynamically check if a value is set on the user. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->attributes[$key]); + } + + /** + * Dynamically unset a value on the user. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->attributes[$key]); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Guard.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Guard.php new file mode 100644 index 0000000..482c3ea --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Guard.php @@ -0,0 +1,771 @@ +session = $session; + $this->request = $request; + $this->provider = $provider; + } + + /** + * Determine if the current user is authenticated. + * + * @return bool + */ + public function check() + { + return ! is_null($this->user()); + } + + /** + * Determine if the current user is a guest. + * + * @return bool + */ + public function guest() + { + return ! $this->check(); + } + + /** + * Get the currently authenticated user. + * + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function user() + { + if ($this->loggedOut) { + return; + } + + // If we've already retrieved the user for the current request we can just + // return it back immediately. We do not want to fetch the user data on + // every call to this method because that would be tremendously slow. + if (! is_null($this->user)) { + return $this->user; + } + + $id = $this->session->get($this->getName()); + + // First we will try to load the user using the identifier in the session if + // one exists. Otherwise we will check for a "remember me" cookie in this + // request, and if one exists, attempt to retrieve the user using that. + $user = null; + + if (! is_null($id)) { + $user = $this->provider->retrieveById($id); + } + + // If the user is null, but we decrypt a "recaller" cookie we can attempt to + // pull the user data on that cookie which serves as a remember cookie on + // the application. Once we have a user we can return it to the caller. + $recaller = $this->getRecaller(); + + if (is_null($user) && ! is_null($recaller)) { + $user = $this->getUserByRecaller($recaller); + + if ($user) { + $this->updateSession($user->getAuthIdentifier()); + + $this->fireLoginEvent($user, true); + } + } + + return $this->user = $user; + } + + /** + * Get the ID for the currently authenticated user. + * + * @return int|null + */ + public function id() + { + if ($this->loggedOut) { + return; + } + + $id = $this->session->get($this->getName()); + + if (is_null($id) && $this->user()) { + $id = $this->user()->getAuthIdentifier(); + } + + return $id; + } + + /** + * Pull a user from the repository by its recaller ID. + * + * @param string $recaller + * @return mixed + */ + protected function getUserByRecaller($recaller) + { + if ($this->validRecaller($recaller) && ! $this->tokenRetrievalAttempted) { + $this->tokenRetrievalAttempted = true; + + list($id, $token) = explode('|', $recaller, 2); + + $this->viaRemember = ! is_null($user = $this->provider->retrieveByToken($id, $token)); + + return $user; + } + } + + /** + * Get the decrypted recaller cookie for the request. + * + * @return string|null + */ + protected function getRecaller() + { + return $this->request->cookies->get($this->getRecallerName()); + } + + /** + * Get the user ID from the recaller cookie. + * + * @return string|null + */ + protected function getRecallerId() + { + if ($this->validRecaller($recaller = $this->getRecaller())) { + return head(explode('|', $recaller)); + } + } + + /** + * Determine if the recaller cookie is in a valid format. + * + * @param string $recaller + * @return bool + */ + protected function validRecaller($recaller) + { + if (! is_string($recaller) || ! Str::contains($recaller, '|')) { + return false; + } + + $segments = explode('|', $recaller); + + return count($segments) == 2 && trim($segments[0]) !== '' && trim($segments[1]) !== ''; + } + + /** + * Log a user into the application without sessions or cookies. + * + * @param array $credentials + * @return bool + */ + public function once(array $credentials = []) + { + if ($this->validate($credentials)) { + $this->setUser($this->lastAttempted); + + return true; + } + + return false; + } + + /** + * Validate a user's credentials. + * + * @param array $credentials + * @return bool + */ + public function validate(array $credentials = []) + { + return $this->attempt($credentials, false, false); + } + + /** + * Attempt to authenticate using HTTP Basic Auth. + * + * @param string $field + * @return \Symfony\Component\HttpFoundation\Response|null + */ + public function basic($field = 'email') + { + if ($this->check()) { + return; + } + + // If a username is set on the HTTP basic request, we will return out without + // interrupting the request lifecycle. Otherwise, we'll need to generate a + // request indicating that the given credentials were invalid for login. + if ($this->attemptBasic($this->getRequest(), $field)) { + return; + } + + return $this->getBasicResponse(); + } + + /** + * Perform a stateless HTTP Basic login attempt. + * + * @param string $field + * @return \Symfony\Component\HttpFoundation\Response|null + */ + public function onceBasic($field = 'email') + { + if (! $this->once($this->getBasicCredentials($this->getRequest(), $field))) { + return $this->getBasicResponse(); + } + } + + /** + * Attempt to authenticate using basic authentication. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $field + * @return bool + */ + protected function attemptBasic(Request $request, $field) + { + if (! $request->getUser()) { + return false; + } + + return $this->attempt($this->getBasicCredentials($request, $field)); + } + + /** + * Get the credential array for a HTTP Basic request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $field + * @return array + */ + protected function getBasicCredentials(Request $request, $field) + { + return [$field => $request->getUser(), 'password' => $request->getPassword()]; + } + + /** + * Get the response for basic authentication. + * + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function getBasicResponse() + { + $headers = ['WWW-Authenticate' => 'Basic']; + + return new Response('Invalid credentials.', 401, $headers); + } + + /** + * Attempt to authenticate a user using the given credentials. + * + * @param array $credentials + * @param bool $remember + * @param bool $login + * @return bool + */ + public function attempt(array $credentials = [], $remember = false, $login = true) + { + $this->fireAttemptEvent($credentials, $remember, $login); + + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + + // If an implementation of UserInterface was returned, we'll ask the provider + // to validate the user against the given credentials, and if they are in + // fact valid we'll log the users into the application and return true. + if ($this->hasValidCredentials($user, $credentials)) { + if ($login) { + $this->login($user, $remember); + } + + return true; + } + + return false; + } + + /** + * Determine if the user matches the credentials. + * + * @param mixed $user + * @param array $credentials + * @return bool + */ + protected function hasValidCredentials($user, $credentials) + { + return ! is_null($user) && $this->provider->validateCredentials($user, $credentials); + } + + /** + * Fire the attempt event with the arguments. + * + * @param array $credentials + * @param bool $remember + * @param bool $login + * @return void + */ + protected function fireAttemptEvent(array $credentials, $remember, $login) + { + if ($this->events) { + $payload = [$credentials, $remember, $login]; + + $this->events->fire('auth.attempt', $payload); + } + } + + /** + * Register an authentication attempt event listener. + * + * @param mixed $callback + * @return void + */ + public function attempting($callback) + { + if ($this->events) { + $this->events->listen('auth.attempt', $callback); + } + } + + /** + * Log a user into the application. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param bool $remember + * @return void + */ + public function login(UserContract $user, $remember = false) + { + $this->updateSession($user->getAuthIdentifier()); + + // If the user should be permanently "remembered" by the application we will + // queue a permanent cookie that contains the encrypted copy of the user + // identifier. We will then decrypt this later to retrieve the users. + if ($remember) { + $this->createRememberTokenIfDoesntExist($user); + + $this->queueRecallerCookie($user); + } + + // If we have an event dispatcher instance set we will fire an event so that + // any listeners will hook into the authentication events and run actions + // based on the login and logout events fired from the guard instances. + $this->fireLoginEvent($user, $remember); + + $this->setUser($user); + } + + /** + * Fire the login event if the dispatcher is set. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param bool $remember + * @return void + */ + protected function fireLoginEvent($user, $remember = false) + { + if (isset($this->events)) { + $this->events->fire('auth.login', [$user, $remember]); + } + } + + /** + * Update the session with the given ID. + * + * @param string $id + * @return void + */ + protected function updateSession($id) + { + $this->session->set($this->getName(), $id); + + $this->session->migrate(true); + } + + /** + * Log the given user ID into the application. + * + * @param mixed $id + * @param bool $remember + * @return \Illuminate\Contracts\Auth\Authenticatable + */ + public function loginUsingId($id, $remember = false) + { + $this->session->set($this->getName(), $id); + + $this->login($user = $this->provider->retrieveById($id), $remember); + + return $user; + } + + /** + * Log the given user ID into the application without sessions or cookies. + * + * @param mixed $id + * @return bool + */ + public function onceUsingId($id) + { + if (! is_null($user = $this->provider->retrieveById($id))) { + $this->setUser($user); + + return true; + } + + return false; + } + + /** + * Queue the recaller cookie into the cookie jar. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return void + */ + protected function queueRecallerCookie(UserContract $user) + { + $value = $user->getAuthIdentifier().'|'.$user->getRememberToken(); + + $this->getCookieJar()->queue($this->createRecaller($value)); + } + + /** + * Create a "remember me" cookie for a given ID. + * + * @param string $value + * @return \Symfony\Component\HttpFoundation\Cookie + */ + protected function createRecaller($value) + { + return $this->getCookieJar()->forever($this->getRecallerName(), $value); + } + + /** + * Log the user out of the application. + * + * @return void + */ + public function logout() + { + $user = $this->user(); + + // If we have an event dispatcher instance, we can fire off the logout event + // so any further processing can be done. This allows the developer to be + // listening for anytime a user signs out of this application manually. + $this->clearUserDataFromStorage(); + + if (! is_null($this->user)) { + $this->refreshRememberToken($user); + } + + if (isset($this->events)) { + $this->events->fire('auth.logout', [$user]); + } + + // Once we have fired the logout event we will clear the users out of memory + // so they are no longer available as the user is no longer considered as + // being signed into this application and should not be available here. + $this->user = null; + + $this->loggedOut = true; + } + + /** + * Remove the user data from the session and cookies. + * + * @return void + */ + protected function clearUserDataFromStorage() + { + $this->session->remove($this->getName()); + + if (! is_null($this->getRecaller())) { + $recaller = $this->getRecallerName(); + + $this->getCookieJar()->queue($this->getCookieJar()->forget($recaller)); + } + } + + /** + * Refresh the "remember me" token for the user. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return void + */ + protected function refreshRememberToken(UserContract $user) + { + $user->setRememberToken($token = Str::random(60)); + + $this->provider->updateRememberToken($user, $token); + } + + /** + * Create a new "remember me" token for the user if one doesn't already exist. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return void + */ + protected function createRememberTokenIfDoesntExist(UserContract $user) + { + if (empty($user->getRememberToken())) { + $this->refreshRememberToken($user); + } + } + + /** + * Get the cookie creator instance used by the guard. + * + * @return \Illuminate\Contracts\Cookie\QueueingFactory + * + * @throws \RuntimeException + */ + public function getCookieJar() + { + if (! isset($this->cookie)) { + throw new RuntimeException('Cookie jar has not been set.'); + } + + return $this->cookie; + } + + /** + * Set the cookie creator instance used by the guard. + * + * @param \Illuminate\Contracts\Cookie\QueueingFactory $cookie + * @return void + */ + public function setCookieJar(CookieJar $cookie) + { + $this->cookie = $cookie; + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public function getDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return void + */ + public function setDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Get the session store used by the guard. + * + * @return \Illuminate\Session\Store + */ + public function getSession() + { + return $this->session; + } + + /** + * Get the user provider used by the guard. + * + * @return \Illuminate\Contracts\Auth\UserProvider + */ + public function getProvider() + { + return $this->provider; + } + + /** + * Set the user provider used by the guard. + * + * @param \Illuminate\Contracts\Auth\UserProvider $provider + * @return void + */ + public function setProvider(UserProvider $provider) + { + $this->provider = $provider; + } + + /** + * Return the currently cached user. + * + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function getUser() + { + return $this->user; + } + + /** + * Set the current user. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return void + */ + public function setUser(UserContract $user) + { + $this->user = $user; + + $this->loggedOut = false; + } + + /** + * Get the current request instance. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->request ?: Request::createFromGlobals(); + } + + /** + * Set the current request instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return $this + */ + public function setRequest(Request $request) + { + $this->request = $request; + + return $this; + } + + /** + * Get the last user we attempted to authenticate. + * + * @return \Illuminate\Contracts\Auth\Authenticatable + */ + public function getLastAttempted() + { + return $this->lastAttempted; + } + + /** + * Get a unique identifier for the auth session value. + * + * @return string + */ + public function getName() + { + return 'login_'.md5(get_class($this)); + } + + /** + * Get the name of the cookie used to store the "recaller". + * + * @return string + */ + public function getRecallerName() + { + return 'remember_'.md5(get_class($this)); + } + + /** + * Determine if the user was authenticated via "remember me" cookie. + * + * @return bool + */ + public function viaRemember() + { + return $this->viaRemember; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Middleware/AuthenticateWithBasicAuth.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Middleware/AuthenticateWithBasicAuth.php new file mode 100644 index 0000000..a2418b3 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Middleware/AuthenticateWithBasicAuth.php @@ -0,0 +1,39 @@ +auth = $auth; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + return $this->auth->basic() ?: $next($request); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php new file mode 100644 index 0000000..d5d8e16 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php @@ -0,0 +1,16 @@ +email; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php new file mode 100644 index 0000000..db93cf9 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php @@ -0,0 +1,193 @@ +table = $table; + $this->hashKey = $hashKey; + $this->expires = $expires * 60; + $this->connection = $connection; + } + + /** + * Create a new token record. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword $user + * @return string + */ + public function create(CanResetPasswordContract $user) + { + $email = $user->getEmailForPasswordReset(); + + $this->deleteExisting($user); + + // We will create a new, random token for the user so that we can e-mail them + // a safe link to the password reset form. Then we will insert a record in + // the database so that we can verify the token within the actual reset. + $token = $this->createNewToken(); + + $this->getTable()->insert($this->getPayload($email, $token)); + + return $token; + } + + /** + * Delete all existing reset tokens from the database. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword $user + * @return int + */ + protected function deleteExisting(CanResetPasswordContract $user) + { + return $this->getTable()->where('email', $user->getEmailForPasswordReset())->delete(); + } + + /** + * Build the record payload for the table. + * + * @param string $email + * @param string $token + * @return array + */ + protected function getPayload($email, $token) + { + return ['email' => $email, 'token' => $token, 'created_at' => new Carbon]; + } + + /** + * Determine if a token record exists and is valid. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword $user + * @param string $token + * @return bool + */ + public function exists(CanResetPasswordContract $user, $token) + { + $email = $user->getEmailForPasswordReset(); + + $token = (array) $this->getTable()->where('email', $email)->where('token', $token)->first(); + + return $token && ! $this->tokenExpired($token); + } + + /** + * Determine if the token has expired. + * + * @param array $token + * @return bool + */ + protected function tokenExpired($token) + { + $expirationTime = strtotime($token['created_at']) + $this->expires; + + return $expirationTime < $this->getCurrentTime(); + } + + /** + * Get the current UNIX timestamp. + * + * @return int + */ + protected function getCurrentTime() + { + return time(); + } + + /** + * Delete a token record by token. + * + * @param string $token + * @return void + */ + public function delete($token) + { + $this->getTable()->where('token', $token)->delete(); + } + + /** + * Delete expired tokens. + * + * @return void + */ + public function deleteExpired() + { + $expiredAt = Carbon::now()->subSeconds($this->expires); + + $this->getTable()->where('created_at', '<', $expiredAt)->delete(); + } + + /** + * Create a new token for the user. + * + * @return string + */ + public function createNewToken() + { + return hash_hmac('sha256', Str::random(40), $this->hashKey); + } + + /** + * Begin a new database query against the table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function getTable() + { + return $this->connection->table($this->table); + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php new file mode 100644 index 0000000..2fecd5b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php @@ -0,0 +1,253 @@ +users = $users; + $this->mailer = $mailer; + $this->tokens = $tokens; + $this->emailView = $emailView; + } + + /** + * Send a password reset link to a user. + * + * @param array $credentials + * @param \Closure|null $callback + * @return string + */ + public function sendResetLink(array $credentials, Closure $callback = null) + { + // First we will check to see if we found a user at the given credentials and + // if we did not we will redirect back to this current URI with a piece of + // "flash" data in the session to indicate to the developers the errors. + $user = $this->getUser($credentials); + + if (is_null($user)) { + return PasswordBrokerContract::INVALID_USER; + } + + // Once we have the reset token, we are ready to send the message out to this + // user with a link to reset their password. We will then redirect back to + // the current URI having nothing set in the session to indicate errors. + $token = $this->tokens->create($user); + + $this->emailResetLink($user, $token, $callback); + + return PasswordBrokerContract::RESET_LINK_SENT; + } + + /** + * Send the password reset link via e-mail. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword $user + * @param string $token + * @param \Closure|null $callback + * @return int + */ + public function emailResetLink(CanResetPasswordContract $user, $token, Closure $callback = null) + { + // We will use the reminder view that was given to the broker to display the + // password reminder e-mail. We'll pass a "token" variable into the views + // so that it may be displayed for an user to click for password reset. + $view = $this->emailView; + + return $this->mailer->send($view, compact('token', 'user'), function ($m) use ($user, $token, $callback) { + $m->to($user->getEmailForPasswordReset()); + + if (! is_null($callback)) { + call_user_func($callback, $m, $user, $token); + } + }); + } + + /** + * Reset the password for the given token. + * + * @param array $credentials + * @param \Closure $callback + * @return mixed + */ + public function reset(array $credentials, Closure $callback) + { + // If the responses from the validate method is not a user instance, we will + // assume that it is a redirect and simply return it from this method and + // the user is properly redirected having an error message on the post. + $user = $this->validateReset($credentials); + + if (! $user instanceof CanResetPasswordContract) { + return $user; + } + + $pass = $credentials['password']; + + // Once we have called this callback, we will remove this token row from the + // table and return the response from this callback so the user gets sent + // to the destination given by the developers from the callback return. + call_user_func($callback, $user, $pass); + + $this->tokens->delete($credentials['token']); + + return PasswordBrokerContract::PASSWORD_RESET; + } + + /** + * Validate a password reset for the given credentials. + * + * @param array $credentials + * @return \Illuminate\Contracts\Auth\CanResetPassword + */ + protected function validateReset(array $credentials) + { + if (is_null($user = $this->getUser($credentials))) { + return PasswordBrokerContract::INVALID_USER; + } + + if (! $this->validateNewPassword($credentials)) { + return PasswordBrokerContract::INVALID_PASSWORD; + } + + if (! $this->tokens->exists($user, $credentials['token'])) { + return PasswordBrokerContract::INVALID_TOKEN; + } + + return $user; + } + + /** + * Set a custom password validator. + * + * @param \Closure $callback + * @return void + */ + public function validator(Closure $callback) + { + $this->passwordValidator = $callback; + } + + /** + * Determine if the passwords match for the request. + * + * @param array $credentials + * @return bool + */ + public function validateNewPassword(array $credentials) + { + list($password, $confirm) = [ + $credentials['password'], + $credentials['password_confirmation'], + ]; + + if (isset($this->passwordValidator)) { + return call_user_func( + $this->passwordValidator, $credentials) && $password === $confirm; + } + + return $this->validatePasswordWithDefaults($credentials); + } + + /** + * Determine if the passwords are valid for the request. + * + * @param array $credentials + * @return bool + */ + protected function validatePasswordWithDefaults(array $credentials) + { + list($password, $confirm) = [ + $credentials['password'], + $credentials['password_confirmation'], + ]; + + return $password === $confirm && mb_strlen($password) >= 6; + } + + /** + * Get the user for the given credentials. + * + * @param array $credentials + * @return \Illuminate\Contracts\Auth\CanResetPassword + * + * @throws \UnexpectedValueException + */ + public function getUser(array $credentials) + { + $credentials = Arr::except($credentials, ['token']); + + $user = $this->users->retrieveByCredentials($credentials); + + if ($user && ! $user instanceof CanResetPasswordContract) { + throw new UnexpectedValueException('User must implement CanResetPassword interface.'); + } + + return $user; + } + + /** + * Get the password reset token repository implementation. + * + * @return \Illuminate\Auth\Passwords\TokenRepositoryInterface + */ + protected function getRepository() + { + return $this->tokens; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordResetServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordResetServiceProvider.php new file mode 100644 index 0000000..2da8f7d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordResetServiceProvider.php @@ -0,0 +1,87 @@ +registerPasswordBroker(); + + $this->registerTokenRepository(); + } + + /** + * Register the password broker instance. + * + * @return void + */ + protected function registerPasswordBroker() + { + $this->app->singleton('auth.password', function ($app) { + // The password token repository is responsible for storing the email addresses + // and password reset tokens. It will be used to verify the tokens are valid + // for the given e-mail addresses. We will resolve an implementation here. + $tokens = $app['auth.password.tokens']; + + $users = $app['auth']->driver()->getProvider(); + + $view = $app['config']['auth.password.email']; + + // The password broker uses a token repository to validate tokens and send user + // password e-mails, as well as validating that password reset process as an + // aggregate service of sorts providing a convenient interface for resets. + return new PasswordBroker( + $tokens, $users, $app['mailer'], $view + ); + }); + } + + /** + * Register the token repository implementation. + * + * @return void + */ + protected function registerTokenRepository() + { + $this->app->singleton('auth.password.tokens', function ($app) { + $connection = $app['db']->connection(); + + // The database token repository is an implementation of the token repository + // interface, and is responsible for the actual storing of auth tokens and + // their e-mail addresses. We will inject this table and hash key to it. + $table = $app['config']['auth.password.table']; + + $key = $app['config']['app.key']; + + $expire = $app['config']->get('auth.password.expire', 60); + + return new DbRepository($connection, $table, $key, $expire); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['auth.password', 'auth.password.tokens']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/TokenRepositoryInterface.php b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/TokenRepositoryInterface.php new file mode 100644 index 0000000..0570773 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Auth/Passwords/TokenRepositoryInterface.php @@ -0,0 +1,40 @@ +=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/http": "5.1.*", + "illuminate/session": "5.1.*", + "illuminate/support": "5.1.*", + "nesbot/carbon": "~1.19" + }, + "autoload": { + "psr-4": { + "Illuminate\\Auth\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "illuminate/console": "Required to use the auth:clear-resets command (5.1.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastEvent.php b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastEvent.php new file mode 100644 index 0000000..1b6bad9 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastEvent.php @@ -0,0 +1,87 @@ +broadcaster = $broadcaster; + } + + /** + * Handle the queued job. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param array $data + * @return void + */ + public function fire(Job $job, array $data) + { + $event = unserialize($data['event']); + + $name = method_exists($event, 'broadcastAs') + ? $event->broadcastAs() : get_class($event); + + $this->broadcaster->broadcast( + $event->broadcastOn(), $name, $this->getPayloadFromEvent($event) + ); + + $job->delete(); + } + + /** + * Get the payload for the given event. + * + * @param mixed $event + * @return array + */ + protected function getPayloadFromEvent($event) + { + if (method_exists($event, 'broadcastWith')) { + return $event->broadcastWith(); + } + + $payload = []; + + foreach ((new ReflectionClass($event))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { + $payload[$property->getName()] = $this->formatProperty($property->getValue($event)); + } + + return $payload; + } + + /** + * Format the given value for a property. + * + * @param mixed $value + * @return mixed + */ + protected function formatProperty($value) + { + if ($value instanceof Arrayable) { + return $value->toArray(); + } + + return $value; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php new file mode 100644 index 0000000..97a0fe5 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php @@ -0,0 +1,219 @@ +app = $app; + } + + /** + * Get a driver instance. + * + * @param string $driver + * @return mixed + */ + public function connection($driver = null) + { + return $this->driver($driver); + } + + /** + * Get a driver instance. + * + * @param string $name + * @return mixed + */ + public function driver($name = null) + { + $name = $name ?: $this->getDefaultDriver(); + + return $this->drivers[$name] = $this->get($name); + } + + /** + * Attempt to get the connection from the local cache. + * + * @param string $name + * @return \Illuminate\Contracts\Broadcasting\Broadcaster + */ + protected function get($name) + { + return isset($this->drivers[$name]) ? $this->drivers[$name] : $this->resolve($name); + } + + /** + * Resolve the given store. + * + * @param string $name + * @return \Illuminate\Contracts\Broadcasting\Broadcaster + * + * @throws \InvalidArgumentException + */ + protected function resolve($name) + { + $config = $this->getConfig($name); + + if (is_null($config)) { + throw new InvalidArgumentException("Broadcaster [{$name}] is not defined."); + } + + if (isset($this->customCreators[$config['driver']])) { + return $this->callCustomCreator($config); + } else { + $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; + + if (method_exists($this, $driverMethod)) { + return $this->{$driverMethod}($config); + } else { + throw new InvalidArgumentException("Driver [{$config['driver']}] not supported."); + } + } + } + + /** + * Call a custom driver creator. + * + * @param array $config + * @return mixed + */ + protected function callCustomCreator(array $config) + { + return $this->customCreators[$config['driver']]($this->app, $config); + } + + /** + * Create an instance of the driver. + * + * @param array $config + * @return \Illuminate\Contracts\Broadcasting\Broadcaster + */ + protected function createPusherDriver(array $config) + { + return new PusherBroadcaster( + new Pusher($config['key'], $config['secret'], $config['app_id'], Arr::get($config, 'options', [])) + ); + } + + /** + * Create an instance of the driver. + * + * @param array $config + * @return \Illuminate\Contracts\Broadcasting\Broadcaster + */ + protected function createRedisDriver(array $config) + { + return new RedisBroadcaster( + $this->app->make('redis'), Arr::get($config, 'connection') + ); + } + + /** + * Create an instance of the driver. + * + * @param array $config + * @return \Illuminate\Contracts\Broadcasting\Broadcaster + */ + protected function createLogDriver(array $config) + { + return new LogBroadcaster( + $this->app->make('Psr\Log\LoggerInterface') + ); + } + + /** + * Get the connection configuration. + * + * @param string $name + * @return array + */ + protected function getConfig($name) + { + return $this->app['config']["broadcasting.connections.{$name}"]; + } + + /** + * Get the default driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['broadcasting.default']; + } + + /** + * Set the default driver name. + * + * @param string $name + * @return void + */ + public function setDefaultDriver($name) + { + $this->app['config']['broadcasting.default'] = $name; + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * @return $this + */ + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback; + + return $this; + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->driver(), $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastServiceProvider.php new file mode 100644 index 0000000..c6f14fe --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastServiceProvider.php @@ -0,0 +1,49 @@ +app->singleton('Illuminate\Broadcasting\BroadcastManager', function ($app) { + return new BroadcastManager($app); + }); + + $this->app->singleton('Illuminate\Contracts\Broadcasting\Broadcaster', function ($app) { + return $app->make('Illuminate\Broadcasting\BroadcastManager')->connection(); + }); + + $this->app->alias( + 'Illuminate\Broadcasting\BroadcastManager', 'Illuminate\Contracts\Broadcasting\Factory' + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'Illuminate\Broadcasting\BroadcastManager', + 'Illuminate\Contracts\Broadcasting\Factory', + 'Illuminate\Contracts\Broadcasting\Broadcaster', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/LogBroadcaster.php b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/LogBroadcaster.php new file mode 100644 index 0000000..6b90492 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/LogBroadcaster.php @@ -0,0 +1,39 @@ +logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function broadcast(array $channels, $event, array $payload = []) + { + $channels = implode(', ', $channels); + + $payload = json_encode($payload, JSON_PRETTY_PRINT); + + $this->logger->info('Broadcasting ['.$event.'] on channels ['.$channels.'] with payload:'.PHP_EOL.$payload); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php new file mode 100644 index 0000000..cea2d38 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php @@ -0,0 +1,45 @@ +pusher = $pusher; + } + + /** + * {@inheritdoc} + */ + public function broadcast(array $channels, $event, array $payload = []) + { + $this->pusher->trigger($channels, $event, $payload); + } + + /** + * Get the Pusher SDK instance. + * + * @return \Pusher + */ + public function getPusher() + { + return $this->pusher; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php new file mode 100644 index 0000000..91c7c61 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php @@ -0,0 +1,50 @@ +redis = $redis; + $this->connection = $connection; + } + + /** + * {@inheritdoc} + */ + public function broadcast(array $channels, $event, array $payload = []) + { + $connection = $this->redis->connection($this->connection); + + $payload = json_encode(['event' => $event, 'data' => $payload]); + + foreach ($channels as $channel) { + $connection->publish($channel, $payload); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Broadcasting/composer.json b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/composer.json new file mode 100644 index 0000000..c3e89d1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Broadcasting/composer.json @@ -0,0 +1,35 @@ +{ + "name": "illuminate/broadcasting", + "description": "The Illuminate Broadcasting package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Broadcasting\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Bus/BusServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Bus/BusServiceProvider.php new file mode 100644 index 0000000..55eeb2a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Bus/BusServiceProvider.php @@ -0,0 +1,51 @@ +app->singleton('Illuminate\Bus\Dispatcher', function ($app) { + return new Dispatcher($app, function () use ($app) { + return $app['Illuminate\Contracts\Queue\Queue']; + }); + }); + + $this->app->alias( + 'Illuminate\Bus\Dispatcher', 'Illuminate\Contracts\Bus\Dispatcher' + ); + + $this->app->alias( + 'Illuminate\Bus\Dispatcher', 'Illuminate\Contracts\Bus\QueueingDispatcher' + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'Illuminate\Bus\Dispatcher', + 'Illuminate\Contracts\Bus\Dispatcher', + 'Illuminate\Contracts\Bus\QueueingDispatcher', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php b/core/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php new file mode 100644 index 0000000..cecf9f1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php @@ -0,0 +1,409 @@ +container = $container; + $this->queueResolver = $queueResolver; + $this->pipeline = new Pipeline($container); + } + + /** + * Marshal a command and dispatch it to its appropriate handler. + * + * @param mixed $command + * @param array $array + * @return mixed + */ + public function dispatchFromArray($command, array $array) + { + return $this->dispatch($this->marshalFromArray($command, $array)); + } + + /** + * Marshal a command and dispatch it to its appropriate handler. + * + * @param mixed $command + * @param \ArrayAccess $source + * @param array $extras + * @return mixed + */ + public function dispatchFrom($command, ArrayAccess $source, array $extras = []) + { + return $this->dispatch($this->marshal($command, $source, $extras)); + } + + /** + * Marshal a command from the given array. + * + * @param string $command + * @param array $array + * @return mixed + */ + protected function marshalFromArray($command, array $array) + { + return $this->marshal($command, new Collection, $array); + } + + /** + * Marshal a command from the given array accessible object. + * + * @param string $command + * @param \ArrayAccess $source + * @param array $extras + * @return mixed + */ + protected function marshal($command, ArrayAccess $source, array $extras = []) + { + $injected = []; + + $reflection = new ReflectionClass($command); + + if ($constructor = $reflection->getConstructor()) { + $injected = array_map(function ($parameter) use ($command, $source, $extras) { + return $this->getParameterValueForCommand($command, $source, $parameter, $extras); + }, $constructor->getParameters()); + } + + return $reflection->newInstanceArgs($injected); + } + + /** + * Get a parameter value for a marshalled command. + * + * @param string $command + * @param \ArrayAccess $source + * @param \ReflectionParameter $parameter + * @param array $extras + * @return mixed + */ + protected function getParameterValueForCommand($command, ArrayAccess $source, ReflectionParameter $parameter, array $extras = []) + { + if (array_key_exists($parameter->name, $extras)) { + return $extras[$parameter->name]; + } + + if (isset($source[$parameter->name])) { + return $source[$parameter->name]; + } + + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + MarshalException::whileMapping($command, $parameter); + } + + /** + * Dispatch a command to its appropriate handler. + * + * @param mixed $command + * @param \Closure|null $afterResolving + * @return mixed + */ + public function dispatch($command, Closure $afterResolving = null) + { + if ($this->queueResolver && $this->commandShouldBeQueued($command)) { + return $this->dispatchToQueue($command); + } else { + return $this->dispatchNow($command, $afterResolving); + } + } + + /** + * Dispatch a command to its appropriate handler in the current process. + * + * @param mixed $command + * @param \Closure|null $afterResolving + * @return mixed + */ + public function dispatchNow($command, Closure $afterResolving = null) + { + return $this->pipeline->send($command)->through($this->pipes)->then(function ($command) use ($afterResolving) { + if ($command instanceof SelfHandling) { + return $this->container->call([$command, 'handle']); + } + + $handler = $this->resolveHandler($command); + + if ($afterResolving) { + call_user_func($afterResolving, $handler); + } + + return call_user_func( + [$handler, $this->getHandlerMethod($command)], $command + ); + }); + } + + /** + * Determine if the given command should be queued. + * + * @param mixed $command + * @return bool + */ + protected function commandShouldBeQueued($command) + { + if ($command instanceof ShouldQueue) { + return true; + } + + return (new ReflectionClass($this->getHandlerClass($command)))->implementsInterface( + 'Illuminate\Contracts\Queue\ShouldQueue' + ); + } + + /** + * Dispatch a command to its appropriate handler behind a queue. + * + * @param mixed $command + * @return mixed + * + * @throws \RuntimeException + */ + public function dispatchToQueue($command) + { + $queue = call_user_func($this->queueResolver); + + if (! $queue instanceof Queue) { + throw new RuntimeException('Queue resolver did not return a Queue implementation.'); + } + + if (method_exists($command, 'queue')) { + return $command->queue($queue, $command); + } else { + return $this->pushCommandToQueue($queue, $command); + } + } + + /** + * Push the command onto the given queue instance. + * + * @param \Illuminate\Contracts\Queue\Queue $queue + * @param mixed $command + * @return mixed + */ + protected function pushCommandToQueue($queue, $command) + { + if (isset($command->queue, $command->delay)) { + return $queue->laterOn($command->queue, $command->delay, $command); + } + + if (isset($command->queue)) { + return $queue->pushOn($command->queue, $command); + } + + if (isset($command->delay)) { + return $queue->later($command->delay, $command); + } + + return $queue->push($command); + } + + /** + * Get the handler instance for the given command. + * + * @param mixed $command + * @return mixed + */ + public function resolveHandler($command) + { + if ($command instanceof SelfHandling) { + return $command; + } + + return $this->container->make($this->getHandlerClass($command)); + } + + /** + * Get the handler class for the given command. + * + * @param mixed $command + * @return string + */ + public function getHandlerClass($command) + { + if ($command instanceof SelfHandling) { + return get_class($command); + } + + return $this->inflectSegment($command, 0); + } + + /** + * Get the handler method for the given command. + * + * @param mixed $command + * @return string + */ + public function getHandlerMethod($command) + { + if ($command instanceof SelfHandling) { + return 'handle'; + } + + return $this->inflectSegment($command, 1); + } + + /** + * Get the given handler segment for the given command. + * + * @param mixed $command + * @param int $segment + * @return string + */ + protected function inflectSegment($command, $segment) + { + $className = get_class($command); + + if (isset($this->mappings[$className])) { + return $this->getMappingSegment($className, $segment); + } elseif ($this->mapper) { + return $this->getMapperSegment($command, $segment); + } + + throw new InvalidArgumentException("No handler registered for command [{$className}]"); + } + + /** + * Get the given segment from a given class handler. + * + * @param string $className + * @param int $segment + * @return string + */ + protected function getMappingSegment($className, $segment) + { + return explode('@', $this->mappings[$className])[$segment]; + } + + /** + * Get the given segment from a given class handler using the custom mapper. + * + * @param mixed $command + * @param int $segment + * @return string + */ + protected function getMapperSegment($command, $segment) + { + return explode('@', call_user_func($this->mapper, $command))[$segment]; + } + + /** + * Register command-to-handler mappings. + * + * @param array $commands + * @return void + */ + public function maps(array $commands) + { + $this->mappings = array_merge($this->mappings, $commands); + } + + /** + * Register a fallback mapper callback. + * + * @param \Closure $mapper + * @return void + */ + public function mapUsing(Closure $mapper) + { + $this->mapper = $mapper; + } + + /** + * Map the command to a handler within a given root namespace. + * + * @param mixed $command + * @param string $commandNamespace + * @param string $handlerNamespace + * @return string + */ + public static function simpleMapping($command, $commandNamespace, $handlerNamespace) + { + $command = str_replace($commandNamespace, '', get_class($command)); + + return $handlerNamespace.'\\'.trim($command, '\\').'Handler@handle'; + } + + /** + * Set the pipes through which commands should be piped before dispatching. + * + * @param array $pipes + * @return $this + */ + public function pipeThrough(array $pipes) + { + $this->pipes = $pipes; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Bus/MarshalException.php b/core/vendor/laravel/framework/src/Illuminate/Bus/MarshalException.php new file mode 100644 index 0000000..49c7674 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Bus/MarshalException.php @@ -0,0 +1,23 @@ +name}] to command [{$command}]"); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Bus/Queueable.php b/core/vendor/laravel/framework/src/Illuminate/Bus/Queueable.php new file mode 100644 index 0000000..42d1ca5 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Bus/Queueable.php @@ -0,0 +1,46 @@ +queue = $queue; + + return $this; + } + + /** + * Set the desired delay for the job. + * + * @param int $delay + * @return $this + */ + public function delay($delay) + { + $this->delay = $delay; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Bus/composer.json b/core/vendor/laravel/framework/src/Illuminate/Bus/composer.json new file mode 100644 index 0000000..16982e6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Bus/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/bus", + "description": "The Illuminate Bus package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/pipeline": "5.1.*", + "illuminate/support": "5.1.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/ApcStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/ApcStore.php new file mode 100644 index 0000000..9125dec --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/ApcStore.php @@ -0,0 +1,130 @@ +apc = $apc; + $this->prefix = $prefix; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $value = $this->apc->get($this->prefix.$key); + + if ($value !== false) { + return $value; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $this->apc->put($this->prefix.$key, $value, $minutes * 60); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function increment($key, $value = 1) + { + return $this->apc->increment($this->prefix.$key, $value); + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function decrement($key, $value = 1) + { + return $this->apc->decrement($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + return $this->apc->delete($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->apc->flush(); + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/ApcWrapper.php b/core/vendor/laravel/framework/src/Illuminate/Cache/ApcWrapper.php new file mode 100644 index 0000000..9fdf293 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/ApcWrapper.php @@ -0,0 +1,92 @@ +apcu = function_exists('apcu_fetch'); + } + + /** + * Get an item from the cache. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + return $this->apcu ? apcu_fetch($key) : apc_fetch($key); + } + + /** + * Store an item in the cache. + * + * @param string $key + * @param mixed $value + * @param int $seconds + * @return array|bool + */ + public function put($key, $value, $seconds) + { + return $this->apcu ? apcu_store($key, $value, $seconds) : apc_store($key, $value, $seconds); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function increment($key, $value) + { + return $this->apcu ? apcu_inc($key, $value) : apc_inc($key, $value); + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function decrement($key, $value) + { + return $this->apcu ? apcu_dec($key, $value) : apc_dec($key, $value); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function delete($key) + { + return $this->apcu ? apcu_delete($key) : apc_delete($key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->apcu ? apcu_clear_cache() : apc_clear_cache('user'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/ArrayStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/ArrayStore.php new file mode 100644 index 0000000..3dd8c04 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/ArrayStore.php @@ -0,0 +1,112 @@ +storage)) { + return $this->storage[$key]; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $this->storage[$key] = $value; + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function increment($key, $value = 1) + { + $this->storage[$key] = ((int) $this->storage[$key]) + $value; + + return $this->storage[$key]; + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function decrement($key, $value = 1) + { + return $this->increment($key, $value * -1); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + unset($this->storage[$key]); + + return true; + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->storage = []; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return ''; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php b/core/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php new file mode 100644 index 0000000..1eb7935 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php @@ -0,0 +1,320 @@ +app = $app; + } + + /** + * Get a cache store instance by name. + * + * @param string|null $name + * @return mixed + */ + public function store($name = null) + { + $name = $name ?: $this->getDefaultDriver(); + + return $this->stores[$name] = $this->get($name); + } + + /** + * Get a cache driver instance. + * + * @param string $driver + * @return mixed + */ + public function driver($driver = null) + { + return $this->store($driver); + } + + /** + * Attempt to get the store from the local cache. + * + * @param string $name + * @return \Illuminate\Contracts\Cache\Repository + */ + protected function get($name) + { + return isset($this->stores[$name]) ? $this->stores[$name] : $this->resolve($name); + } + + /** + * Resolve the given store. + * + * @param string $name + * @return \Illuminate\Contracts\Cache\Repository + * + * @throws \InvalidArgumentException + */ + protected function resolve($name) + { + $config = $this->getConfig($name); + + if (is_null($config)) { + throw new InvalidArgumentException("Cache store [{$name}] is not defined."); + } + + if (isset($this->customCreators[$config['driver']])) { + return $this->callCustomCreator($config); + } else { + $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; + + if (method_exists($this, $driverMethod)) { + return $this->{$driverMethod}($config); + } else { + throw new InvalidArgumentException("Driver [{$config['driver']}] not supported."); + } + } + } + + /** + * Call a custom driver creator. + * + * @param array $config + * @return mixed + */ + protected function callCustomCreator(array $config) + { + return $this->customCreators[$config['driver']]($this->app, $config); + } + + /** + * Create an instance of the APC cache driver. + * + * @param array $config + * @return \Illuminate\Cache\ApcStore + */ + protected function createApcDriver(array $config) + { + $prefix = $this->getPrefix($config); + + return $this->repository(new ApcStore(new ApcWrapper, $prefix)); + } + + /** + * Create an instance of the array cache driver. + * + * @return \Illuminate\Cache\ArrayStore + */ + protected function createArrayDriver() + { + return $this->repository(new ArrayStore); + } + + /** + * Create an instance of the file cache driver. + * + * @param array $config + * @return \Illuminate\Cache\FileStore + */ + protected function createFileDriver(array $config) + { + return $this->repository(new FileStore($this->app['files'], $config['path'])); + } + + /** + * Create an instance of the Memcached cache driver. + * + * @param array $config + * @return \Illuminate\Cache\MemcachedStore + */ + protected function createMemcachedDriver(array $config) + { + $prefix = $this->getPrefix($config); + + $memcached = $this->app['memcached.connector']->connect($config['servers']); + + return $this->repository(new MemcachedStore($memcached, $prefix)); + } + + /** + * Create an instance of the Null cache driver. + * + * @return \Illuminate\Cache\NullStore + */ + protected function createNullDriver() + { + return $this->repository(new NullStore); + } + + /** + * Create an instance of the WinCache cache driver. + * + * @param array $config + * @return \Illuminate\Cache\WinCacheStore + */ + protected function createWincacheDriver(array $config) + { + return $this->repository(new WinCacheStore($this->getPrefix($config))); + } + + /** + * Create an instance of the XCache cache driver. + * + * @param array $config + * @return \Illuminate\Cache\WinCacheStore + */ + protected function createXcacheDriver(array $config) + { + return $this->repository(new XCacheStore($this->getPrefix($config))); + } + + /** + * Create an instance of the Redis cache driver. + * + * @param array $config + * @return \Illuminate\Cache\RedisStore + */ + protected function createRedisDriver(array $config) + { + $redis = $this->app['redis']; + + $connection = Arr::get($config, 'connection', 'default'); + + return $this->repository(new RedisStore($redis, $this->getPrefix($config), $connection)); + } + + /** + * Create an instance of the database cache driver. + * + * @param array $config + * @return \Illuminate\Cache\DatabaseStore + */ + protected function createDatabaseDriver(array $config) + { + $connection = $this->app['db']->connection(Arr::get($config, 'connection')); + + return $this->repository( + new DatabaseStore( + $connection, $this->app['encrypter'], $config['table'], $this->getPrefix($config) + ) + ); + } + + /** + * Create a new cache repository with the given implementation. + * + * @param \Illuminate\Contracts\Cache\Store $store + * @return \Illuminate\Cache\Repository + */ + public function repository(Store $store) + { + $repository = new Repository($store); + + if ($this->app->bound('Illuminate\Contracts\Events\Dispatcher')) { + $repository->setEventDispatcher( + $this->app['Illuminate\Contracts\Events\Dispatcher'] + ); + } + + return $repository; + } + + /** + * Get the cache prefix. + * + * @param array $config + * @return string + */ + protected function getPrefix(array $config) + { + return Arr::get($config, 'prefix') ?: $this->app['config']['cache.prefix']; + } + + /** + * Get the cache connection configuration. + * + * @param string $name + * @return array + */ + protected function getConfig($name) + { + return $this->app['config']["cache.stores.{$name}"]; + } + + /** + * Get the default cache driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['cache.default']; + } + + /** + * Set the default cache driver name. + * + * @param string $name + * @return void + */ + public function setDefaultDriver($name) + { + $this->app['config']['cache.default'] = $name; + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * @return $this + */ + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback; + + return $this; + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->store(), $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php new file mode 100644 index 0000000..fe4801e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php @@ -0,0 +1,69 @@ +app->singleton('cache', function ($app) { + return new CacheManager($app); + }); + + $this->app->singleton('cache.store', function ($app) { + return $app['cache']->driver(); + }); + + $this->app->singleton('memcached.connector', function () { + return new MemcachedConnector; + }); + + $this->registerCommands(); + } + + /** + * Register the cache related console commands. + * + * @return void + */ + public function registerCommands() + { + $this->app->singleton('command.cache.clear', function ($app) { + return new ClearCommand($app['cache']); + }); + + $this->app->singleton('command.cache.table', function ($app) { + return new CacheTableCommand($app['files'], $app['composer']); + }); + + $this->commands('command.cache.clear', 'command.cache.table'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'cache', 'cache.store', 'memcached.connector', 'command.cache.clear', 'command.cache.table', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/Console/CacheTableCommand.php b/core/vendor/laravel/framework/src/Illuminate/Cache/Console/CacheTableCommand.php new file mode 100644 index 0000000..2e436d6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/Console/CacheTableCommand.php @@ -0,0 +1,81 @@ +files = $files; + $this->composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $fullPath = $this->createBaseMigration(); + + $this->files->put($fullPath, $this->files->get(__DIR__.'/stubs/cache.stub')); + + $this->info('Migration created successfully!'); + + $this->composer->dumpAutoloads(); + } + + /** + * Create a base migration file for the table. + * + * @return string + */ + protected function createBaseMigration() + { + $name = 'create_cache_table'; + + $path = $this->laravel->databasePath().'/migrations'; + + return $this->laravel['migration.creator']->create($name, $path); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php b/core/vendor/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php new file mode 100644 index 0000000..02ea032 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php @@ -0,0 +1,74 @@ +cache = $cache; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $storeName = $this->argument('store'); + + $this->laravel['events']->fire('cache:clearing', [$storeName]); + + $this->cache->store($storeName)->flush(); + + $this->laravel['events']->fire('cache:cleared', [$storeName]); + + $this->info('Application cache cleared!'); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['store', InputArgument::OPTIONAL, 'The name of the store you would like to clear.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/Console/stubs/cache.stub b/core/vendor/laravel/framework/src/Illuminate/Cache/Console/stubs/cache.stub new file mode 100644 index 0000000..c972a4f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/Console/stubs/cache.stub @@ -0,0 +1,31 @@ +string('key')->unique(); + $table->text('value'); + $table->integer('expiration'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('cache'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/DatabaseStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/DatabaseStore.php new file mode 100644 index 0000000..20f20c7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/DatabaseStore.php @@ -0,0 +1,260 @@ +table = $table; + $this->prefix = $prefix; + $this->encrypter = $encrypter; + $this->connection = $connection; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $prefixed = $this->prefix.$key; + + $cache = $this->table()->where('key', '=', $prefixed)->first(); + + // If we have a cache record we will check the expiration time against current + // time on the system and see if the record has expired. If it has, we will + // remove the records from the database table so it isn't returned again. + if (! is_null($cache)) { + if (is_array($cache)) { + $cache = (object) $cache; + } + + if (time() >= $cache->expiration) { + $this->forget($key); + + return; + } + + return $this->encrypter->decrypt($cache->value); + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $key = $this->prefix.$key; + + // All of the cached values in the database are encrypted in case this is used + // as a session data store by the consumer. We'll also calculate the expire + // time and place that on the table so we will check it on our retrieval. + $value = $this->encrypter->encrypt($value); + + $expiration = $this->getTime() + ($minutes * 60); + + try { + $this->table()->insert(compact('key', 'value', 'expiration')); + } catch (Exception $e) { + $this->table()->where('key', '=', $key)->update(compact('value', 'expiration')); + } + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function increment($key, $value = 1) + { + return $this->incrementOrDecrement($key, $value, function ($current, $value) { + return $current + $value; + }); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function decrement($key, $value = 1) + { + return $this->incrementOrDecrement($key, $value, function ($current, $value) { + return $current - $value; + }); + } + + /** + * Increment or decrement an item in the cache. + * + * @param string $key + * @param mixed $value + * @param \Closure $callback + * @return int|bool + */ + protected function incrementOrDecrement($key, $value, Closure $callback) + { + return $this->connection->transaction(function () use ($key, $value, $callback) { + $prefixed = $this->prefix.$key; + + $cache = $this->table()->where('key', $prefixed)->lockForUpdate()->first(); + + if (is_null($cache)) { + return false; + } + + $current = $this->encrypter->decrypt($cache->value); + $new = $callback((int) $current, $value); + + if (! is_numeric($current)) { + return false; + } + + $this->table()->where('key', $prefixed)->update([ + 'value' => $this->encrypter->encrypt($new), + ]); + + return $new; + }); + } + + /** + * Get the current system time. + * + * @return int + */ + protected function getTime() + { + return time(); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->put($key, $value, 5256000); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + $this->table()->where('key', '=', $this->prefix.$key)->delete(); + + return true; + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->table()->delete(); + } + + /** + * Get a query builder for the cache table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function table() + { + return $this->connection->table($this->table); + } + + /** + * Get the underlying database connection. + * + * @return \Illuminate\Database\ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Get the encrypter instance. + * + * @return \Illuminate\Contracts\Encryption\Encrypter + */ + public function getEncrypter() + { + return $this->encrypter; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/FileStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/FileStore.php new file mode 100644 index 0000000..05f3eda --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/FileStore.php @@ -0,0 +1,250 @@ +files = $files; + $this->directory = $directory; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + return Arr::get($this->getPayload($key), 'data'); + } + + /** + * Retrieve an item and expiry time from the cache by key. + * + * @param string $key + * @return array + */ + protected function getPayload($key) + { + $path = $this->path($key); + + // If the file doesn't exists, we obviously can't return the cache so we will + // just return null. Otherwise, we'll get the contents of the file and get + // the expiration UNIX timestamps from the start of the file's contents. + try { + $expire = substr($contents = $this->files->get($path), 0, 10); + } catch (Exception $e) { + return ['data' => null, 'time' => null]; + } + + // If the current time is greater than expiration timestamps we will delete + // the file and return null. This helps clean up the old files and keeps + // this directory much cleaner for us as old files aren't hanging out. + if (time() >= $expire) { + $this->forget($key); + + return ['data' => null, 'time' => null]; + } + + $data = unserialize(substr($contents, 10)); + + // Next, we'll extract the number of minutes that are remaining for a cache + // so that we can properly retain the time for things like the increment + // operation that may be performed on the cache. We'll round this out. + $time = ceil(($expire - time()) / 60); + + return compact('data', 'time'); + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $value = $this->expiration($minutes).serialize($value); + + $this->createCacheDirectory($path = $this->path($key)); + + $this->files->put($path, $value); + } + + /** + * Create the file cache directory if necessary. + * + * @param string $path + * @return void + */ + protected function createCacheDirectory($path) + { + if (! $this->files->exists(dirname($path))) { + $this->files->makeDirectory(dirname($path), 0777, true, true); + } + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function increment($key, $value = 1) + { + $raw = $this->getPayload($key); + + $int = ((int) $raw['data']) + $value; + + $this->put($key, $int, (int) $raw['time']); + + return $int; + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function decrement($key, $value = 1) + { + return $this->increment($key, $value * -1); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + $file = $this->path($key); + + if ($this->files->exists($file)) { + return $this->files->delete($file); + } + + return false; + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + if ($this->files->isDirectory($this->directory)) { + foreach ($this->files->directories($this->directory) as $directory) { + $this->files->deleteDirectory($directory); + } + } + } + + /** + * Get the full path for the given cache key. + * + * @param string $key + * @return string + */ + protected function path($key) + { + $parts = array_slice(str_split($hash = md5($key), 2), 0, 2); + + return $this->directory.'/'.implode('/', $parts).'/'.$hash; + } + + /** + * Get the expiration time based on the given minutes. + * + * @param int $minutes + * @return int + */ + protected function expiration($minutes) + { + $time = time() + ($minutes * 60); + + if ($minutes === 0 || $time > 9999999999) { + return 9999999999; + } + + return $time; + } + + /** + * Get the Filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + + /** + * Get the working directory of the cache. + * + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return ''; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php b/core/vendor/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php new file mode 100644 index 0000000..49f5cd8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php @@ -0,0 +1,53 @@ +getMemcached(); + + // For each server in the array, we'll just extract the configuration and add + // the server to the Memcached connection. Once we have added all of these + // servers we'll verify the connection is successful and return it back. + foreach ($servers as $server) { + $memcached->addServer( + $server['host'], $server['port'], $server['weight'] + ); + } + + $memcachedStatus = $memcached->getVersion(); + + if (! is_array($memcachedStatus)) { + throw new RuntimeException('No Memcached servers added.'); + } + + if (in_array('255.255.255', $memcachedStatus) && count(array_unique($memcachedStatus)) === 1) { + throw new RuntimeException('Could not establish Memcached connection.'); + } + + return $memcached; + } + + /** + * Get a new Memcached instance. + * + * @return \Memcached + */ + protected function getMemcached() + { + return new Memcached; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php new file mode 100644 index 0000000..2c2aee0 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php @@ -0,0 +1,164 @@ +setPrefix($prefix); + $this->memcached = $memcached; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $value = $this->memcached->get($this->prefix.$key); + + if ($this->memcached->getResultCode() == 0) { + return $value; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $this->memcached->set($this->prefix.$key, $value, $minutes * 60); + } + + /** + * Store an item in the cache if the key doesn't exist. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return bool + */ + public function add($key, $value, $minutes) + { + return $this->memcached->add($this->prefix.$key, $value, $minutes * 60); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function increment($key, $value = 1) + { + return $this->memcached->increment($this->prefix.$key, $value); + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function decrement($key, $value = 1) + { + return $this->memcached->decrement($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + return $this->memcached->delete($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->memcached->flush(); + } + + /** + * Get the underlying Memcached connection. + * + * @return \Memcached + */ + public function getMemcached() + { + return $this->memcached; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * Set the cache key prefix. + * + * @param string $prefix + * @return void + */ + public function setPrefix($prefix) + { + $this->prefix = ! empty($prefix) ? $prefix.':' : ''; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/NullStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/NullStore.php new file mode 100644 index 0000000..46aed16 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/NullStore.php @@ -0,0 +1,106 @@ +cache = $cache; + } + + /** + * Determine if the given key has been "accessed" too many times. + * + * @param string $key + * @param int $maxAttempts + * @param int $decayMinutes + * @return bool + */ + public function tooManyAttempts($key, $maxAttempts, $decayMinutes = 1) + { + $lockedOut = $this->cache->has($key.':lockout'); + + if ($this->attempts($key) > $maxAttempts || $lockedOut) { + if (! $lockedOut) { + $this->cache->add($key.':lockout', time() + ($decayMinutes * 60), $decayMinutes); + } + + return true; + } + + return false; + } + + /** + * Increment the counter for a given key for a given decay time. + * + * @param string $key + * @param int $decayMinutes + * @return int + */ + public function hit($key, $decayMinutes = 1) + { + $this->cache->add($key, 1, $decayMinutes); + + return (int) $this->cache->increment($key); + } + + /** + * Get the number of attempts for the given key. + * + * @param string $key + * @return mixed + */ + public function attempts($key) + { + return $this->cache->get($key, 0); + } + + /** + * Clear the hits and lockout for the given key. + * + * @param string $key + * @return void + */ + public function clear($key) + { + $this->cache->forget($key); + + $this->cache->forget($key.':lockout'); + } + + /** + * Get the number of seconds until the "key" is accessible again. + * + * @param string $key + * @return int + */ + public function availableIn($key) + { + return $this->cache->get($key.':lockout') - time(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php new file mode 100644 index 0000000..aeea3f4 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php @@ -0,0 +1,197 @@ +redis = $redis; + $this->setPrefix($prefix); + $this->connection = $connection; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + if (! is_null($value = $this->connection()->get($this->prefix.$key))) { + return is_numeric($value) ? $value : unserialize($value); + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $value = is_numeric($value) ? $value : serialize($value); + + $minutes = max(1, $minutes); + + $this->connection()->setex($this->prefix.$key, $minutes * 60, $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function increment($key, $value = 1) + { + return $this->connection()->incrby($this->prefix.$key, $value); + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function decrement($key, $value = 1) + { + return $this->connection()->decrby($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $value = is_numeric($value) ? $value : serialize($value); + + $this->connection()->set($this->prefix.$key, $value); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + return (bool) $this->connection()->del($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->connection()->flushdb(); + } + + /** + * Begin executing a new tags operation. + * + * @param array|mixed $names + * @return \Illuminate\Cache\RedisTaggedCache + */ + public function tags($names) + { + return new RedisTaggedCache($this, new TagSet($this, is_array($names) ? $names : func_get_args())); + } + + /** + * Get the Redis connection instance. + * + * @return \Predis\ClientInterface + */ + public function connection() + { + return $this->redis->connection($this->connection); + } + + /** + * Set the connection name to be used. + * + * @param string $connection + * @return void + */ + public function setConnection($connection) + { + $this->connection = $connection; + } + + /** + * Get the Redis database instance. + * + * @return \Illuminate\Redis\Database + */ + public function getRedis() + { + return $this->redis; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * Set the cache key prefix. + * + * @param string $prefix + * @return void + */ + public function setPrefix($prefix) + { + $this->prefix = ! empty($prefix) ? $prefix.':' : ''; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/RedisTaggedCache.php b/core/vendor/laravel/framework/src/Illuminate/Cache/RedisTaggedCache.php new file mode 100644 index 0000000..285eaf9 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/RedisTaggedCache.php @@ -0,0 +1,164 @@ +pushStandardKeys($this->tags->getNamespace(), $key); + + parent::put($key, $value, $minutes); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->pushForeverKeys($this->tags->getNamespace(), $key); + + parent::forever($key, $value); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->deleteForeverKeys(); + $this->deleteStandardKeys(); + + parent::flush(); + } + + /** + * Store standard key references into store. + * + * @param string $namespace + * @param string $key + * @return void + */ + protected function pushStandardKeys($namespace, $key) + { + $this->pushKeys($namespace, $key, self::REFERENCE_KEY_STANDARD); + } + + /** + * Store forever key references into store. + * + * @param string $namespace + * @param string $key + * @return void + */ + protected function pushForeverKeys($namespace, $key) + { + $this->pushKeys($namespace, $key, self::REFERENCE_KEY_FOREVER); + } + + /** + * Store a reference to the cache key against the reference key. + * + * @param string $namespace + * @param string $key + * @param string $reference + * @return void + */ + protected function pushKeys($namespace, $key, $reference) + { + $fullKey = $this->getPrefix().sha1($namespace).':'.$key; + + foreach (explode('|', $namespace) as $segment) { + $this->store->connection()->sadd($this->referenceKey($segment, $reference), $fullKey); + } + } + + /** + * Delete all of the items that were stored forever. + * + * @return void + */ + protected function deleteForeverKeys() + { + $this->deleteKeysByReference(self::REFERENCE_KEY_FOREVER); + } + + /** + * Delete all standard items. + * + * @return void + */ + protected function deleteStandardKeys() + { + $this->deleteKeysByReference(self::REFERENCE_KEY_STANDARD); + } + + /** + * Find and delete all of the items that were stored against a reference. + * + * @param string $reference + * @return void + */ + protected function deleteKeysByReference($reference) + { + foreach (explode('|', $this->tags->getNamespace()) as $segment) { + $this->deleteValues($segment = $this->referenceKey($segment, $reference)); + + $this->store->connection()->del($segment); + } + } + + /** + * Delete item keys that have been stored against a reference. + * + * @param string $referenceKey + * @return void + */ + protected function deleteValues($referenceKey) + { + $values = array_unique($this->store->connection()->smembers($referenceKey)); + + if (count($values) > 0) { + call_user_func_array([$this->store->connection(), 'del'], $values); + } + } + + /** + * Get the reference key for the segment. + * + * @param string $segment + * @param string $suffix + * @return string + */ + protected function referenceKey($segment, $suffix) + { + return $this->getPrefix().$segment.':'.$suffix; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/Repository.php b/core/vendor/laravel/framework/src/Illuminate/Cache/Repository.php new file mode 100644 index 0000000..fb76105 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/Repository.php @@ -0,0 +1,426 @@ +store = $store; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return void + */ + public function setEventDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Fire an event for this cache instance. + * + * @param string $event + * @param array $payload + * @return void + */ + protected function fireCacheEvent($event, $payload) + { + if (isset($this->events)) { + $this->events->fire('cache.'.$event, $payload); + } + } + + /** + * Determine if an item exists in the cache. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return ! is_null($this->get($key)); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + $value = $this->store->get($this->itemKey($key)); + + if (is_null($value)) { + $this->fireCacheEvent('missed', [$key]); + + $value = value($default); + } else { + $this->fireCacheEvent('hit', [$key, $value]); + } + + return $value; + } + + /** + * Retrieve an item from the cache and delete it. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function pull($key, $default = null) + { + $value = $this->get($key, $default); + + $this->forget($key); + + return $value; + } + + /** + * Store an item in the cache. + * + * @param string $key + * @param mixed $value + * @param \DateTime|int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $minutes = $this->getMinutes($minutes); + + if (! is_null($minutes)) { + $this->store->put($this->itemKey($key), $value, $minutes); + + $this->fireCacheEvent('write', [$key, $value, $minutes]); + } + } + + /** + * Store an item in the cache if the key does not exist. + * + * @param string $key + * @param mixed $value + * @param \DateTime|int $minutes + * @return bool + */ + public function add($key, $value, $minutes) + { + $minutes = $this->getMinutes($minutes); + + if (is_null($minutes)) { + return false; + } + + if (method_exists($this->store, 'add')) { + return $this->store->add($this->itemKey($key), $value, $minutes); + } + + if (is_null($this->get($key))) { + $this->put($key, $value, $minutes); + + return true; + } + + return false; + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->store->forever($this->itemKey($key), $value); + + $this->fireCacheEvent('write', [$key, $value, 0]); + } + + /** + * Get an item from the cache, or store the default value. + * + * @param string $key + * @param \DateTime|int $minutes + * @param \Closure $callback + * @return mixed + */ + public function remember($key, $minutes, Closure $callback) + { + // If the item exists in the cache we will just return this immediately + // otherwise we will execute the given Closure and cache the result + // of that execution for the given number of minutes in storage. + if (! is_null($value = $this->get($key))) { + return $value; + } + + $this->put($key, $value = $callback(), $minutes); + + return $value; + } + + /** + * Get an item from the cache, or store the default value forever. + * + * @param string $key + * @param \Closure $callback + * @return mixed + */ + public function sear($key, Closure $callback) + { + return $this->rememberForever($key, $callback); + } + + /** + * Get an item from the cache, or store the default value forever. + * + * @param string $key + * @param \Closure $callback + * @return mixed + */ + public function rememberForever($key, Closure $callback) + { + // If the item exists in the cache we will just return this immediately + // otherwise we will execute the given Closure and cache the result + // of that execution for the given number of minutes. It's easy. + if (! is_null($value = $this->get($key))) { + return $value; + } + + $this->forever($key, $value = $callback()); + + return $value; + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + $success = $this->store->forget($this->itemKey($key)); + + $this->fireCacheEvent('delete', [$key]); + + return $success; + } + + /** + * Begin executing a new tags operation if the store supports it. + * + * @param string $name + * @return \Illuminate\Cache\TaggedCache + * + * @deprecated since version 5.1. Use tags instead. + */ + public function section($name) + { + return $this->tags($name); + } + + /** + * Begin executing a new tags operation if the store supports it. + * + * @param array|mixed $names + * @return \Illuminate\Cache\TaggedCache + * + * @throws \BadMethodCallException + */ + public function tags($names) + { + if (method_exists($this->store, 'tags')) { + $taggedCache = $this->store->tags($names); + + if (! is_null($this->events)) { + $taggedCache->setEventDispatcher($this->events); + } + + $taggedCache->setDefaultCacheTime($this->default); + + return $taggedCache; + } + + throw new BadMethodCallException('This cache store does not support tagging.'); + } + + /** + * Format the key for a cache item. + * + * @param string $key + * @return string + */ + protected function itemKey($key) + { + return $key; + } + + /** + * Get the default cache time. + * + * @return int + */ + public function getDefaultCacheTime() + { + return $this->default; + } + + /** + * Set the default cache time in minutes. + * + * @param int $minutes + * @return void + */ + public function setDefaultCacheTime($minutes) + { + $this->default = $minutes; + } + + /** + * Get the cache store implementation. + * + * @return \Illuminate\Contracts\Cache\Store + */ + public function getStore() + { + return $this->store; + } + + /** + * Determine if a cached value exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Store an item in the cache for the default time. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->put($key, $value, $this->default); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + $this->forget($key); + } + + /** + * Calculate the number of minutes with the given duration. + * + * @param \DateTime|int $duration + * @return int|null + */ + protected function getMinutes($duration) + { + if ($duration instanceof DateTime) { + $fromNow = Carbon::now()->diffInMinutes(Carbon::instance($duration), false); + + return $fromNow > 0 ? $fromNow : null; + } + + return is_string($duration) ? (int) $duration : $duration; + } + + /** + * Handle dynamic calls into macros or pass missing methods to the store. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + return call_user_func_array([$this->store, $method], $parameters); + } + + /** + * Clone cache repository instance. + * + * @return void + */ + public function __clone() + { + $this->store = clone $this->store; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/TagSet.php b/core/vendor/laravel/framework/src/Illuminate/Cache/TagSet.php new file mode 100644 index 0000000..3f15f23 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/TagSet.php @@ -0,0 +1,110 @@ +store = $store; + $this->names = $names; + } + + /** + * Reset all tags in the set. + * + * @return void + */ + public function reset() + { + array_walk($this->names, [$this, 'resetTag']); + } + + /** + * Get the unique tag identifier for a given tag. + * + * @param string $name + * @return string + */ + public function tagId($name) + { + return $this->store->get($this->tagKey($name)) ?: $this->resetTag($name); + } + + /** + * Get an array of tag identifiers for all of the tags in the set. + * + * @return array + */ + protected function tagIds() + { + return array_map([$this, 'tagId'], $this->names); + } + + /** + * Get a unique namespace that changes when any of the tags are flushed. + * + * @return string + */ + public function getNamespace() + { + return implode('|', $this->tagIds()); + } + + /** + * Reset the tag and return the new tag identifier. + * + * @param string $name + * @return string + */ + public function resetTag($name) + { + $this->store->forever($this->tagKey($name), $id = str_replace('.', '', uniqid('', true))); + + return $id; + } + + /** + * Get the tag identifier key for a given tag. + * + * @param string $name + * @return string + */ + public function tagKey($name) + { + return 'tag:'.$name.':key'; + } + + /** + * Get all of the tag names in the set. + * + * @return array + */ + public function getNames() + { + return $this->names; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/TaggableStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/TaggableStore.php new file mode 100644 index 0000000..4479fcc --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/TaggableStore.php @@ -0,0 +1,30 @@ +tags($name); + } + + /** + * Begin executing a new tags operation. + * + * @param array|mixed $names + * @return \Illuminate\Cache\TaggedCache + */ + public function tags($names) + { + return new TaggedCache($this, new TagSet($this, is_array($names) ? $names : func_get_args())); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/TaggedCache.php b/core/vendor/laravel/framework/src/Illuminate/Cache/TaggedCache.php new file mode 100644 index 0000000..c1cfec8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/TaggedCache.php @@ -0,0 +1,92 @@ +tags = $tags; + } + + /** + * {@inheritdoc} + */ + protected function fireCacheEvent($event, $payload) + { + $payload[] = $this->tags->getNames(); + + parent::fireCacheEvent($event, $payload); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function increment($key, $value = 1) + { + $this->store->increment($this->itemKey($key), $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function decrement($key, $value = 1) + { + $this->store->decrement($this->itemKey($key), $value); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->tags->reset(); + } + + /** + * {@inheritdoc} + */ + protected function itemKey($key) + { + return $this->taggedItemKey($key); + } + + /** + * Get a fully qualified key for a tagged item. + * + * @param string $key + * @return string + */ + public function taggedItemKey($key) + { + return sha1($this->tags->getNamespace()).':'.$key; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/WinCacheStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/WinCacheStore.php new file mode 100644 index 0000000..6dc3fc7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/WinCacheStore.php @@ -0,0 +1,121 @@ +prefix = $prefix; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $value = wincache_ucache_get($this->prefix.$key); + + if ($value !== false) { + return $value; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + wincache_ucache_set($this->prefix.$key, $value, $minutes * 60); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function increment($key, $value = 1) + { + return wincache_ucache_inc($this->prefix.$key, $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function decrement($key, $value = 1) + { + return wincache_ucache_dec($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + return wincache_ucache_delete($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + wincache_ucache_clear(); + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/XCacheStore.php b/core/vendor/laravel/framework/src/Illuminate/Cache/XCacheStore.php new file mode 100644 index 0000000..7ef64cf --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/XCacheStore.php @@ -0,0 +1,121 @@ +prefix = $prefix; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $value = xcache_get($this->prefix.$key); + + if (isset($value)) { + return $value; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + xcache_set($this->prefix.$key, $value, $minutes * 60); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function increment($key, $value = 1) + { + return xcache_inc($this->prefix.$key, $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function decrement($key, $value = 1) + { + return xcache_dec($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + return $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + return xcache_unset($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + xcache_clear_cache(XC_TYPE_VAR); + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cache/composer.json b/core/vendor/laravel/framework/src/Illuminate/Cache/composer.json new file mode 100644 index 0000000..51ee8ac --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cache/composer.json @@ -0,0 +1,38 @@ +{ + "name": "illuminate/cache", + "description": "The Illuminate Cache package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "nesbot/carbon": "~1.19" + }, + "autoload": { + "psr-4": { + "Illuminate\\Cache\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "illuminate/database": "Required to use the database cache driver (5.1.*).", + "illuminate/filesystem": "Required to use the file cache driver (5.1.*).", + "illuminate/redis": "Required to use the redis cache driver (5.1.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Config/Repository.php b/core/vendor/laravel/framework/src/Illuminate/Config/Repository.php new file mode 100644 index 0000000..184a04a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Config/Repository.php @@ -0,0 +1,156 @@ +items = $items; + } + + /** + * Determine if the given configuration value exists. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return Arr::has($this->items, $key); + } + + /** + * Get the specified configuration value. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + return Arr::get($this->items, $key, $default); + } + + /** + * Set a given configuration value. + * + * @param array|string $key + * @param mixed $value + * @return void + */ + public function set($key, $value = null) + { + if (is_array($key)) { + foreach ($key as $innerKey => $innerValue) { + Arr::set($this->items, $innerKey, $innerValue); + } + } else { + Arr::set($this->items, $key, $value); + } + } + + /** + * Prepend a value onto an array configuration value. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function prepend($key, $value) + { + $array = $this->get($key); + + array_unshift($array, $value); + + $this->set($key, $array); + } + + /** + * Push a value onto an array configuration value. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function push($key, $value) + { + $array = $this->get($key); + + $array[] = $value; + + $this->set($key, $array); + } + + /** + * Get all of the configuration items for the application. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Determine if the given configuration option exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * Get a configuration option. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Set a configuration option. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->set($key, $value); + } + + /** + * Unset a configuration option. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + $this->set($key, null); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Config/composer.json b/core/vendor/laravel/framework/src/Illuminate/Config/composer.json new file mode 100644 index 0000000..b4c4f23 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Config/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/config", + "description": "The Illuminate Config package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/filesystem": "5.1.*", + "illuminate/support": "5.1.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Config\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/AppNamespaceDetectorTrait.php b/core/vendor/laravel/framework/src/Illuminate/Console/AppNamespaceDetectorTrait.php new file mode 100644 index 0000000..4feede9 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/AppNamespaceDetectorTrait.php @@ -0,0 +1,18 @@ +getNamespace(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/Application.php b/core/vendor/laravel/framework/src/Illuminate/Console/Application.php new file mode 100644 index 0000000..ee45c0a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/Application.php @@ -0,0 +1,172 @@ +laravel = $laravel; + $this->setAutoExit(false); + $this->setCatchExceptions(false); + + $events->fire('artisan.start', [$this]); + } + + /** + * Run an Artisan console command by name. + * + * @param string $command + * @param array $parameters + * @return int + */ + public function call($command, array $parameters = []) + { + $parameters = collect($parameters)->prepend($command); + + $this->lastOutput = new BufferedOutput; + + $this->setCatchExceptions(false); + + $result = $this->run(new ArrayInput($parameters->toArray()), $this->lastOutput); + + $this->setCatchExceptions(true); + + return $result; + } + + /** + * Get the output for the last run command. + * + * @return string + */ + public function output() + { + return $this->lastOutput ? $this->lastOutput->fetch() : ''; + } + + /** + * Add a command to the console. + * + * @param \Symfony\Component\Console\Command\Command $command + * @return \Symfony\Component\Console\Command\Command + */ + public function add(SymfonyCommand $command) + { + if ($command instanceof Command) { + $command->setLaravel($this->laravel); + } + + return $this->addToParent($command); + } + + /** + * Add the command to the parent instance. + * + * @param \Symfony\Component\Console\Command\Command $command + * @return \Symfony\Component\Console\Command\Command + */ + protected function addToParent(SymfonyCommand $command) + { + return parent::add($command); + } + + /** + * Add a command, resolving through the application. + * + * @param string $command + * @return \Symfony\Component\Console\Command\Command + */ + public function resolve($command) + { + return $this->add($this->laravel->make($command)); + } + + /** + * Resolve an array of commands through the application. + * + * @param array|mixed $commands + * @return $this + */ + public function resolveCommands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + + foreach ($commands as $command) { + $this->resolve($command); + } + + return $this; + } + + /** + * Get the default input definitions for the applications. + * + * This is used to add the --env option to every available command. + * + * @return \Symfony\Component\Console\Input\InputDefinition + */ + protected function getDefaultInputDefinition() + { + $definition = parent::getDefaultInputDefinition(); + + $definition->addOption($this->getEnvironmentOption()); + + return $definition; + } + + /** + * Get the global environment option for the definition. + * + * @return \Symfony\Component\Console\Input\InputOption + */ + protected function getEnvironmentOption() + { + $message = 'The environment the command should run under.'; + + return new InputOption('--env', null, InputOption::VALUE_OPTIONAL, $message); + } + + /** + * Get the Laravel application instance. + * + * @return \Illuminate\Contracts\Foundation\Application + */ + public function getLaravel() + { + return $this->laravel; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/Command.php b/core/vendor/laravel/framework/src/Illuminate/Console/Command.php new file mode 100644 index 0000000..47b79d0 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/Command.php @@ -0,0 +1,445 @@ +signature)) { + $this->configureUsingFluentDefinition(); + } else { + parent::__construct($this->name); + } + + $this->setDescription($this->description); + + if (! isset($this->signature)) { + $this->specifyParameters(); + } + } + + /** + * Configure the console command using a fluent definition. + * + * @return void + */ + protected function configureUsingFluentDefinition() + { + list($name, $arguments, $options) = Parser::parse($this->signature); + + parent::__construct($name); + + foreach ($arguments as $argument) { + $this->getDefinition()->addArgument($argument); + } + + foreach ($options as $option) { + $this->getDefinition()->addOption($option); + } + } + + /** + * Specify the arguments and options on the command. + * + * @return void + */ + protected function specifyParameters() + { + // We will loop through all of the arguments and options for the command and + // set them all on the base command instance. This specifies what can get + // passed into these commands as "parameters" to control the execution. + foreach ($this->getArguments() as $arguments) { + call_user_func_array([$this, 'addArgument'], $arguments); + } + + foreach ($this->getOptions() as $options) { + call_user_func_array([$this, 'addOption'], $options); + } + } + + /** + * Run the console command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return int + */ + public function run(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + + $this->output = new OutputStyle($input, $output); + + return parent::run($input, $output); + } + + /** + * Execute the console command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return mixed + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $method = method_exists($this, 'handle') ? 'handle' : 'fire'; + + return $this->laravel->call([$this, $method]); + } + + /** + * Call another console command. + * + * @param string $command + * @param array $arguments + * @return int + */ + public function call($command, array $arguments = []) + { + $instance = $this->getApplication()->find($command); + + $arguments['command'] = $command; + + return $instance->run(new ArrayInput($arguments), $this->output); + } + + /** + * Call another console command silently. + * + * @param string $command + * @param array $arguments + * @return int + */ + public function callSilent($command, array $arguments = []) + { + $instance = $this->getApplication()->find($command); + + $arguments['command'] = $command; + + return $instance->run(new ArrayInput($arguments), new NullOutput); + } + + /** + * Get the value of a command argument. + * + * @param string $key + * @return string|array + */ + public function argument($key = null) + { + if (is_null($key)) { + return $this->input->getArguments(); + } + + return $this->input->getArgument($key); + } + + /** + * Get the value of a command option. + * + * @param string $key + * @return string|array + */ + public function option($key = null) + { + if (is_null($key)) { + return $this->input->getOptions(); + } + + return $this->input->getOption($key); + } + + /** + * Confirm a question with the user. + * + * @param string $question + * @param bool $default + * @return bool + */ + public function confirm($question, $default = false) + { + return $this->output->confirm($question, $default); + } + + /** + * Prompt the user for input. + * + * @param string $question + * @param string $default + * @return string + */ + public function ask($question, $default = null) + { + return $this->output->ask($question, $default); + } + + /** + * Prompt the user for input with auto completion. + * + * @param string $question + * @param array $choices + * @param string $default + * @return string + */ + public function anticipate($question, array $choices, $default = null) + { + return $this->askWithCompletion($question, $choices, $default); + } + + /** + * Prompt the user for input with auto completion. + * + * @param string $question + * @param array $choices + * @param string $default + * @return string + */ + public function askWithCompletion($question, array $choices, $default = null) + { + $question = new Question($question, $default); + + $question->setAutocompleterValues($choices); + + return $this->output->askQuestion($question); + } + + /** + * Prompt the user for input but hide the answer from the console. + * + * @param string $question + * @param bool $fallback + * @return string + */ + public function secret($question, $fallback = true) + { + $question = new Question($question); + + $question->setHidden(true)->setHiddenFallback($fallback); + + return $this->output->askQuestion($question); + } + + /** + * Give the user a single choice from an array of answers. + * + * @param string $question + * @param array $choices + * @param string $default + * @param mixed $attempts + * @param bool $multiple + * @return string + */ + public function choice($question, array $choices, $default = null, $attempts = null, $multiple = null) + { + $question = new ChoiceQuestion($question, $choices, $default); + + $question->setMaxAttempts($attempts)->setMultiselect($multiple); + + return $this->output->askQuestion($question); + } + + /** + * Format input to textual table. + * + * @param array $headers + * @param \Illuminate\Contracts\Support\Arrayable|array $rows + * @param string $style + * @return void + */ + public function table(array $headers, $rows, $style = 'default') + { + $table = new Table($this->output); + + if ($rows instanceof Arrayable) { + $rows = $rows->toArray(); + } + + $table->setHeaders($headers)->setRows($rows)->setStyle($style)->render(); + } + + /** + * Write a string as information output. + * + * @param string $string + * @return void + */ + public function info($string) + { + $this->output->writeln("$string"); + } + + /** + * Write a string as standard output. + * + * @param string $string + * @return void + */ + public function line($string) + { + $this->output->writeln($string); + } + + /** + * Write a string as comment output. + * + * @param string $string + * @return void + */ + public function comment($string) + { + $this->output->writeln("$string"); + } + + /** + * Write a string as question output. + * + * @param string $string + * @return void + */ + public function question($string) + { + $this->output->writeln("$string"); + } + + /** + * Write a string as error output. + * + * @param string $string + * @return void + */ + public function error($string) + { + $this->output->writeln("$string"); + } + + /** + * Write a string as warning output. + * + * @param string $string + * @return void + */ + public function warn($string) + { + if (! $this->output->getFormatter()->hasStyle('warning')) { + $style = new OutputFormatterStyle('yellow'); + + $this->output->getFormatter()->setStyle('warning', $style); + } + + $this->output->writeln("$string"); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return []; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return []; + } + + /** + * Get the output implementation. + * + * @return \Symfony\Component\Console\Output\OutputInterface + */ + public function getOutput() + { + return $this->output; + } + + /** + * Get the Laravel application instance. + * + * @return \Illuminate\Contracts\Foundation\Application + */ + public function getLaravel() + { + return $this->laravel; + } + + /** + * Set the Laravel application instance. + * + * @param \Illuminate\Contracts\Foundation\Application $laravel + * @return void + */ + public function setLaravel(LaravelApplication $laravel) + { + $this->laravel = $laravel; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/ConfirmableTrait.php b/core/vendor/laravel/framework/src/Illuminate/Console/ConfirmableTrait.php new file mode 100644 index 0000000..30f4402 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/ConfirmableTrait.php @@ -0,0 +1,57 @@ +getDefaultConfirmCallback() : $callback; + + $shouldConfirm = $callback instanceof Closure ? call_user_func($callback) : $callback; + + if ($shouldConfirm) { + if ($this->option('force')) { + return true; + } + + $this->comment(str_repeat('*', strlen($warning) + 12)); + $this->comment('* '.$warning.' *'); + $this->comment(str_repeat('*', strlen($warning) + 12)); + $this->output->writeln(''); + + $confirmed = $this->confirm('Do you really wish to run this command?'); + + if (! $confirmed) { + $this->comment('Command Cancelled!'); + + return false; + } + } + + return true; + } + + /** + * Get the default confirmation callback. + * + * @return \Closure + */ + protected function getDefaultConfirmCallback() + { + return function () { + return $this->getLaravel()->environment() == 'production'; + }; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/GeneratorCommand.php b/core/vendor/laravel/framework/src/Illuminate/Console/GeneratorCommand.php new file mode 100644 index 0000000..65203f9 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/GeneratorCommand.php @@ -0,0 +1,219 @@ +files = $files; + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + abstract protected function getStub(); + + /** + * Execute the console command. + * + * @return bool|null + */ + public function fire() + { + $name = $this->parseName($this->getNameInput()); + + $path = $this->getPath($name); + + if ($this->alreadyExists($this->getNameInput())) { + $this->error($this->type.' already exists!'); + + return false; + } + + $this->makeDirectory($path); + + $this->files->put($path, $this->buildClass($name)); + + $this->info($this->type.' created successfully.'); + } + + /** + * Determine if the class already exists. + * + * @param string $rawName + * @return bool + */ + protected function alreadyExists($rawName) + { + $name = $this->parseName($rawName); + + return $this->files->exists($this->getPath($name)); + } + + /** + * Get the destination class path. + * + * @param string $name + * @return string + */ + protected function getPath($name) + { + $name = str_replace($this->laravel->getNamespace(), '', $name); + + return $this->laravel['path'].'/'.str_replace('\\', '/', $name).'.php'; + } + + /** + * Parse the name and format according to the root namespace. + * + * @param string $name + * @return string + */ + protected function parseName($name) + { + $rootNamespace = $this->laravel->getNamespace(); + + if (Str::startsWith($name, $rootNamespace)) { + return $name; + } + + if (Str::contains($name, '/')) { + $name = str_replace('/', '\\', $name); + } + + return $this->parseName($this->getDefaultNamespace(trim($rootNamespace, '\\')).'\\'.$name); + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace; + } + + /** + * Build the directory for the class if necessary. + * + * @param string $path + * @return string + */ + protected function makeDirectory($path) + { + if (! $this->files->isDirectory(dirname($path))) { + $this->files->makeDirectory(dirname($path), 0777, true, true); + } + } + + /** + * Build the class with the given name. + * + * @param string $name + * @return string + */ + protected function buildClass($name) + { + $stub = $this->files->get($this->getStub()); + + return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name); + } + + /** + * Replace the namespace for the given stub. + * + * @param string $stub + * @param string $name + * @return $this + */ + protected function replaceNamespace(&$stub, $name) + { + $stub = str_replace( + 'DummyNamespace', $this->getNamespace($name), $stub + ); + + $stub = str_replace( + 'DummyRootNamespace', $this->laravel->getNamespace(), $stub + ); + + return $this; + } + + /** + * Get the full namespace name for a given class. + * + * @param string $name + * @return string + */ + protected function getNamespace($name) + { + return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + } + + /** + * Replace the class name for the given stub. + * + * @param string $stub + * @param string $name + * @return string + */ + protected function replaceClass($stub, $name) + { + $class = str_replace($this->getNamespace($name).'\\', '', $name); + + return str_replace('DummyClass', $class, $stub); + } + + /** + * Get the desired class name from the input. + * + * @return string + */ + protected function getNameInput() + { + return trim($this->argument('name')); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['name', InputArgument::REQUIRED, 'The name of the class'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/OutputStyle.php b/core/vendor/laravel/framework/src/Illuminate/Console/OutputStyle.php new file mode 100644 index 0000000..925e66d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/OutputStyle.php @@ -0,0 +1,71 @@ +output = $output; + + parent::__construct($input, $output); + } + + /** + * Returns whether verbosity is quiet (-q). + * + * @return bool + */ + public function isQuiet() + { + return $this->output->isQuiet(); + } + + /** + * Returns whether verbosity is verbose (-v). + * + * @return bool + */ + public function isVerbose() + { + return $this->output->isVerbose(); + } + + /** + * Returns whether verbosity is very verbose (-vv). + * + * @return bool + */ + public function isVeryVerbose() + { + return $this->output->isVeryVerbose(); + } + + /** + * Returns whether verbosity is debug (-vvv). + * + * @return bool + */ + public function isDebug() + { + return $this->output->isDebug(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/Parser.php b/core/vendor/laravel/framework/src/Illuminate/Console/Parser.php new file mode 100644 index 0000000..babba0e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/Parser.php @@ -0,0 +1,134 @@ +commands('Illuminate\Console\Scheduling\ScheduleRunCommand'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'Illuminate\Console\Scheduling\ScheduleRunCommand', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/CallbackEvent.php b/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/CallbackEvent.php new file mode 100644 index 0000000..494f0e8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/CallbackEvent.php @@ -0,0 +1,122 @@ +callback = $callback; + $this->parameters = $parameters; + + if (! is_string($this->callback) && ! is_callable($this->callback)) { + throw new InvalidArgumentException( + 'Invalid scheduled callback event. Must be string or callable.' + ); + } + } + + /** + * Run the given event. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return mixed + * + * @throws \Exception + */ + public function run(Container $container) + { + if ($this->description) { + touch($this->mutexPath()); + } + + try { + $response = $container->call($this->callback, $this->parameters); + } finally { + $this->removeMutex(); + } + + parent::callAfterCallbacks($container); + + return $response; + } + + /** + * Remove the mutex file from disk. + * + * @return void + */ + protected function removeMutex() + { + if ($this->description) { + @unlink($this->mutexPath()); + } + } + + /** + * Do not allow the event to overlap each other. + * + * @return $this + */ + public function withoutOverlapping() + { + if (! isset($this->description)) { + throw new LogicException( + "A scheduled event name is required to prevent overlapping. Use the 'name' method before 'withoutOverlapping'." + ); + } + + return $this->skip(function () { + return file_exists($this->mutexPath()); + }); + } + + /** + * Get the mutex path for the scheduled command. + * + * @return string + */ + protected function mutexPath() + { + return storage_path('framework/schedule-'.md5($this->description)); + } + + /** + * Get the summary of the event for display. + * + * @return string + */ + public function getSummaryForDisplay() + { + if (is_string($this->description)) { + return $this->description; + } + + return is_string($this->callback) ? $this->callback : 'Closure'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Event.php b/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Event.php new file mode 100644 index 0000000..6fee7ed --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Event.php @@ -0,0 +1,859 @@ +command = $command; + $this->output = $this->getDefaultOutput(); + } + + /** + * Get the default output depending on the OS. + * + * @return string + */ + protected function getDefaultOutput() + { + return (DIRECTORY_SEPARATOR == '\\') ? 'NUL' : '/dev/null'; + } + + /** + * Run the given event. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public function run(Container $container) + { + if (count($this->afterCallbacks) > 0 || count($this->beforeCallbacks) > 0) { + $this->runCommandInForeground($container); + } else { + $this->runCommandInBackground(); + } + } + + /** + * Run the command in the background using exec. + * + * @return void + */ + protected function runCommandInBackground() + { + chdir(base_path()); + + exec($this->buildCommand()); + } + + /** + * Run the command in the foreground. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + protected function runCommandInForeground(Container $container) + { + $this->callBeforeCallbacks($container); + + (new Process( + trim($this->buildCommand(), '& '), base_path(), null, null, null + ))->run(); + + $this->callAfterCallbacks($container); + } + + /** + * Call all of the "before" callbacks for the event. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + protected function callBeforeCallbacks(Container $container) + { + foreach ($this->beforeCallbacks as $callback) { + $container->call($callback); + } + } + + /** + * Call all of the "after" callbacks for the event. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + protected function callAfterCallbacks(Container $container) + { + foreach ($this->afterCallbacks as $callback) { + $container->call($callback); + } + } + + /** + * Build the command string. + * + * @return string + */ + public function buildCommand() + { + $output = ProcessUtils::escapeArgument($this->output); + + $redirect = $this->shouldAppendOutput ? ' >> ' : ' > '; + + if ($this->withoutOverlapping) { + if (windows_os()) { + $command = '(echo \'\' > "'.$this->mutexPath().'" & '.$this->command.' & del "'.$this->mutexPath().'")'.$redirect.$output.' 2>&1 &'; + } else { + $command = '(touch '.$this->mutexPath().'; '.$this->command.'; rm '.$this->mutexPath().')'.$redirect.$output.' 2>&1 &'; + } + } else { + $command = $this->command.$redirect.$output.' 2>&1 &'; + } + + return $this->user && ! windows_os() ? 'sudo -u '.$this->user.' -- sh -c \''.$command.'\'' : $command; + } + + /** + * Get the mutex path for the scheduled command. + * + * @return string + */ + protected function mutexPath() + { + return storage_path('framework/schedule-'.md5($this->expression.$this->command)); + } + + /** + * Determine if the given event should run based on the Cron expression. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return bool + */ + public function isDue(Application $app) + { + if (! $this->runsInMaintenanceMode() && $app->isDownForMaintenance()) { + return false; + } + + return $this->expressionPasses() && + $this->filtersPass($app) && + $this->runsInEnvironment($app->environment()); + } + + /** + * Determine if the Cron expression passes. + * + * @return bool + */ + protected function expressionPasses() + { + $date = Carbon::now(); + + if ($this->timezone) { + $date->setTimezone($this->timezone); + } + + return CronExpression::factory($this->expression)->isDue($date->toDateTimeString()); + } + + /** + * Determine if the filters pass for the event. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return bool + */ + protected function filtersPass(Application $app) + { + if (($this->filter && ! $app->call($this->filter)) || + $this->reject && $app->call($this->reject)) { + return false; + } + + return true; + } + + /** + * Determine if the event runs in the given environment. + * + * @param string $environment + * @return bool + */ + public function runsInEnvironment($environment) + { + return empty($this->environments) || in_array($environment, $this->environments); + } + + /** + * Determine if the event runs in maintenance mode. + * + * @return bool + */ + public function runsInMaintenanceMode() + { + return $this->evenInMaintenanceMode; + } + + /** + * The Cron expression representing the event's frequency. + * + * @param string $expression + * @return $this + */ + public function cron($expression) + { + $this->expression = $expression; + + return $this; + } + + /** + * Schedule the event to run hourly. + * + * @return $this + */ + public function hourly() + { + return $this->cron('0 * * * * *'); + } + + /** + * Schedule the event to run daily. + * + * @return $this + */ + public function daily() + { + return $this->cron('0 0 * * * *'); + } + + /** + * Schedule the command at a given time. + * + * @param string $time + * @return $this + */ + public function at($time) + { + return $this->dailyAt($time); + } + + /** + * Schedule the event to run daily at a given time (10:00, 19:30, etc). + * + * @param string $time + * @return $this + */ + public function dailyAt($time) + { + $segments = explode(':', $time); + + return $this->spliceIntoPosition(2, (int) $segments[0]) + ->spliceIntoPosition(1, count($segments) == 2 ? (int) $segments[1] : '0'); + } + + /** + * Schedule the event to run twice daily. + * + * @param int $first + * @param int $second + * @return $this + */ + public function twiceDaily($first = 1, $second = 13) + { + $hours = $first.','.$second; + + return $this->spliceIntoPosition(1, 0) + ->spliceIntoPosition(2, $hours); + } + + /** + * Schedule the event to run only on weekdays. + * + * @return $this + */ + public function weekdays() + { + return $this->spliceIntoPosition(5, '1-5'); + } + + /** + * Schedule the event to run only on Mondays. + * + * @return $this + */ + public function mondays() + { + return $this->days(1); + } + + /** + * Schedule the event to run only on Tuesdays. + * + * @return $this + */ + public function tuesdays() + { + return $this->days(2); + } + + /** + * Schedule the event to run only on Wednesdays. + * + * @return $this + */ + public function wednesdays() + { + return $this->days(3); + } + + /** + * Schedule the event to run only on Thursdays. + * + * @return $this + */ + public function thursdays() + { + return $this->days(4); + } + + /** + * Schedule the event to run only on Fridays. + * + * @return $this + */ + public function fridays() + { + return $this->days(5); + } + + /** + * Schedule the event to run only on Saturdays. + * + * @return $this + */ + public function saturdays() + { + return $this->days(6); + } + + /** + * Schedule the event to run only on Sundays. + * + * @return $this + */ + public function sundays() + { + return $this->days(0); + } + + /** + * Schedule the event to run weekly. + * + * @return $this + */ + public function weekly() + { + return $this->cron('0 0 * * 0 *'); + } + + /** + * Schedule the event to run weekly on a given day and time. + * + * @param int $day + * @param string $time + * @return $this + */ + public function weeklyOn($day, $time = '0:0') + { + $this->dailyAt($time); + + return $this->spliceIntoPosition(5, $day); + } + + /** + * Schedule the event to run monthly. + * + * @return $this + */ + public function monthly() + { + return $this->cron('0 0 1 * * *'); + } + + /** + * Schedule the event to run yearly. + * + * @return $this + */ + public function yearly() + { + return $this->cron('0 0 1 1 * *'); + } + + /** + * Schedule the event to run every minute. + * + * @return $this + */ + public function everyMinute() + { + return $this->cron('* * * * * *'); + } + + /** + * Schedule the event to run every five minutes. + * + * @return $this + */ + public function everyFiveMinutes() + { + return $this->cron('*/5 * * * * *'); + } + + /** + * Schedule the event to run every ten minutes. + * + * @return $this + */ + public function everyTenMinutes() + { + return $this->cron('*/10 * * * * *'); + } + + /** + * Schedule the event to run every thirty minutes. + * + * @return $this + */ + public function everyThirtyMinutes() + { + return $this->cron('0,30 * * * * *'); + } + + /** + * Set the days of the week the command should run on. + * + * @param array|mixed $days + * @return $this + */ + public function days($days) + { + $days = is_array($days) ? $days : func_get_args(); + + return $this->spliceIntoPosition(5, implode(',', $days)); + } + + /** + * Set the timezone the date should be evaluated on. + * + * @param \DateTimeZone|string $timezone + * @return $this + */ + public function timezone($timezone) + { + $this->timezone = $timezone; + + return $this; + } + + /** + * Set which user the command should run as. + * + * @param string $user + * @return $this + */ + public function user($user) + { + $this->user = $user; + + return $this; + } + + /** + * Limit the environments the command should run in. + * + * @param array|mixed $environments + * @return $this + */ + public function environments($environments) + { + $this->environments = is_array($environments) ? $environments : func_get_args(); + + return $this; + } + + /** + * State that the command should run even in maintenance mode. + * + * @return $this + */ + public function evenInMaintenanceMode() + { + $this->evenInMaintenanceMode = true; + + return $this; + } + + /** + * Do not allow the event to overlap each other. + * + * @return $this + */ + public function withoutOverlapping() + { + $this->withoutOverlapping = true; + + return $this->skip(function () { + return file_exists($this->mutexPath()); + }); + } + + /** + * Register a callback to further filter the schedule. + * + * @param \Closure $callback + * @return $this + */ + public function when(Closure $callback) + { + $this->filter = $callback; + + return $this; + } + + /** + * Register a callback to further filter the schedule. + * + * @param \Closure $callback + * @return $this + */ + public function skip(Closure $callback) + { + $this->reject = $callback; + + return $this; + } + + /** + * Send the output of the command to a given location. + * + * @param string $location + * @param bool $append + * @return $this + */ + public function sendOutputTo($location, $append = false) + { + $this->output = $location; + + $this->shouldAppendOutput = $append; + + return $this; + } + + /** + * Append the output of the command to a given location. + * + * @param string $location + * @return $this + */ + public function appendOutputTo($location) + { + return $this->sendOutputTo($location, true); + } + + /** + * E-mail the results of the scheduled operation. + * + * @param array|mixed $addresses + * @return $this + * + * @throws \LogicException + */ + public function emailOutputTo($addresses) + { + if (is_null($this->output) || $this->output == $this->getDefaultOutput()) { + throw new LogicException('Must direct output to a file in order to e-mail results.'); + } + + $addresses = is_array($addresses) ? $addresses : func_get_args(); + + return $this->then(function (Mailer $mailer) use ($addresses) { + $this->emailOutput($mailer, $addresses); + }); + } + + /** + * E-mail the output of the event to the recipients. + * + * @param \Illuminate\Contracts\Mail\Mailer $mailer + * @param array $addresses + * @return void + */ + protected function emailOutput(Mailer $mailer, $addresses) + { + $mailer->raw(file_get_contents($this->output), function ($m) use ($addresses) { + $m->subject($this->getEmailSubject()); + + foreach ($addresses as $address) { + $m->to($address); + } + }); + } + + /** + * Get the e-mail subject line for output results. + * + * @return string + */ + protected function getEmailSubject() + { + if ($this->description) { + return 'Scheduled Job Output ('.$this->description.')'; + } + + return 'Scheduled Job Output'; + } + + /** + * Register a callback to ping a given URL before the job runs. + * + * @param string $url + * @return $this + */ + public function pingBefore($url) + { + return $this->before(function () use ($url) { + (new HttpClient)->get($url); + }); + } + + /** + * Register a callback to be called before the operation. + * + * @param \Closure $callback + * @return $this + */ + public function before(Closure $callback) + { + $this->beforeCallbacks[] = $callback; + + return $this; + } + + /** + * Register a callback to ping a given URL after the job runs. + * + * @param string $url + * @return $this + */ + public function thenPing($url) + { + return $this->then(function () use ($url) { + (new HttpClient)->get($url); + }); + } + + /** + * Register a callback to be called after the operation. + * + * @param \Closure $callback + * @return $this + */ + public function after(Closure $callback) + { + return $this->then($callback); + } + + /** + * Register a callback to be called after the operation. + * + * @param \Closure $callback + * @return $this + */ + public function then(Closure $callback) + { + $this->afterCallbacks[] = $callback; + + return $this; + } + + /** + * Set the human-friendly description of the event. + * + * @param string $description + * @return $this + */ + public function name($description) + { + return $this->description($description); + } + + /** + * Set the human-friendly description of the event. + * + * @param string $description + * @return $this + */ + public function description($description) + { + $this->description = $description; + + return $this; + } + + /** + * Splice the given value into the given position of the expression. + * + * @param int $position + * @param string $value + * @return $this + */ + protected function spliceIntoPosition($position, $value) + { + $segments = explode(' ', $this->expression); + + $segments[$position - 1] = $value; + + return $this->cron(implode(' ', $segments)); + } + + /** + * Get the summary of the event for display. + * + * @return string + */ + public function getSummaryForDisplay() + { + if (is_string($this->description)) { + return $this->description; + } + + return $this->buildCommand(); + } + + /** + * Get the Cron expression for the event. + * + * @return string + */ + public function getExpression() + { + return $this->expression; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Schedule.php b/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Schedule.php new file mode 100644 index 0000000..1f56b3d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Schedule.php @@ -0,0 +1,109 @@ +events[] = $event = new CallbackEvent($callback, $parameters); + + return $event; + } + + /** + * Add a new Artisan command event to the schedule. + * + * @param string $command + * @param array $parameters + * @return \Illuminate\Console\Scheduling\Event + */ + public function command($command, array $parameters = []) + { + $binary = ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)); + + if (defined('HHVM_VERSION')) { + $binary .= ' --php'; + } + + if (defined('ARTISAN_BINARY')) { + $artisan = ProcessUtils::escapeArgument(ARTISAN_BINARY); + } else { + $artisan = 'artisan'; + } + + return $this->exec("{$binary} {$artisan} {$command}", $parameters); + } + + /** + * Add a new command event to the schedule. + * + * @param string $command + * @param array $parameters + * @return \Illuminate\Console\Scheduling\Event + */ + public function exec($command, array $parameters = []) + { + if (count($parameters)) { + $command .= ' '.$this->compileParameters($parameters); + } + + $this->events[] = $event = new Event($command); + + return $event; + } + + /** + * Compile parameters for a command. + * + * @param array $parameters + * @return string + */ + protected function compileParameters(array $parameters) + { + return collect($parameters)->map(function ($value, $key) { + return is_numeric($key) ? $value : $key.'='.(is_numeric($value) ? $value : ProcessUtils::escapeArgument($value)); + })->implode(' '); + } + + /** + * Get all of the events on the schedule. + * + * @return array + */ + public function events() + { + return $this->events; + } + + /** + * Get all of the events on the schedule that are due. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return array + */ + public function dueEvents(Application $app) + { + return array_filter($this->events, function ($event) use ($app) { + return $event->isDue($app); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php b/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php new file mode 100644 index 0000000..3ce658c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php @@ -0,0 +1,62 @@ +schedule = $schedule; + + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $events = $this->schedule->dueEvents($this->laravel); + + foreach ($events as $event) { + $this->line('Running scheduled command: '.$event->getSummaryForDisplay()); + + $event->run($this->laravel); + } + + if (count($events) === 0) { + $this->info('No scheduled commands are ready to run.'); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Console/composer.json b/core/vendor/laravel/framework/src/Illuminate/Console/composer.json new file mode 100644 index 0000000..e7c8652 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Console/composer.json @@ -0,0 +1,39 @@ +{ + "name": "illuminate/console", + "description": "The Illuminate Console package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/console": "2.7.*", + "nesbot/carbon": "~1.19" + }, + "autoload": { + "psr-4": { + "Illuminate\\Console\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "guzzlehttp/guzzle": "Required to use the ping methods on schedules (~5.3|~6.0).", + "mtdowling/cron-expression": "Required to use scheduling component (~1.0).", + "symfony/process": "Required to use scheduling component (2.7.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Container/BindingResolutionException.php b/core/vendor/laravel/framework/src/Illuminate/Container/BindingResolutionException.php new file mode 100644 index 0000000..198963e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Container/BindingResolutionException.php @@ -0,0 +1,13 @@ +bindings[$abstract]) || isset($this->instances[$abstract]) || $this->isAlias($abstract); + } + + /** + * Determine if the given abstract type has been resolved. + * + * @param string $abstract + * @return bool + */ + public function resolved($abstract) + { + if ($this->isAlias($abstract)) { + $abstract = $this->getAlias($abstract); + } + + return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]); + } + + /** + * Determine if a given string is an alias. + * + * @param string $name + * @return bool + */ + public function isAlias($name) + { + return isset($this->aliases[$name]); + } + + /** + * Register a binding with the container. + * + * @param string|array $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + */ + public function bind($abstract, $concrete = null, $shared = false) + { + // If the given types are actually an array, we will assume an alias is being + // defined and will grab this "real" abstract class name and register this + // alias with the container so that it can be used as a shortcut for it. + if (is_array($abstract)) { + list($abstract, $alias) = $this->extractAlias($abstract); + + $this->alias($abstract, $alias); + } + + // If no concrete type was given, we will simply set the concrete type to the + // abstract type. This will allow concrete type to be registered as shared + // without being forced to state their classes in both of the parameter. + $this->dropStaleInstances($abstract); + + if (is_null($concrete)) { + $concrete = $abstract; + } + + // If the factory is not a Closure, it means it is just a class name which is + // bound into this container to the abstract type and we will just wrap it + // up inside its own Closure to give us more convenience when extending. + if (! $concrete instanceof Closure) { + $concrete = $this->getClosure($abstract, $concrete); + } + + $this->bindings[$abstract] = compact('concrete', 'shared'); + + // If the abstract type was already resolved in this container we'll fire the + // rebound listener so that any objects which have already gotten resolved + // can have their copy of the object updated via the listener callbacks. + if ($this->resolved($abstract)) { + $this->rebound($abstract); + } + } + + /** + * Get the Closure to be used when building a type. + * + * @param string $abstract + * @param string $concrete + * @return \Closure + */ + protected function getClosure($abstract, $concrete) + { + return function ($c, $parameters = []) use ($abstract, $concrete) { + $method = ($abstract == $concrete) ? 'build' : 'make'; + + return $c->$method($concrete, $parameters); + }; + } + + /** + * Add a contextual binding to the container. + * + * @param string $concrete + * @param string $abstract + * @param \Closure|string $implementation + * @return void + */ + public function addContextualBinding($concrete, $abstract, $implementation) + { + $this->contextual[$concrete][$abstract] = $implementation; + } + + /** + * Register a binding if it hasn't already been registered. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + */ + public function bindIf($abstract, $concrete = null, $shared = false) + { + if (! $this->bound($abstract)) { + $this->bind($abstract, $concrete, $shared); + } + } + + /** + * Register a shared binding in the container. + * + * @param string|array $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function singleton($abstract, $concrete = null) + { + $this->bind($abstract, $concrete, true); + } + + /** + * Wrap a Closure such that it is shared. + * + * @param \Closure $closure + * @return \Closure + */ + public function share(Closure $closure) + { + return function ($container) use ($closure) { + // We'll simply declare a static variable within the Closures and if it has + // not been set we will execute the given Closures to resolve this value + // and return it back to these consumers of the method as an instance. + static $object; + + if (is_null($object)) { + $object = $closure($container); + } + + return $object; + }; + } + + /** + * Bind a shared Closure into the container. + * + * @param string $abstract + * @param \Closure $closure + * @return void + * + * @deprecated since version 5.1. Use singleton instead. + */ + public function bindShared($abstract, Closure $closure) + { + $this->bind($abstract, $this->share($closure), true); + } + + /** + * "Extend" an abstract type in the container. + * + * @param string $abstract + * @param \Closure $closure + * @return void + * + * @throws \InvalidArgumentException + */ + public function extend($abstract, Closure $closure) + { + if (isset($this->instances[$abstract])) { + $this->instances[$abstract] = $closure($this->instances[$abstract], $this); + + $this->rebound($abstract); + } else { + $this->extenders[$abstract][] = $closure; + } + } + + /** + * Register an existing instance as shared in the container. + * + * @param string $abstract + * @param mixed $instance + * @return void + */ + public function instance($abstract, $instance) + { + // First, we will extract the alias from the abstract if it is an array so we + // are using the correct name when binding the type. If we get an alias it + // will be registered with the container so we can resolve it out later. + if (is_array($abstract)) { + list($abstract, $alias) = $this->extractAlias($abstract); + + $this->alias($abstract, $alias); + } + + unset($this->aliases[$abstract]); + + // We'll check to determine if this type has been bound before, and if it has + // we will fire the rebound callbacks registered with the container and it + // can be updated with consuming classes that have gotten resolved here. + $bound = $this->bound($abstract); + + $this->instances[$abstract] = $instance; + + if ($bound) { + $this->rebound($abstract); + } + } + + /** + * Assign a set of tags to a given binding. + * + * @param array|string $abstracts + * @param array|mixed ...$tags + * @return void + */ + public function tag($abstracts, $tags) + { + $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1); + + foreach ($tags as $tag) { + if (! isset($this->tags[$tag])) { + $this->tags[$tag] = []; + } + + foreach ((array) $abstracts as $abstract) { + $this->tags[$tag][] = $abstract; + } + } + } + + /** + * Resolve all of the bindings for a given tag. + * + * @param string $tag + * @return array + */ + public function tagged($tag) + { + $results = []; + + if (isset($this->tags[$tag])) { + foreach ($this->tags[$tag] as $abstract) { + $results[] = $this->make($abstract); + } + } + + return $results; + } + + /** + * Alias a type to a different name. + * + * @param string $abstract + * @param string $alias + * @return void + */ + public function alias($abstract, $alias) + { + $this->aliases[$alias] = $abstract; + } + + /** + * Extract the type and alias from a given definition. + * + * @param array $definition + * @return array + */ + protected function extractAlias(array $definition) + { + return [key($definition), current($definition)]; + } + + /** + * Bind a new callback to an abstract's rebind event. + * + * @param string $abstract + * @param \Closure $callback + * @return mixed + */ + public function rebinding($abstract, Closure $callback) + { + $this->reboundCallbacks[$abstract][] = $callback; + + if ($this->bound($abstract)) { + return $this->make($abstract); + } + } + + /** + * Refresh an instance on the given target and method. + * + * @param string $abstract + * @param mixed $target + * @param string $method + * @return mixed + */ + public function refresh($abstract, $target, $method) + { + return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) { + $target->{$method}($instance); + }); + } + + /** + * Fire the "rebound" callbacks for the given abstract type. + * + * @param string $abstract + * @return void + */ + protected function rebound($abstract) + { + $instance = $this->make($abstract); + + foreach ($this->getReboundCallbacks($abstract) as $callback) { + call_user_func($callback, $this, $instance); + } + } + + /** + * Get the rebound callbacks for a given type. + * + * @param string $abstract + * @return array + */ + protected function getReboundCallbacks($abstract) + { + if (isset($this->reboundCallbacks[$abstract])) { + return $this->reboundCallbacks[$abstract]; + } + + return []; + } + + /** + * Wrap the given closure such that its dependencies will be injected when executed. + * + * @param \Closure $callback + * @param array $parameters + * @return \Closure + */ + public function wrap(Closure $callback, array $parameters = []) + { + return function () use ($callback, $parameters) { + return $this->call($callback, $parameters); + }; + } + + /** + * Call the given Closure / class@method and inject its dependencies. + * + * @param callable|string $callback + * @param array $parameters + * @param string|null $defaultMethod + * @return mixed + */ + public function call($callback, array $parameters = [], $defaultMethod = null) + { + if ($this->isCallableWithAtSign($callback) || $defaultMethod) { + return $this->callClass($callback, $parameters, $defaultMethod); + } + + $dependencies = $this->getMethodDependencies($callback, $parameters); + + return call_user_func_array($callback, $dependencies); + } + + /** + * Determine if the given string is in Class@method syntax. + * + * @param mixed $callback + * @return bool + */ + protected function isCallableWithAtSign($callback) + { + if (! is_string($callback)) { + return false; + } + + return strpos($callback, '@') !== false; + } + + /** + * Get all dependencies for a given method. + * + * @param callable|string $callback + * @param array $parameters + * @return array + */ + protected function getMethodDependencies($callback, array $parameters = []) + { + $dependencies = []; + + foreach ($this->getCallReflector($callback)->getParameters() as $key => $parameter) { + $this->addDependencyForCallParameter($parameter, $parameters, $dependencies); + } + + return array_merge($dependencies, $parameters); + } + + /** + * Get the proper reflection instance for the given callback. + * + * @param callable|string $callback + * @return \ReflectionFunctionAbstract + */ + protected function getCallReflector($callback) + { + if (is_string($callback) && strpos($callback, '::') !== false) { + $callback = explode('::', $callback); + } + + if (is_array($callback)) { + return new ReflectionMethod($callback[0], $callback[1]); + } + + return new ReflectionFunction($callback); + } + + /** + * Get the dependency for the given call parameter. + * + * @param \ReflectionParameter $parameter + * @param array $parameters + * @param array $dependencies + * @return mixed + */ + protected function addDependencyForCallParameter(ReflectionParameter $parameter, array &$parameters, &$dependencies) + { + if (array_key_exists($parameter->name, $parameters)) { + $dependencies[] = $parameters[$parameter->name]; + + unset($parameters[$parameter->name]); + } elseif ($parameter->getClass()) { + $dependencies[] = $this->make($parameter->getClass()->name); + } elseif ($parameter->isDefaultValueAvailable()) { + $dependencies[] = $parameter->getDefaultValue(); + } + } + + /** + * Call a string reference to a class using Class@method syntax. + * + * @param string $target + * @param array $parameters + * @param string|null $defaultMethod + * @return mixed + */ + protected function callClass($target, array $parameters = [], $defaultMethod = null) + { + $segments = explode('@', $target); + + // If the listener has an @ sign, we will assume it is being used to delimit + // the class name from the handle method name. This allows for handlers + // to run multiple handler methods in a single class for convenience. + $method = count($segments) == 2 ? $segments[1] : $defaultMethod; + + if (is_null($method)) { + throw new InvalidArgumentException('Method not provided.'); + } + + return $this->call([$this->make($segments[0]), $method], $parameters); + } + + /** + * Resolve the given type from the container. + * + * @param string $abstract + * @param array $parameters + * @return mixed + */ + public function make($abstract, array $parameters = []) + { + $abstract = $this->getAlias($abstract); + + // If an instance of the type is currently being managed as a singleton we'll + // just return an existing instance instead of instantiating new instances + // so the developer can keep using the same objects instance every time. + if (isset($this->instances[$abstract])) { + return $this->instances[$abstract]; + } + + $concrete = $this->getConcrete($abstract); + + // We're ready to instantiate an instance of the concrete type registered for + // the binding. This will instantiate the types, as well as resolve any of + // its "nested" dependencies recursively until all have gotten resolved. + if ($this->isBuildable($concrete, $abstract)) { + $object = $this->build($concrete, $parameters); + } else { + $object = $this->make($concrete, $parameters); + } + + // If we defined any extenders for this type, we'll need to spin through them + // and apply them to the object being built. This allows for the extension + // of services, such as changing configuration or decorating the object. + foreach ($this->getExtenders($abstract) as $extender) { + $object = $extender($object, $this); + } + + // If the requested type is registered as a singleton we'll want to cache off + // the instances in "memory" so we can return it later without creating an + // entirely new instance of an object on each subsequent request for it. + if ($this->isShared($abstract)) { + $this->instances[$abstract] = $object; + } + + $this->fireResolvingCallbacks($abstract, $object); + + $this->resolved[$abstract] = true; + + return $object; + } + + /** + * Get the concrete type for a given abstract. + * + * @param string $abstract + * @return mixed $concrete + */ + protected function getConcrete($abstract) + { + if (! is_null($concrete = $this->getContextualConcrete($abstract))) { + return $concrete; + } + + // If we don't have a registered resolver or concrete for the type, we'll just + // assume each type is a concrete name and will attempt to resolve it as is + // since the container should be able to resolve concretes automatically. + if (! isset($this->bindings[$abstract])) { + if ($this->missingLeadingSlash($abstract) && + isset($this->bindings['\\'.$abstract])) { + $abstract = '\\'.$abstract; + } + + return $abstract; + } + + return $this->bindings[$abstract]['concrete']; + } + + /** + * Get the contextual concrete binding for the given abstract. + * + * @param string $abstract + * @return string|null + */ + protected function getContextualConcrete($abstract) + { + if (isset($this->contextual[end($this->buildStack)][$abstract])) { + return $this->contextual[end($this->buildStack)][$abstract]; + } + } + + /** + * Determine if the given abstract has a leading slash. + * + * @param string $abstract + * @return bool + */ + protected function missingLeadingSlash($abstract) + { + return is_string($abstract) && strpos($abstract, '\\') !== 0; + } + + /** + * Get the extender callbacks for a given type. + * + * @param string $abstract + * @return array + */ + protected function getExtenders($abstract) + { + if (isset($this->extenders[$abstract])) { + return $this->extenders[$abstract]; + } + + return []; + } + + /** + * Instantiate a concrete instance of the given type. + * + * @param string $concrete + * @param array $parameters + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function build($concrete, array $parameters = []) + { + // If the concrete type is actually a Closure, we will just execute it and + // hand back the results of the functions, which allows functions to be + // used as resolvers for more fine-tuned resolution of these objects. + if ($concrete instanceof Closure) { + return $concrete($this, $parameters); + } + + $reflector = new ReflectionClass($concrete); + + // If the type is not instantiable, the developer is attempting to resolve + // an abstract type such as an Interface of Abstract Class and there is + // no binding registered for the abstractions so we need to bail out. + if (! $reflector->isInstantiable()) { + $message = "Target [$concrete] is not instantiable."; + + throw new BindingResolutionContractException($message); + } + + $this->buildStack[] = $concrete; + + $constructor = $reflector->getConstructor(); + + // If there are no constructors, that means there are no dependencies then + // we can just resolve the instances of the objects right away, without + // resolving any other types or dependencies out of these containers. + if (is_null($constructor)) { + array_pop($this->buildStack); + + return new $concrete; + } + + $dependencies = $constructor->getParameters(); + + // Once we have all the constructor's parameters we can create each of the + // dependency instances and then use the reflection instances to make a + // new instance of this class, injecting the created dependencies in. + $parameters = $this->keyParametersByArgument( + $dependencies, $parameters + ); + + $instances = $this->getDependencies( + $dependencies, $parameters + ); + + array_pop($this->buildStack); + + return $reflector->newInstanceArgs($instances); + } + + /** + * Resolve all of the dependencies from the ReflectionParameters. + * + * @param array $parameters + * @param array $primitives + * @return array + */ + protected function getDependencies(array $parameters, array $primitives = []) + { + $dependencies = []; + + foreach ($parameters as $parameter) { + $dependency = $parameter->getClass(); + + // If the class is null, it means the dependency is a string or some other + // primitive type which we can not resolve since it is not a class and + // we will just bomb out with an error since we have no-where to go. + if (array_key_exists($parameter->name, $primitives)) { + $dependencies[] = $primitives[$parameter->name]; + } elseif (is_null($dependency)) { + $dependencies[] = $this->resolveNonClass($parameter); + } else { + $dependencies[] = $this->resolveClass($parameter); + } + } + + return (array) $dependencies; + } + + /** + * Resolve a non-class hinted dependency. + * + * @param \ReflectionParameter $parameter + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolveNonClass(ReflectionParameter $parameter) + { + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}"; + + throw new BindingResolutionContractException($message); + } + + /** + * Resolve a class based dependency from the container. + * + * @param \ReflectionParameter $parameter + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolveClass(ReflectionParameter $parameter) + { + try { + return $this->make($parameter->getClass()->name); + } + + // If we can not resolve the class instance, we will check to see if the value + // is optional, and if it is we will return the optional parameter value as + // the value of the dependency, similarly to how we do this with scalars. + catch (BindingResolutionContractException $e) { + if ($parameter->isOptional()) { + return $parameter->getDefaultValue(); + } + + throw $e; + } + } + + /** + * If extra parameters are passed by numeric ID, rekey them by argument name. + * + * @param array $dependencies + * @param array $parameters + * @return array + */ + protected function keyParametersByArgument(array $dependencies, array $parameters) + { + foreach ($parameters as $key => $value) { + if (is_numeric($key)) { + unset($parameters[$key]); + + $parameters[$dependencies[$key]->name] = $value; + } + } + + return $parameters; + } + + /** + * Register a new resolving callback. + * + * @param string $abstract + * @param \Closure|null $callback + * @return void + */ + public function resolving($abstract, Closure $callback = null) + { + if ($callback === null && $abstract instanceof Closure) { + $this->resolvingCallback($abstract); + } else { + $this->resolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Register a new after resolving callback for all types. + * + * @param string $abstract + * @param \Closure|null $callback + * @return void + */ + public function afterResolving($abstract, Closure $callback = null) + { + if ($abstract instanceof Closure && $callback === null) { + $this->afterResolvingCallback($abstract); + } else { + $this->afterResolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Register a new resolving callback by type of its first argument. + * + * @param \Closure $callback + * @return void + */ + protected function resolvingCallback(Closure $callback) + { + $abstract = $this->getFunctionHint($callback); + + if ($abstract) { + $this->resolvingCallbacks[$abstract][] = $callback; + } else { + $this->globalResolvingCallbacks[] = $callback; + } + } + + /** + * Register a new after resolving callback by type of its first argument. + * + * @param \Closure $callback + * @return void + */ + protected function afterResolvingCallback(Closure $callback) + { + $abstract = $this->getFunctionHint($callback); + + if ($abstract) { + $this->afterResolvingCallbacks[$abstract][] = $callback; + } else { + $this->globalAfterResolvingCallbacks[] = $callback; + } + } + + /** + * Get the type hint for this closure's first argument. + * + * @param \Closure $callback + * @return mixed + */ + protected function getFunctionHint(Closure $callback) + { + $function = new ReflectionFunction($callback); + + if ($function->getNumberOfParameters() == 0) { + return; + } + + $expected = $function->getParameters()[0]; + + if (! $expected->getClass()) { + return; + } + + return $expected->getClass()->name; + } + + /** + * Fire all of the resolving callbacks. + * + * @param string $abstract + * @param mixed $object + * @return void + */ + protected function fireResolvingCallbacks($abstract, $object) + { + $this->fireCallbackArray($object, $this->globalResolvingCallbacks); + + $this->fireCallbackArray( + $object, $this->getCallbacksForType( + $abstract, $object, $this->resolvingCallbacks + ) + ); + + $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks); + + $this->fireCallbackArray( + $object, $this->getCallbacksForType( + $abstract, $object, $this->afterResolvingCallbacks + ) + ); + } + + /** + * Get all callbacks for a given type. + * + * @param string $abstract + * @param object $object + * @param array $callbacksPerType + * + * @return array + */ + protected function getCallbacksForType($abstract, $object, array $callbacksPerType) + { + $results = []; + + foreach ($callbacksPerType as $type => $callbacks) { + if ($type === $abstract || $object instanceof $type) { + $results = array_merge($results, $callbacks); + } + } + + return $results; + } + + /** + * Fire an array of callbacks with an object. + * + * @param mixed $object + * @param array $callbacks + * @return void + */ + protected function fireCallbackArray($object, array $callbacks) + { + foreach ($callbacks as $callback) { + $callback($object, $this); + } + } + + /** + * Determine if a given type is shared. + * + * @param string $abstract + * @return bool + */ + public function isShared($abstract) + { + if (isset($this->bindings[$abstract]['shared'])) { + $shared = $this->bindings[$abstract]['shared']; + } else { + $shared = false; + } + + return isset($this->instances[$abstract]) || $shared === true; + } + + /** + * Determine if the given concrete is buildable. + * + * @param mixed $concrete + * @param string $abstract + * @return bool + */ + protected function isBuildable($concrete, $abstract) + { + return $concrete === $abstract || $concrete instanceof Closure; + } + + /** + * Get the alias for an abstract if available. + * + * @param string $abstract + * @return string + */ + protected function getAlias($abstract) + { + if (! isset($this->aliases[$abstract])) { + return $abstract; + } + + return $this->getAlias($this->aliases[$abstract]); + } + + /** + * Get the container's bindings. + * + * @return array + */ + public function getBindings() + { + return $this->bindings; + } + + /** + * Drop all of the stale instances and aliases. + * + * @param string $abstract + * @return void + */ + protected function dropStaleInstances($abstract) + { + unset($this->instances[$abstract], $this->aliases[$abstract]); + } + + /** + * Remove a resolved instance from the instance cache. + * + * @param string $abstract + * @return void + */ + public function forgetInstance($abstract) + { + unset($this->instances[$abstract]); + } + + /** + * Clear all of the instances from the container. + * + * @return void + */ + public function forgetInstances() + { + $this->instances = []; + } + + /** + * Flush the container of all bindings and resolved instances. + * + * @return void + */ + public function flush() + { + $this->aliases = []; + $this->resolved = []; + $this->bindings = []; + $this->instances = []; + } + + /** + * Set the globally available instance of the container. + * + * @return static + */ + public static function getInstance() + { + return static::$instance; + } + + /** + * Set the shared instance of the container. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public static function setInstance(ContainerContract $container) + { + static::$instance = $container; + } + + /** + * Determine if a given offset exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return $this->bound($key); + } + + /** + * Get the value at a given offset. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->make($key); + } + + /** + * Set the value at a given offset. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + // If the value is not a Closure, we will make it one. This simply gives + // more "drop-in" replacement functionality for the Pimple which this + // container's simplest functions are base modeled and built after. + if (! $value instanceof Closure) { + $value = function () use ($value) { + return $value; + }; + } + + $this->bind($key, $value); + } + + /** + * Unset the value at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]); + } + + /** + * Dynamically access container services. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this[$key]; + } + + /** + * Dynamically set container services. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this[$key] = $value; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Container/ContextualBindingBuilder.php b/core/vendor/laravel/framework/src/Illuminate/Container/ContextualBindingBuilder.php new file mode 100644 index 0000000..fc5cd61 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Container/ContextualBindingBuilder.php @@ -0,0 +1,66 @@ +concrete = $concrete; + $this->container = $container; + } + + /** + * Define the abstract target that depends on the context. + * + * @param string $abstract + * @return $this + */ + public function needs($abstract) + { + $this->needs = $abstract; + + return $this; + } + + /** + * Define the implementation for the contextual binding. + * + * @param \Closure|string $implementation + * @return void + */ + public function give($implementation) + { + $this->container->addContextualBinding($this->concrete, $this->needs, $implementation); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Container/composer.json b/core/vendor/laravel/framework/src/Illuminate/Container/composer.json new file mode 100644 index 0000000..567d018 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Container/composer.json @@ -0,0 +1,31 @@ +{ + "name": "illuminate/container", + "description": "The Illuminate Container package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Contracts/Auth/Access/Authorizable.php b/core/vendor/laravel/framework/src/Illuminate/Contracts/Auth/Access/Authorizable.php new file mode 100644 index 0000000..2f9657c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Contracts/Auth/Access/Authorizable.php @@ -0,0 +1,15 @@ +id = $id; + $this->class = $class; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Contracts/Debug/ExceptionHandler.php b/core/vendor/laravel/framework/src/Illuminate/Contracts/Debug/ExceptionHandler.php new file mode 100644 index 0000000..e3f18a5 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Contracts/Debug/ExceptionHandler.php @@ -0,0 +1,34 @@ +provider = $provider; + } + + /** + * Get the validation error message provider. + * + * @return \Illuminate\Contracts\Support\MessageBag + */ + public function errors() + { + return $this->provider->getMessageBag(); + } + + /** + * Get the validation error message provider. + * + * @return \Illuminate\Contracts\Support\MessageProvider + */ + public function getMessageProvider() + { + return $this->provider; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Contracts/Validation/Validator.php b/core/vendor/laravel/framework/src/Illuminate/Contracts/Validation/Validator.php new file mode 100644 index 0000000..9cf68c7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Contracts/Validation/Validator.php @@ -0,0 +1,40 @@ +=5.5.9" + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cookie/CookieJar.php b/core/vendor/laravel/framework/src/Illuminate/Cookie/CookieJar.php new file mode 100644 index 0000000..ec7540a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cookie/CookieJar.php @@ -0,0 +1,177 @@ +getPathAndDomain($path, $domain, $secure); + + $time = ($minutes == 0) ? 0 : time() + ($minutes * 60); + + return new Cookie($name, $value, $time, $path, $domain, $secure, $httpOnly); + } + + /** + * Create a cookie that lasts "forever" (five years). + * + * @param string $name + * @param string $value + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + * @return \Symfony\Component\HttpFoundation\Cookie + */ + public function forever($name, $value, $path = null, $domain = null, $secure = false, $httpOnly = true) + { + return $this->make($name, $value, 2628000, $path, $domain, $secure, $httpOnly); + } + + /** + * Expire the given cookie. + * + * @param string $name + * @param string $path + * @param string $domain + * @return \Symfony\Component\HttpFoundation\Cookie + */ + public function forget($name, $path = null, $domain = null) + { + return $this->make($name, null, -2628000, $path, $domain); + } + + /** + * Determine if a cookie has been queued. + * + * @param string $key + * @return bool + */ + public function hasQueued($key) + { + return ! is_null($this->queued($key)); + } + + /** + * Get a queued cookie instance. + * + * @param string $key + * @param mixed $default + * @return \Symfony\Component\HttpFoundation\Cookie + */ + public function queued($key, $default = null) + { + return Arr::get($this->queued, $key, $default); + } + + /** + * Queue a cookie to send with the next response. + * + * @param mixed + * @return void + */ + public function queue() + { + if (head(func_get_args()) instanceof Cookie) { + $cookie = head(func_get_args()); + } else { + $cookie = call_user_func_array([$this, 'make'], func_get_args()); + } + + $this->queued[$cookie->getName()] = $cookie; + } + + /** + * Remove a cookie from the queue. + * + * @param string $name + * @return void + */ + public function unqueue($name) + { + unset($this->queued[$name]); + } + + /** + * Get the path and domain, or the default values. + * + * @param string $path + * @param string $domain + * @param bool $secure + * @return array + */ + protected function getPathAndDomain($path, $domain, $secure = false) + { + return [$path ?: $this->path, $domain ?: $this->domain, $secure ?: $this->secure]; + } + + /** + * Set the default path and domain for the jar. + * + * @param string $path + * @param string $domain + * @param bool $secure + * @return $this + */ + public function setDefaultPathAndDomain($path, $domain, $secure = false) + { + list($this->path, $this->domain, $this->secure) = [$path, $domain, $secure]; + + return $this; + } + + /** + * Get the cookies which have been queued for the next request. + * + * @return array + */ + public function getQueuedCookies() + { + return $this->queued; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cookie/CookieServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Cookie/CookieServiceProvider.php new file mode 100644 index 0000000..cd04f12 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cookie/CookieServiceProvider.php @@ -0,0 +1,22 @@ +app->singleton('cookie', function ($app) { + $config = $app['config']['session']; + + return (new CookieJar)->setDefaultPathAndDomain($config['path'], $config['domain'], $config['secure']); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php b/core/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php new file mode 100644 index 0000000..4caa574 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php @@ -0,0 +1,45 @@ +cookies = $cookies; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + $response = $next($request); + + foreach ($this->cookies->getQueuedCookies() as $cookie) { + $response->headers->setCookie($cookie); + } + + return $response; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php b/core/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php new file mode 100644 index 0000000..c30d38c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php @@ -0,0 +1,163 @@ +encrypter = $encrypter; + } + + /** + * Disable encryption for the given cookie name(s). + * + * @param string|array $cookieName + * @return void + */ + public function disableFor($cookieName) + { + $this->except = array_merge($this->except, (array) $cookieName); + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + return $this->encrypt($next($this->decrypt($request))); + } + + /** + * Decrypt the cookies on the request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\Request + */ + protected function decrypt(Request $request) + { + foreach ($request->cookies as $key => $c) { + if ($this->isDisabled($key)) { + continue; + } + + try { + $request->cookies->set($key, $this->decryptCookie($c)); + } catch (DecryptException $e) { + $request->cookies->set($key, null); + } + } + + return $request; + } + + /** + * Decrypt the given cookie and return the value. + * + * @param string|array $cookie + * @return string|array + */ + protected function decryptCookie($cookie) + { + return is_array($cookie) + ? $this->decryptArray($cookie) + : $this->encrypter->decrypt($cookie); + } + + /** + * Decrypt an array based cookie. + * + * @param array $cookie + * @return array + */ + protected function decryptArray(array $cookie) + { + $decrypted = []; + + foreach ($cookie as $key => $value) { + if (is_string($value)) { + $decrypted[$key] = $this->encrypter->decrypt($value); + } + } + + return $decrypted; + } + + /** + * Encrypt the cookies on an outgoing response. + * + * @param \Symfony\Component\HttpFoundation\Response $response + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function encrypt(Response $response) + { + foreach ($response->headers->getCookies() as $cookie) { + if ($this->isDisabled($cookie->getName())) { + continue; + } + + $response->headers->setCookie($this->duplicate( + $cookie, $this->encrypter->encrypt($cookie->getValue()) + )); + } + + return $response; + } + + /** + * Duplicate a cookie with a new value. + * + * @param \Symfony\Component\HttpFoundation\Cookie $c + * @param mixed $value + * @return \Symfony\Component\HttpFoundation\Cookie + */ + protected function duplicate(Cookie $c, $value) + { + return new Cookie( + $c->getName(), $value, $c->getExpiresTime(), $c->getPath(), + $c->getDomain(), $c->isSecure(), $c->isHttpOnly() + ); + } + + /** + * Determine whether encryption has been disabled for the given cookie. + * + * @param string $name + * @return bool + */ + public function isDisabled($name) + { + return in_array($name, $this->except); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Cookie/composer.json b/core/vendor/laravel/framework/src/Illuminate/Cookie/composer.json new file mode 100644 index 0000000..a3c3312 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Cookie/composer.json @@ -0,0 +1,34 @@ +{ + "name": "illuminate/cookie", + "description": "The Illuminate Cookie package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/http-kernel": "2.7.*", + "symfony/http-foundation": "2.7.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Cookie\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Capsule/Manager.php b/core/vendor/laravel/framework/src/Illuminate/Database/Capsule/Manager.php new file mode 100644 index 0000000..1a14401 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Capsule/Manager.php @@ -0,0 +1,201 @@ +setupContainer($container ?: new Container); + + // Once we have the container setup, we will setup the default configuration + // options in the container "config" binding. This will make the database + // manager behave correctly since all the correct binding are in place. + $this->setupDefaultConfiguration(); + + $this->setupManager(); + } + + /** + * Setup the default database configuration options. + * + * @return void + */ + protected function setupDefaultConfiguration() + { + $this->container['config']['database.fetch'] = PDO::FETCH_OBJ; + + $this->container['config']['database.default'] = 'default'; + } + + /** + * Build the database manager instance. + * + * @return void + */ + protected function setupManager() + { + $factory = new ConnectionFactory($this->container); + + $this->manager = new DatabaseManager($this->container, $factory); + } + + /** + * Get a connection instance from the global manager. + * + * @param string $connection + * @return \Illuminate\Database\Connection + */ + public static function connection($connection = null) + { + return static::$instance->getConnection($connection); + } + + /** + * Get a fluent query builder instance. + * + * @param string $table + * @param string $connection + * @return \Illuminate\Database\Query\Builder + */ + public static function table($table, $connection = null) + { + return static::$instance->connection($connection)->table($table); + } + + /** + * Get a schema builder instance. + * + * @param string $connection + * @return \Illuminate\Database\Schema\Builder + */ + public static function schema($connection = null) + { + return static::$instance->connection($connection)->getSchemaBuilder(); + } + + /** + * Get a registered connection instance. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function getConnection($name = null) + { + return $this->manager->connection($name); + } + + /** + * Register a connection with the manager. + * + * @param array $config + * @param string $name + * @return void + */ + public function addConnection(array $config, $name = 'default') + { + $connections = $this->container['config']['database.connections']; + + $connections[$name] = $config; + + $this->container['config']['database.connections'] = $connections; + } + + /** + * Bootstrap Eloquent so it is ready for usage. + * + * @return void + */ + public function bootEloquent() + { + Eloquent::setConnectionResolver($this->manager); + + // If we have an event dispatcher instance, we will go ahead and register it + // with the Eloquent ORM, allowing for model callbacks while creating and + // updating "model" instances; however, if it not necessary to operate. + if ($dispatcher = $this->getEventDispatcher()) { + Eloquent::setEventDispatcher($dispatcher); + } + } + + /** + * Set the fetch mode for the database connections. + * + * @param int $fetchMode + * @return $this + */ + public function setFetchMode($fetchMode) + { + $this->container['config']['database.fetch'] = $fetchMode; + + return $this; + } + + /** + * Get the database manager instance. + * + * @return \Illuminate\Database\DatabaseManager + */ + public function getDatabaseManager() + { + return $this->manager; + } + + /** + * Get the current event dispatcher instance. + * + * @return \Illuminate\Contracts\Events\Dispatcher|null + */ + public function getEventDispatcher() + { + if ($this->container->bound('events')) { + return $this->container['events']; + } + } + + /** + * Set the event dispatcher instance to be used by connections. + * + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @return void + */ + public function setEventDispatcher(Dispatcher $dispatcher) + { + $this->container->instance('events', $dispatcher); + } + + /** + * Dynamically pass methods to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return call_user_func_array([static::connection(), $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Connection.php b/core/vendor/laravel/framework/src/Illuminate/Database/Connection.php new file mode 100644 index 0000000..cc46b2e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Connection.php @@ -0,0 +1,1165 @@ +pdo = $pdo; + + // First we will setup the default properties. We keep track of the DB + // name we are connected to since it is needed when some reflective + // type commands are run such as checking whether a table exists. + $this->database = $database; + + $this->tablePrefix = $tablePrefix; + + $this->config = $config; + + // We need to initialize a query grammar and the query post processors + // which are both very important parts of the database abstractions + // so we initialize these to their default values while starting. + $this->useDefaultQueryGrammar(); + + $this->useDefaultPostProcessor(); + } + + /** + * Set the query grammar to the default implementation. + * + * @return void + */ + public function useDefaultQueryGrammar() + { + $this->queryGrammar = $this->getDefaultQueryGrammar(); + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + protected function getDefaultQueryGrammar() + { + return new QueryGrammar; + } + + /** + * Set the schema grammar to the default implementation. + * + * @return void + */ + public function useDefaultSchemaGrammar() + { + $this->schemaGrammar = $this->getDefaultSchemaGrammar(); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + protected function getDefaultSchemaGrammar() + { + // + } + + /** + * Set the query post processor to the default implementation. + * + * @return void + */ + public function useDefaultPostProcessor() + { + $this->postProcessor = $this->getDefaultPostProcessor(); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + protected function getDefaultPostProcessor() + { + return new Processor; + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\Builder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new SchemaBuilder($this); + } + + /** + * Begin a fluent query against a database table. + * + * @param string $table + * @return \Illuminate\Database\Query\Builder + */ + public function table($table) + { + return $this->query()->from($table); + } + + /** + * Get a new query builder instance. + * + * @return \Illuminate\Database\Query\Builder + */ + public function query() + { + return new QueryBuilder( + $this, $this->getQueryGrammar(), $this->getPostProcessor() + ); + } + + /** + * Get a new raw query expression. + * + * @param mixed $value + * @return \Illuminate\Database\Query\Expression + */ + public function raw($value) + { + return new Expression($value); + } + + /** + * Run a select statement and return a single result. + * + * @param string $query + * @param array $bindings + * @return mixed + */ + public function selectOne($query, $bindings = []) + { + $records = $this->select($query, $bindings); + + return count($records) > 0 ? reset($records) : null; + } + + /** + * Run a select statement against the database. + * + * @param string $query + * @param array $bindings + * @return array + */ + public function selectFromWriteConnection($query, $bindings = []) + { + return $this->select($query, $bindings, false); + } + + /** + * Run a select statement against the database. + * + * @param string $query + * @param array $bindings + * @param bool $useReadPdo + * @return array + */ + public function select($query, $bindings = [], $useReadPdo = true) + { + return $this->run($query, $bindings, function ($me, $query, $bindings) use ($useReadPdo) { + if ($me->pretending()) { + return []; + } + + // For select statements, we'll simply execute the query and return an array + // of the database result set. Each element in the array will be a single + // row from the database table, and will either be an array or objects. + $statement = $this->getPdoForSelect($useReadPdo)->prepare($query); + + $statement->execute($me->prepareBindings($bindings)); + + return $statement->fetchAll($me->getFetchMode()); + }); + } + + /** + * Get the PDO connection to use for a select query. + * + * @param bool $useReadPdo + * @return \PDO + */ + protected function getPdoForSelect($useReadPdo = true) + { + return $useReadPdo ? $this->getReadPdo() : $this->getPdo(); + } + + /** + * Run an insert statement against the database. + * + * @param string $query + * @param array $bindings + * @return bool + */ + public function insert($query, $bindings = []) + { + return $this->statement($query, $bindings); + } + + /** + * Run an update statement against the database. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function update($query, $bindings = []) + { + return $this->affectingStatement($query, $bindings); + } + + /** + * Run a delete statement against the database. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function delete($query, $bindings = []) + { + return $this->affectingStatement($query, $bindings); + } + + /** + * Execute an SQL statement and return the boolean result. + * + * @param string $query + * @param array $bindings + * @return bool + */ + public function statement($query, $bindings = []) + { + return $this->run($query, $bindings, function ($me, $query, $bindings) { + if ($me->pretending()) { + return true; + } + + $bindings = $me->prepareBindings($bindings); + + return $me->getPdo()->prepare($query)->execute($bindings); + }); + } + + /** + * Run an SQL statement and get the number of rows affected. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function affectingStatement($query, $bindings = []) + { + return $this->run($query, $bindings, function ($me, $query, $bindings) { + if ($me->pretending()) { + return 0; + } + + // For update or delete statements, we want to get the number of rows affected + // by the statement and return that back to the developer. We'll first need + // to execute the statement and then we'll use PDO to fetch the affected. + $statement = $me->getPdo()->prepare($query); + + $statement->execute($me->prepareBindings($bindings)); + + return $statement->rowCount(); + }); + } + + /** + * Run a raw, unprepared query against the PDO connection. + * + * @param string $query + * @return bool + */ + public function unprepared($query) + { + return $this->run($query, [], function ($me, $query) { + if ($me->pretending()) { + return true; + } + + return (bool) $me->getPdo()->exec($query); + }); + } + + /** + * Prepare the query bindings for execution. + * + * @param array $bindings + * @return array + */ + public function prepareBindings(array $bindings) + { + $grammar = $this->getQueryGrammar(); + + foreach ($bindings as $key => $value) { + // We need to transform all instances of DateTimeInterface into the actual + // date string. Each query grammar maintains its own date string format + // so we'll just ask the grammar for the format to get from the date. + if ($value instanceof DateTimeInterface) { + $bindings[$key] = $value->format($grammar->getDateFormat()); + } elseif ($value === false) { + $bindings[$key] = 0; + } + } + + return $bindings; + } + + /** + * Execute a Closure within a transaction. + * + * @param \Closure $callback + * @return mixed + * + * @throws \Throwable + */ + public function transaction(Closure $callback) + { + $this->beginTransaction(); + + // We'll simply execute the given callback within a try / catch block + // and if we catch any exception we can rollback the transaction + // so that none of the changes are persisted to the database. + try { + $result = $callback($this); + + $this->commit(); + } + + // If we catch an exception, we will roll back so nothing gets messed + // up in the database. Then we'll re-throw the exception so it can + // be handled how the developer sees fit for their applications. + catch (Exception $e) { + $this->rollBack(); + + throw $e; + } catch (Throwable $e) { + $this->rollBack(); + + throw $e; + } + + return $result; + } + + /** + * Start a new database transaction. + * + * @return void + * + * @throws \Exception + */ + public function beginTransaction() + { + if ($this->transactions == 0) { + try { + $this->pdo->beginTransaction(); + } catch (Exception $e) { + if ($this->causedByLostConnection($e)) { + $this->reconnect(); + $this->pdo->beginTransaction(); + } else { + throw $e; + } + } + } elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) { + $this->pdo->exec( + $this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1)) + ); + } + + ++$this->transactions; + + $this->fireConnectionEvent('beganTransaction'); + } + + /** + * Commit the active database transaction. + * + * @return void + */ + public function commit() + { + if ($this->transactions == 1) { + $this->pdo->commit(); + } + + --$this->transactions; + + $this->fireConnectionEvent('committed'); + } + + /** + * Rollback the active database transaction. + * + * @return void + */ + public function rollBack() + { + if ($this->transactions == 1) { + $this->pdo->rollBack(); + } elseif ($this->transactions > 1 && $this->queryGrammar->supportsSavepoints()) { + $this->pdo->exec( + $this->queryGrammar->compileSavepointRollBack('trans'.$this->transactions) + ); + } + + $this->transactions = max(0, $this->transactions - 1); + + $this->fireConnectionEvent('rollingBack'); + } + + /** + * Get the number of active transactions. + * + * @return int + */ + public function transactionLevel() + { + return $this->transactions; + } + + /** + * Execute the given callback in "dry run" mode. + * + * @param \Closure $callback + * @return array + */ + public function pretend(Closure $callback) + { + $loggingQueries = $this->loggingQueries; + + $this->enableQueryLog(); + + $this->pretending = true; + + $this->queryLog = []; + + // Basically to make the database connection "pretend", we will just return + // the default values for all the query methods, then we will return an + // array of queries that were "executed" within the Closure callback. + $callback($this); + + $this->pretending = false; + + $this->loggingQueries = $loggingQueries; + + return $this->queryLog; + } + + /** + * Run a SQL statement and log its execution context. + * + * @param string $query + * @param array $bindings + * @param \Closure $callback + * @return mixed + * + * @throws \Illuminate\Database\QueryException + */ + protected function run($query, $bindings, Closure $callback) + { + $this->reconnectIfMissingConnection(); + + $start = microtime(true); + + // Here we will run this query. If an exception occurs we'll determine if it was + // caused by a connection that has been lost. If that is the cause, we'll try + // to re-establish connection and re-run the query with a fresh connection. + try { + $result = $this->runQueryCallback($query, $bindings, $callback); + } catch (QueryException $e) { + if ($this->transactions >= 1) { + throw $e; + } + + $result = $this->tryAgainIfCausedByLostConnection( + $e, $query, $bindings, $callback + ); + } + + // Once we have run the query we will calculate the time that it took to run and + // then log the query, bindings, and execution time so we will report them on + // the event that the developer needs them. We'll log time in milliseconds. + $time = $this->getElapsedTime($start); + + $this->logQuery($query, $bindings, $time); + + return $result; + } + + /** + * Run a SQL statement. + * + * @param string $query + * @param array $bindings + * @param \Closure $callback + * @return mixed + * + * @throws \Illuminate\Database\QueryException + */ + protected function runQueryCallback($query, $bindings, Closure $callback) + { + // To execute the statement, we'll simply call the callback, which will actually + // run the SQL against the PDO connection. Then we can calculate the time it + // took to execute and log the query SQL, bindings and time in our memory. + try { + $result = $callback($this, $query, $bindings); + } + + // If an exception occurs when attempting to run a query, we'll format the error + // message to include the bindings with SQL, which will make this exception a + // lot more helpful to the developer instead of just the database's errors. + catch (Exception $e) { + throw new QueryException( + $query, $this->prepareBindings($bindings), $e + ); + } + + return $result; + } + + /** + * Handle a query exception that occurred during query execution. + * + * @param \Illuminate\Database\QueryException $e + * @param string $query + * @param array $bindings + * @param \Closure $callback + * @return mixed + * + * @throws \Illuminate\Database\QueryException + */ + protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $bindings, Closure $callback) + { + if ($this->causedByLostConnection($e->getPrevious())) { + $this->reconnect(); + + return $this->runQueryCallback($query, $bindings, $callback); + } + + throw $e; + } + + /** + * Disconnect from the underlying PDO connection. + * + * @return void + */ + public function disconnect() + { + $this->setPdo(null)->setReadPdo(null); + } + + /** + * Reconnect to the database. + * + * @return void + * + * @throws \LogicException + */ + public function reconnect() + { + if (is_callable($this->reconnector)) { + return call_user_func($this->reconnector, $this); + } + + throw new LogicException('Lost connection and no reconnector available.'); + } + + /** + * Reconnect to the database if a PDO connection is missing. + * + * @return void + */ + protected function reconnectIfMissingConnection() + { + if (is_null($this->getPdo()) || is_null($this->getReadPdo())) { + $this->reconnect(); + } + } + + /** + * Log a query in the connection's query log. + * + * @param string $query + * @param array $bindings + * @param float|null $time + * @return void + */ + public function logQuery($query, $bindings, $time = null) + { + if (isset($this->events)) { + $this->events->fire('illuminate.query', [$query, $bindings, $time, $this->getName()]); + } + + if (! $this->loggingQueries) { + return; + } + + $this->queryLog[] = compact('query', 'bindings', 'time'); + } + + /** + * Register a database query listener with the connection. + * + * @param \Closure $callback + * @return void + */ + public function listen(Closure $callback) + { + if (isset($this->events)) { + $this->events->listen('illuminate.query', $callback); + } + } + + /** + * Fire an event for this connection. + * + * @param string $event + * @return void + */ + protected function fireConnectionEvent($event) + { + if (isset($this->events)) { + $this->events->fire('connection.'.$this->getName().'.'.$event, $this); + } + } + + /** + * Get the elapsed time since a given starting point. + * + * @param int $start + * @return float + */ + protected function getElapsedTime($start) + { + return round((microtime(true) - $start) * 1000, 2); + } + + /** + * Is Doctrine available? + * + * @return bool + */ + public function isDoctrineAvailable() + { + return class_exists('Doctrine\DBAL\Connection'); + } + + /** + * Get a Doctrine Schema Column instance. + * + * @param string $table + * @param string $column + * @return \Doctrine\DBAL\Schema\Column + */ + public function getDoctrineColumn($table, $column) + { + $schema = $this->getDoctrineSchemaManager(); + + return $schema->listTableDetails($table)->getColumn($column); + } + + /** + * Get the Doctrine DBAL schema manager for the connection. + * + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getDoctrineSchemaManager() + { + return $this->getDoctrineDriver()->getSchemaManager($this->getDoctrineConnection()); + } + + /** + * Get the Doctrine DBAL database connection instance. + * + * @return \Doctrine\DBAL\Connection + */ + public function getDoctrineConnection() + { + if (is_null($this->doctrineConnection)) { + $driver = $this->getDoctrineDriver(); + + $data = ['pdo' => $this->pdo, 'dbname' => $this->getConfig('database')]; + + $this->doctrineConnection = new DoctrineConnection($data, $driver); + } + + return $this->doctrineConnection; + } + + /** + * Get the current PDO connection. + * + * @return \PDO + */ + public function getPdo() + { + return $this->pdo; + } + + /** + * Get the current PDO connection used for reading. + * + * @return \PDO + */ + public function getReadPdo() + { + if ($this->transactions >= 1) { + return $this->getPdo(); + } + + return $this->readPdo ?: $this->pdo; + } + + /** + * Set the PDO connection. + * + * @param \PDO|null $pdo + * @return $this + * + * @throws \RuntimeException + */ + public function setPdo($pdo) + { + if ($this->transactions >= 1) { + throw new RuntimeException("Can't swap PDO instance while within transaction."); + } + + $this->pdo = $pdo; + + return $this; + } + + /** + * Set the PDO connection used for reading. + * + * @param \PDO|null $pdo + * @return $this + */ + public function setReadPdo($pdo) + { + $this->readPdo = $pdo; + + return $this; + } + + /** + * Set the reconnect instance on the connection. + * + * @param callable $reconnector + * @return $this + */ + public function setReconnector(callable $reconnector) + { + $this->reconnector = $reconnector; + + return $this; + } + + /** + * Get the database connection name. + * + * @return string|null + */ + public function getName() + { + return $this->getConfig('name'); + } + + /** + * Get an option from the configuration options. + * + * @param string $option + * @return mixed + */ + public function getConfig($option) + { + return Arr::get($this->config, $option); + } + + /** + * Get the PDO driver name. + * + * @return string + */ + public function getDriverName() + { + return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + } + + /** + * Get the query grammar used by the connection. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + public function getQueryGrammar() + { + return $this->queryGrammar; + } + + /** + * Set the query grammar used by the connection. + * + * @param \Illuminate\Database\Query\Grammars\Grammar $grammar + * @return void + */ + public function setQueryGrammar(Query\Grammars\Grammar $grammar) + { + $this->queryGrammar = $grammar; + } + + /** + * Get the schema grammar used by the connection. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + public function getSchemaGrammar() + { + return $this->schemaGrammar; + } + + /** + * Set the schema grammar used by the connection. + * + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return void + */ + public function setSchemaGrammar(Schema\Grammars\Grammar $grammar) + { + $this->schemaGrammar = $grammar; + } + + /** + * Get the query post processor used by the connection. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + public function getPostProcessor() + { + return $this->postProcessor; + } + + /** + * Set the query post processor used by the connection. + * + * @param \Illuminate\Database\Query\Processors\Processor $processor + * @return void + */ + public function setPostProcessor(Processor $processor) + { + $this->postProcessor = $processor; + } + + /** + * Get the event dispatcher used by the connection. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public function getEventDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance on the connection. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return void + */ + public function setEventDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Determine if the connection in a "dry run". + * + * @return bool + */ + public function pretending() + { + return $this->pretending === true; + } + + /** + * Get the default fetch mode for the connection. + * + * @return int + */ + public function getFetchMode() + { + return $this->fetchMode; + } + + /** + * Set the default fetch mode for the connection. + * + * @param int $fetchMode + * @return int + */ + public function setFetchMode($fetchMode) + { + $this->fetchMode = $fetchMode; + } + + /** + * Get the connection query log. + * + * @return array + */ + public function getQueryLog() + { + return $this->queryLog; + } + + /** + * Clear the query log. + * + * @return void + */ + public function flushQueryLog() + { + $this->queryLog = []; + } + + /** + * Enable the query log on the connection. + * + * @return void + */ + public function enableQueryLog() + { + $this->loggingQueries = true; + } + + /** + * Disable the query log on the connection. + * + * @return void + */ + public function disableQueryLog() + { + $this->loggingQueries = false; + } + + /** + * Determine whether we're logging queries. + * + * @return bool + */ + public function logging() + { + return $this->loggingQueries; + } + + /** + * Get the name of the connected database. + * + * @return string + */ + public function getDatabaseName() + { + return $this->database; + } + + /** + * Set the name of the connected database. + * + * @param string $database + * @return string + */ + public function setDatabaseName($database) + { + $this->database = $database; + } + + /** + * Get the table prefix for the connection. + * + * @return string + */ + public function getTablePrefix() + { + return $this->tablePrefix; + } + + /** + * Set the table prefix in use by the connection. + * + * @param string $prefix + * @return void + */ + public function setTablePrefix($prefix) + { + $this->tablePrefix = $prefix; + + $this->getQueryGrammar()->setTablePrefix($prefix); + } + + /** + * Set the table prefix and return the grammar. + * + * @param \Illuminate\Database\Grammar $grammar + * @return \Illuminate\Database\Grammar + */ + public function withTablePrefix(Grammar $grammar) + { + $grammar->setTablePrefix($this->tablePrefix); + + return $grammar; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/ConnectionInterface.php b/core/vendor/laravel/framework/src/Illuminate/Database/ConnectionInterface.php new file mode 100644 index 0000000..16eb667 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/ConnectionInterface.php @@ -0,0 +1,149 @@ + $connection) { + $this->addConnection($name, $connection); + } + } + + /** + * Get a database connection instance. + * + * @param string $name + * @return \Illuminate\Database\ConnectionInterface + */ + public function connection($name = null) + { + if (is_null($name)) { + $name = $this->getDefaultConnection(); + } + + return $this->connections[$name]; + } + + /** + * Add a connection to the resolver. + * + * @param string $name + * @param \Illuminate\Database\ConnectionInterface $connection + * @return void + */ + public function addConnection($name, ConnectionInterface $connection) + { + $this->connections[$name] = $connection; + } + + /** + * Check if a connection has been registered. + * + * @param string $name + * @return bool + */ + public function hasConnection($name) + { + return isset($this->connections[$name]); + } + + /** + * Get the default connection name. + * + * @return string + */ + public function getDefaultConnection() + { + return $this->default; + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setDefaultConnection($name) + { + $this->default = $name; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/ConnectionResolverInterface.php b/core/vendor/laravel/framework/src/Illuminate/Database/ConnectionResolverInterface.php new file mode 100644 index 0000000..eb0397a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/ConnectionResolverInterface.php @@ -0,0 +1,29 @@ +container = $container; + } + + /** + * Establish a PDO connection based on the configuration. + * + * @param array $config + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function make(array $config, $name = null) + { + $config = $this->parseConfig($config, $name); + + if (isset($config['read'])) { + return $this->createReadWriteConnection($config); + } + + return $this->createSingleConnection($config); + } + + /** + * Create a single database connection instance. + * + * @param array $config + * @return \Illuminate\Database\Connection + */ + protected function createSingleConnection(array $config) + { + $pdo = $this->createConnector($config)->connect($config); + + return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config); + } + + /** + * Create a single database connection instance. + * + * @param array $config + * @return \Illuminate\Database\Connection + */ + protected function createReadWriteConnection(array $config) + { + $connection = $this->createSingleConnection($this->getWriteConfig($config)); + + return $connection->setReadPdo($this->createReadPdo($config)); + } + + /** + * Create a new PDO instance for reading. + * + * @param array $config + * @return \PDO + */ + protected function createReadPdo(array $config) + { + $readConfig = $this->getReadConfig($config); + + return $this->createConnector($readConfig)->connect($readConfig); + } + + /** + * Get the read configuration for a read / write connection. + * + * @param array $config + * @return array + */ + protected function getReadConfig(array $config) + { + $readConfig = $this->getReadWriteConfig($config, 'read'); + + if (isset($readConfig['host']) && is_array($readConfig['host'])) { + $readConfig['host'] = count($readConfig['host']) > 1 + ? $readConfig['host'][array_rand($readConfig['host'])] + : $readConfig['host'][0]; + } + + return $this->mergeReadWriteConfig($config, $readConfig); + } + + /** + * Get the read configuration for a read / write connection. + * + * @param array $config + * @return array + */ + protected function getWriteConfig(array $config) + { + $writeConfig = $this->getReadWriteConfig($config, 'write'); + + return $this->mergeReadWriteConfig($config, $writeConfig); + } + + /** + * Get a read / write level configuration. + * + * @param array $config + * @param string $type + * @return array + */ + protected function getReadWriteConfig(array $config, $type) + { + if (isset($config[$type][0])) { + return $config[$type][array_rand($config[$type])]; + } + + return $config[$type]; + } + + /** + * Merge a configuration for a read / write connection. + * + * @param array $config + * @param array $merge + * @return array + */ + protected function mergeReadWriteConfig(array $config, array $merge) + { + return Arr::except(array_merge($config, $merge), ['read', 'write']); + } + + /** + * Parse and prepare the database configuration. + * + * @param array $config + * @param string $name + * @return array + */ + protected function parseConfig(array $config, $name) + { + return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name); + } + + /** + * Create a connector instance based on the configuration. + * + * @param array $config + * @return \Illuminate\Database\Connectors\ConnectorInterface + * + * @throws \InvalidArgumentException + */ + public function createConnector(array $config) + { + if (! isset($config['driver'])) { + throw new InvalidArgumentException('A driver must be specified.'); + } + + if ($this->container->bound($key = "db.connector.{$config['driver']}")) { + return $this->container->make($key); + } + + switch ($config['driver']) { + case 'mysql': + return new MySqlConnector; + case 'pgsql': + return new PostgresConnector; + case 'sqlite': + return new SQLiteConnector; + case 'sqlsrv': + return new SqlServerConnector; + } + + throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]"); + } + + /** + * Create a new connection instance. + * + * @param string $driver + * @param \PDO $connection + * @param string $database + * @param string $prefix + * @param array $config + * @return \Illuminate\Database\Connection + * + * @throws \InvalidArgumentException + */ + protected function createConnection($driver, PDO $connection, $database, $prefix = '', array $config = []) + { + if ($this->container->bound($key = "db.connection.{$driver}")) { + return $this->container->make($key, [$connection, $database, $prefix, $config]); + } + + switch ($driver) { + case 'mysql': + return new MySqlConnection($connection, $database, $prefix, $config); + case 'pgsql': + return new PostgresConnection($connection, $database, $prefix, $config); + case 'sqlite': + return new SQLiteConnection($connection, $database, $prefix, $config); + case 'sqlsrv': + return new SqlServerConnection($connection, $database, $prefix, $config); + } + + throw new InvalidArgumentException("Unsupported driver [$driver]"); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php new file mode 100644 index 0000000..ea2637c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php @@ -0,0 +1,106 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + /** + * Get the PDO options based on the configuration. + * + * @param array $config + * @return array + */ + public function getOptions(array $config) + { + $options = Arr::get($config, 'options', []); + + return array_diff_key($this->options, $options) + $options; + } + + /** + * Create a new PDO connection. + * + * @param string $dsn + * @param array $config + * @param array $options + * @return \PDO + */ + public function createConnection($dsn, array $config, array $options) + { + $username = Arr::get($config, 'username'); + + $password = Arr::get($config, 'password'); + + try { + $pdo = new PDO($dsn, $username, $password, $options); + } catch (Exception $e) { + $pdo = $this->tryAgainIfCausedByLostConnection( + $e, $dsn, $username, $password, $options + ); + } + + return $pdo; + } + + /** + * Get the default PDO connection options. + * + * @return array + */ + public function getDefaultOptions() + { + return $this->options; + } + + /** + * Set the default PDO connection options. + * + * @param array $options + * @return void + */ + public function setDefaultOptions(array $options) + { + $this->options = $options; + } + + /** + * Handle a exception that occurred during connect execution. + * + * @param \Exception $e + * @param string $dsn + * @param string $username + * @param string $password + * @param array $options + * @return \PDO + * + * @throws \Exception + */ + protected function tryAgainIfCausedByLostConnection(Exception $e, $dsn, $username, $password, $options) + { + if ($this->causedByLostConnection($e)) { + return new PDO($dsn, $username, $password, $options); + } + + throw $e; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectorInterface.php b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectorInterface.php new file mode 100644 index 0000000..08597ac --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectorInterface.php @@ -0,0 +1,14 @@ +getDsn($config); + + $options = $this->getOptions($config); + + // We need to grab the PDO options that should be used while making the brand + // new connection instance. The PDO options control various aspects of the + // connection's behavior, and some might be specified by the developers. + $connection = $this->createConnection($dsn, $config, $options); + + if (isset($config['unix_socket'])) { + $connection->exec("use `{$config['database']}`;"); + } + + $collation = $config['collation']; + + // Next we will set the "names" and "collation" on the clients connections so + // a correct character set will be used by this client. The collation also + // is set on the server but needs to be set here on this client objects. + if (isset($config['charset'])) { + $charset = $config['charset']; + + $names = "set names '$charset'". + (! is_null($collation) ? " collate '$collation'" : ''); + + $connection->prepare($names)->execute(); + } + // Next, we will check to see if a timezone has been specified in this config + // and if it has we will issue a statement to modify the timezone with the + // database. Setting this DB timezone is an optional configuration item. + if (isset($config['timezone'])) { + $connection->prepare( + 'set time_zone="'.$config['timezone'].'"' + )->execute(); + } + + // If the "strict" option has been configured for the connection we will setup + // strict mode for this session. Strict mode enforces some extra rules when + // using the MySQL database system and is a quicker way to enforce them. + if (isset($config['strict'])) { + if ($config['strict']) { + $connection->prepare("set session sql_mode='STRICT_ALL_TABLES'")->execute(); + } else { + $connection->prepare("set session sql_mode=''")->execute(); + } + } + + return $connection; + } + + /** + * Create a DSN string from a configuration. + * + * Chooses socket or host/port based on the 'unix_socket' config value. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + return $this->configHasSocket($config) ? $this->getSocketDsn($config) : $this->getHostDsn($config); + } + + /** + * Determine if the given configuration array has a UNIX socket value. + * + * @param array $config + * @return bool + */ + protected function configHasSocket(array $config) + { + return isset($config['unix_socket']) && ! empty($config['unix_socket']); + } + + /** + * Get the DSN string for a socket configuration. + * + * @param array $config + * @return string + */ + protected function getSocketDsn(array $config) + { + return "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}"; + } + + /** + * Get the DSN string for a host / port configuration. + * + * @param array $config + * @return string + */ + protected function getHostDsn(array $config) + { + extract($config, EXTR_SKIP); + + return isset($port) + ? "mysql:host={$host};port={$port};dbname={$database}" + : "mysql:host={$host};dbname={$database}"; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/PostgresConnector.php b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/PostgresConnector.php new file mode 100644 index 0000000..9371c8d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/PostgresConnector.php @@ -0,0 +1,117 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * Establish a database connection. + * + * @param array $config + * @return \PDO + */ + public function connect(array $config) + { + // First we'll create the basic DSN and connection instance connecting to the + // using the configuration option specified by the developer. We will also + // set the default character set on the connections to UTF-8 by default. + $dsn = $this->getDsn($config); + + $options = $this->getOptions($config); + + $connection = $this->createConnection($dsn, $config, $options); + + $charset = $config['charset']; + + $connection->prepare("set names '$charset'")->execute(); + + // Next, we will check to see if a timezone has been specified in this config + // and if it has we will issue a statement to modify the timezone with the + // database. Setting this DB timezone is an optional configuration item. + if (isset($config['timezone'])) { + $timezone = $config['timezone']; + + $connection->prepare("set time zone '$timezone'")->execute(); + } + + // Unlike MySQL, Postgres allows the concept of "schema" and a default schema + // may have been specified on the connections. If that is the case we will + // set the default schema search paths to the specified database schema. + if (isset($config['schema'])) { + $schema = $this->formatSchema($config['schema']); + + $connection->prepare("set search_path to {$schema}")->execute(); + } + + // Postgres allows an application_name to be set by the user and this name is + // used to when monitoring the application with pg_stat_activity. So we'll + // determine if the option has been specified and run a statement if so. + if (isset($config['application_name'])) { + $applicationName = $config['application_name']; + + $connection->prepare("set application_name to '$applicationName'")->execute(); + } + + return $connection; + } + + /** + * Create a DSN string from a configuration. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + // First we will create the basic DSN setup as well as the port if it is in + // in the configuration options. This will give us the basic DSN we will + // need to establish the PDO connections and return them back for use. + extract($config, EXTR_SKIP); + + $host = isset($host) ? "host={$host};" : ''; + + $dsn = "pgsql:{$host}dbname={$database}"; + + // If a port was specified, we will add it to this Postgres DSN connections + // format. Once we have done that we are ready to return this connection + // string back out for usage, as this has been fully constructed here. + if (isset($config['port'])) { + $dsn .= ";port={$port}"; + } + + if (isset($config['sslmode'])) { + $dsn .= ";sslmode={$sslmode}"; + } + + return $dsn; + } + + /** + * Format the schema for the DSN. + * + * @param array|string $schema + * @return string + */ + protected function formatSchema($schema) + { + if (is_array($schema)) { + return '"'.implode('", "', $schema).'"'; + } else { + return '"'.$schema.'"'; + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php new file mode 100644 index 0000000..28f9091 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php @@ -0,0 +1,39 @@ +getOptions($config); + + // SQLite supports "in-memory" databases that only last as long as the owning + // connection does. These are useful for tests or for short lifetime store + // querying. In-memory databases may only have a single open connection. + if ($config['database'] == ':memory:') { + return $this->createConnection('sqlite::memory:', $config, $options); + } + + $path = realpath($config['database']); + + // Here we'll verify that the SQLite database exists before going any further + // as the developer probably wants to know if the database exists and this + // SQLite driver will not throw any exception if it does not by default. + if ($path === false) { + throw new InvalidArgumentException("Database (${config['database']}) does not exist."); + } + + return $this->createConnection("sqlite:{$path}", $config, $options); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/SqlServerConnector.php b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/SqlServerConnector.php new file mode 100644 index 0000000..46e0b84 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Connectors/SqlServerConnector.php @@ -0,0 +1,137 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * Establish a database connection. + * + * @param array $config + * @return \PDO + */ + public function connect(array $config) + { + $options = $this->getOptions($config); + + return $this->createConnection($this->getDsn($config), $config, $options); + } + + /** + * Create a DSN string from a configuration. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + // First we will create the basic DSN setup as well as the port if it is in + // in the configuration options. This will give us the basic DSN we will + // need to establish the PDO connections and return them back for use. + if (in_array('dblib', $this->getAvailableDrivers())) { + return $this->getDblibDsn($config); + } else { + return $this->getSqlSrvDsn($config); + } + } + + /** + * Get the DSN string for a DbLib connection. + * + * @param array $config + * @return string + */ + protected function getDblibDsn(array $config) + { + $arguments = [ + 'host' => $this->buildHostString($config, ':'), + 'dbname' => $config['database'], + ]; + + $arguments = array_merge( + $arguments, Arr::only($config, ['appname', 'charset']) + ); + + return $this->buildConnectString('dblib', $arguments); + } + + /** + * Get the DSN string for a SqlSrv connection. + * + * @param array $config + * @return string + */ + protected function getSqlSrvDsn(array $config) + { + $arguments = [ + 'Server' => $this->buildHostString($config, ','), + ]; + + if (isset($config['database'])) { + $arguments['Database'] = $config['database']; + } + + if (isset($config['appname'])) { + $arguments['APP'] = $config['appname']; + } + + return $this->buildConnectString('sqlsrv', $arguments); + } + + /** + * Build a connection string from the given arguments. + * + * @param string $driver + * @param array $arguments + * @return string + */ + protected function buildConnectString($driver, array $arguments) + { + $options = array_map(function ($key) use ($arguments) { + return sprintf('%s=%s', $key, $arguments[$key]); + }, array_keys($arguments)); + + return $driver.':'.implode(';', $options); + } + + /** + * Build a host string from the given configuration. + * + * @param array $config + * @param string $separator + * @return string + */ + protected function buildHostString(array $config, $separator) + { + if (isset($config['port'])) { + return $config['host'].$separator.$config['port']; + } else { + return $config['host']; + } + } + + /** + * Get the available PDO drivers. + * + * @return array + */ + protected function getAvailableDrivers() + { + return PDO::getAvailableDrivers(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/BaseCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/BaseCommand.php new file mode 100644 index 0000000..f3459ac --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/BaseCommand.php @@ -0,0 +1,18 @@ +laravel->databasePath().'/migrations'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/InstallCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/InstallCommand.php new file mode 100644 index 0000000..103dcaa --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/InstallCommand.php @@ -0,0 +1,70 @@ +repository = $repository; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->repository->setSource($this->input->getOption('database')); + + $this->repository->createRepository(); + + $this->info('Migration table created successfully.'); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php new file mode 100644 index 0000000..920d417 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php @@ -0,0 +1,126 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + if (! $this->confirmToProceed()) { + return; + } + + $this->prepareDatabase(); + + // The pretend option can be used for "simulating" the migration and grabbing + // the SQL queries that would fire if the migration were to be run against + // a database for real, which is helpful for double checking migrations. + $pretend = $this->input->getOption('pretend'); + + // Next, we will check to see if a path option has been defined. If it has + // we will use the path relative to the root of this installation folder + // so that migrations may be run for any path within the applications. + if (! is_null($path = $this->input->getOption('path'))) { + $path = $this->laravel->basePath().'/'.$path; + } else { + $path = $this->getMigrationPath(); + } + + $this->migrator->run($path, $pretend); + + // Once the migrator has run we will grab the note output and send it out to + // the console screen, since the migrator itself functions without having + // any instances of the OutputInterface contract passed into the class. + foreach ($this->migrator->getNotes() as $note) { + $this->output->writeln($note); + } + + // Finally, if the "seed" option has been given, we will re-run the database + // seed task to re-populate the database, which is convenient when adding + // a migration and a seed at the same time, as it is only this command. + if ($this->input->getOption('seed')) { + $this->call('db:seed', ['--force' => true]); + } + } + + /** + * Prepare the migration database for running. + * + * @return void + */ + protected function prepareDatabase() + { + $this->migrator->setConnection($this->input->getOption('database')); + + if (! $this->migrator->repositoryExists()) { + $options = ['--database' => $this->input->getOption('database')]; + + $this->call('migrate:install', $options); + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'], + + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'], + + ['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to be executed.'], + + ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'], + + ['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php new file mode 100644 index 0000000..8c78790 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php @@ -0,0 +1,114 @@ +creator = $creator; + $this->composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + // It's possible for the developer to specify the tables to modify in this + // schema operation. The developer may also specify if this table needs + // to be freshly created so we can create the appropriate migrations. + $name = trim($this->input->getArgument('name')); + + $table = $this->input->getOption('table'); + + $create = $this->input->getOption('create'); + + if (! $table && is_string($create)) { + $table = $create; + } + + // Now we are ready to write the migration out to disk. Once we've written + // the migration out, we will dump-autoload for the entire framework to + // make sure that the migrations are registered by the class loaders. + $this->writeMigration($name, $table, $create); + + $this->composer->dumpAutoloads(); + } + + /** + * Write the migration file to disk. + * + * @param string $name + * @param string $table + * @param bool $create + * @return string + */ + protected function writeMigration($name, $table, $create) + { + $path = $this->getMigrationPath(); + + $file = pathinfo($this->creator->create($name, $path, $table, $create), PATHINFO_FILENAME); + + $this->line("Created Migration: $file"); + } + + /** + * Get migration path (either specified by '--path' option or default location). + * + * @return string + */ + protected function getMigrationPath() + { + if (! is_null($targetPath = $this->input->getOption('path'))) { + return $this->laravel->basePath().'/'.$targetPath; + } + + return parent::getMigrationPath(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RefreshCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RefreshCommand.php new file mode 100644 index 0000000..ce4e8f1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RefreshCommand.php @@ -0,0 +1,108 @@ +confirmToProceed()) { + return; + } + + $database = $this->input->getOption('database'); + + $force = $this->input->getOption('force'); + + $path = $this->input->getOption('path'); + + $this->call('migrate:reset', [ + '--database' => $database, '--force' => $force, + ]); + + // The refresh command is essentially just a brief aggregate of a few other of + // the migration commands and just provides a convenient wrapper to execute + // them in succession. We'll also see if we need to re-seed the database. + $this->call('migrate', [ + '--database' => $database, + '--force' => $force, + '--path' => $path, + ]); + + if ($this->needsSeeding()) { + $this->runSeeder($database); + } + } + + /** + * Determine if the developer has requested database seeding. + * + * @return bool + */ + protected function needsSeeding() + { + return $this->option('seed') || $this->option('seeder'); + } + + /** + * Run the database seeder command. + * + * @param string $database + * @return void + */ + protected function runSeeder($database) + { + $class = $this->option('seeder') ?: 'DatabaseSeeder'; + + $force = $this->input->getOption('force'); + + $this->call('db:seed', [ + '--database' => $database, '--class' => $class, '--force' => $force, + ]); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'], + + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'], + + ['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to be executed.'], + + ['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'], + + ['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php new file mode 100644 index 0000000..8871d3d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php @@ -0,0 +1,94 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + if (! $this->confirmToProceed()) { + return; + } + + $this->migrator->setConnection($this->input->getOption('database')); + + if (! $this->migrator->repositoryExists()) { + $this->output->writeln('Migration table not found.'); + + return; + } + + $pretend = $this->input->getOption('pretend'); + + $this->migrator->reset($pretend); + + // Once the migrator has run we will grab the note output and send it out to + // the console screen, since the migrator itself functions without having + // any instances of the OutputInterface contract passed into the class. + foreach ($this->migrator->getNotes() as $note) { + $this->output->writeln($note); + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'], + + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'], + + ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php new file mode 100644 index 0000000..a341b4f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php @@ -0,0 +1,88 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + if (! $this->confirmToProceed()) { + return; + } + + $this->migrator->setConnection($this->input->getOption('database')); + + $pretend = $this->input->getOption('pretend'); + + $this->migrator->rollback($pretend); + + // Once the migrator has run we will grab the note output and send it out to + // the console screen, since the migrator itself functions without having + // any instances of the OutputInterface contract passed into the class. + foreach ($this->migrator->getNotes() as $note) { + $this->output->writeln($note); + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'], + + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'], + + ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/StatusCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/StatusCommand.php new file mode 100644 index 0000000..aba7acf --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/StatusCommand.php @@ -0,0 +1,102 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + if (! $this->migrator->repositoryExists()) { + return $this->error('No migrations found.'); + } + + $this->migrator->setConnection($this->input->getOption('database')); + + if (! is_null($path = $this->input->getOption('path'))) { + $path = $this->laravel->basePath().'/'.$path; + } else { + $path = $this->getMigrationPath(); + } + + $ran = $this->migrator->getRepository()->getRan(); + + $migrations = []; + + foreach ($this->getAllMigrationFiles($path) as $migration) { + $migrations[] = in_array($migration, $ran) ? ['Y', $migration] : ['N', $migration]; + } + + if (count($migrations) > 0) { + $this->table(['Ran?', 'Migration'], $migrations); + } else { + $this->error('No migrations found'); + } + } + + /** + * Get all of the migration files. + * + * @param string $path + * @return array + */ + protected function getAllMigrationFiles($path) + { + return $this->migrator->getMigrationFiles($path); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'], + + ['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to use.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php new file mode 100644 index 0000000..f74164b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php @@ -0,0 +1,103 @@ +resolver = $resolver; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + if (! $this->confirmToProceed()) { + return; + } + + $this->resolver->setDefaultConnection($this->getDatabase()); + + $this->getSeeder()->run(); + } + + /** + * Get a seeder instance from the container. + * + * @return \Illuminate\Database\Seeder + */ + protected function getSeeder() + { + $class = $this->laravel->make($this->input->getOption('class')); + + return $class->setContainer($this->laravel)->setCommand($this); + } + + /** + * Get the name of the database connection to use. + * + * @return string + */ + protected function getDatabase() + { + $database = $this->input->getOption('database'); + + return $database ?: $this->laravel['config']['database.default']; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'DatabaseSeeder'], + + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'], + + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeederMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeederMakeCommand.php new file mode 100644 index 0000000..06a83c1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeederMakeCommand.php @@ -0,0 +1,96 @@ +composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + parent::fire(); + + $this->composer->dumpAutoloads(); + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + return __DIR__.'/stubs/seeder.stub'; + } + + /** + * Get the destination class path. + * + * @param string $name + * @return string + */ + protected function getPath($name) + { + return $this->laravel->databasePath().'/seeds/'.$name.'.php'; + } + + /** + * Parse the name and format according to the root namespace. + * + * @param string $name + * @return string + */ + protected function parseName($name) + { + return $name; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/stubs/seeder.stub b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/stubs/seeder.stub new file mode 100644 index 0000000..4aa3845 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/stubs/seeder.stub @@ -0,0 +1,16 @@ +app = $app; + $this->factory = $factory; + } + + /** + * Get a database connection instance. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function connection($name = null) + { + list($name, $type) = $this->parseConnectionName($name); + + // If we haven't created this connection, we'll create it based on the config + // provided in the application. Once we've created the connections we will + // set the "fetch mode" for PDO which determines the query return types. + if (! isset($this->connections[$name])) { + $connection = $this->makeConnection($name); + + $this->setPdoForType($connection, $type); + + $this->connections[$name] = $this->prepare($connection); + } + + return $this->connections[$name]; + } + + /** + * Parse the connection into an array of the name and read / write type. + * + * @param string $name + * @return array + */ + protected function parseConnectionName($name) + { + $name = $name ?: $this->getDefaultConnection(); + + return Str::endsWith($name, ['::read', '::write']) + ? explode('::', $name, 2) : [$name, null]; + } + + /** + * Disconnect from the given database and remove from local cache. + * + * @param string $name + * @return void + */ + public function purge($name = null) + { + $this->disconnect($name); + + unset($this->connections[$name]); + } + + /** + * Disconnect from the given database. + * + * @param string $name + * @return void + */ + public function disconnect($name = null) + { + if (isset($this->connections[$name = $name ?: $this->getDefaultConnection()])) { + $this->connections[$name]->disconnect(); + } + } + + /** + * Reconnect to the given database. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function reconnect($name = null) + { + $this->disconnect($name = $name ?: $this->getDefaultConnection()); + + if (! isset($this->connections[$name])) { + return $this->connection($name); + } + + return $this->refreshPdoConnections($name); + } + + /** + * Refresh the PDO connections on a given connection. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + protected function refreshPdoConnections($name) + { + $fresh = $this->makeConnection($name); + + return $this->connections[$name] + ->setPdo($fresh->getPdo()) + ->setReadPdo($fresh->getReadPdo()); + } + + /** + * Make the database connection instance. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + protected function makeConnection($name) + { + $config = $this->getConfig($name); + + // First we will check by the connection name to see if an extension has been + // registered specifically for that connection. If it has we will call the + // Closure and pass it the config allowing it to resolve the connection. + if (isset($this->extensions[$name])) { + return call_user_func($this->extensions[$name], $config, $name); + } + + $driver = $config['driver']; + + // Next we will check to see if an extension has been registered for a driver + // and will call the Closure if so, which allows us to have a more generic + // resolver for the drivers themselves which applies to all connections. + if (isset($this->extensions[$driver])) { + return call_user_func($this->extensions[$driver], $config, $name); + } + + return $this->factory->make($config, $name); + } + + /** + * Prepare the database connection instance. + * + * @param \Illuminate\Database\Connection $connection + * @return \Illuminate\Database\Connection + */ + protected function prepare(Connection $connection) + { + $connection->setFetchMode($this->app['config']['database.fetch']); + + if ($this->app->bound('events')) { + $connection->setEventDispatcher($this->app['events']); + } + + // Here we'll set a reconnector callback. This reconnector can be any callable + // so we will set a Closure to reconnect from this manager with the name of + // the connection, which will allow us to reconnect from the connections. + $connection->setReconnector(function ($connection) { + $this->reconnect($connection->getName()); + }); + + return $connection; + } + + /** + * Prepare the read write mode for database connection instance. + * + * @param \Illuminate\Database\Connection $connection + * @param string $type + * @return \Illuminate\Database\Connection + */ + protected function setPdoForType(Connection $connection, $type = null) + { + if ($type == 'read') { + $connection->setPdo($connection->getReadPdo()); + } elseif ($type == 'write') { + $connection->setReadPdo($connection->getPdo()); + } + + return $connection; + } + + /** + * Get the configuration for a connection. + * + * @param string $name + * @return array + * + * @throws \InvalidArgumentException + */ + protected function getConfig($name) + { + $name = $name ?: $this->getDefaultConnection(); + + // To get the database connection configuration, we will just pull each of the + // connection configurations and get the configurations for the given name. + // If the configuration doesn't exist, we'll throw an exception and bail. + $connections = $this->app['config']['database.connections']; + + if (is_null($config = Arr::get($connections, $name))) { + throw new InvalidArgumentException("Database [$name] not configured."); + } + + return $config; + } + + /** + * Get the default connection name. + * + * @return string + */ + public function getDefaultConnection() + { + return $this->app['config']['database.default']; + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setDefaultConnection($name) + { + $this->app['config']['database.default'] = $name; + } + + /** + * Register an extension connection resolver. + * + * @param string $name + * @param callable $resolver + * @return void + */ + public function extend($name, callable $resolver) + { + $this->extensions[$name] = $resolver; + } + + /** + * Return all of the created connections. + * + * @return array + */ + public function getConnections() + { + return $this->connections; + } + + /** + * Dynamically pass methods to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->connection(), $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php new file mode 100644 index 0000000..64d9d4b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php @@ -0,0 +1,88 @@ +app['db']); + + Model::setEventDispatcher($this->app['events']); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + Model::clearBootedModels(); + + $this->registerEloquentFactory(); + + $this->registerQueueableEntityResolver(); + + // The connection factory is used to create the actual connection instances on + // the database. We will inject the factory into the manager so that it may + // make the connections while they are actually needed and not of before. + $this->app->singleton('db.factory', function ($app) { + return new ConnectionFactory($app); + }); + + // The database manager is used to resolve various connections, since multiple + // connections might be managed. It also implements the connection resolver + // interface which may be used by other components requiring connections. + $this->app->singleton('db', function ($app) { + return new DatabaseManager($app, $app['db.factory']); + }); + + $this->app->bind('db.connection', function ($app) { + return $app['db']->connection(); + }); + } + + /** + * Register the Eloquent factory instance in the container. + * + * @return void + */ + protected function registerEloquentFactory() + { + $this->app->singleton(FakerGenerator::class, function () { + return FakerFactory::create(); + }); + + $this->app->singleton(EloquentFactory::class, function ($app) { + $faker = $app->make(FakerGenerator::class); + + return EloquentFactory::construct($faker, database_path('factories')); + }); + } + + /** + * Register the queueable entity resolver implementation. + * + * @return void + */ + protected function registerQueueableEntityResolver() + { + $this->app->singleton('Illuminate\Contracts\Queue\EntityResolver', function () { + return new QueueEntityResolver; + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/DetectsLostConnections.php b/core/vendor/laravel/framework/src/Illuminate/Database/DetectsLostConnections.php new file mode 100644 index 0000000..bee3482 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/DetectsLostConnections.php @@ -0,0 +1,33 @@ +getMessage(); + + return Str::contains($message, [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + ]); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php new file mode 100644 index 0000000..5e45ba7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php @@ -0,0 +1,1029 @@ +query = $query; + } + + /** + * Find a model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null + */ + public function find($id, $columns = ['*']) + { + if (is_array($id)) { + return $this->findMany($id, $columns); + } + + $this->query->where($this->model->getQualifiedKeyName(), '=', $id); + + return $this->first($columns); + } + + /** + * Find a model by its primary key. + * + * @param array $ids + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function findMany($ids, $columns = ['*']) + { + if (empty($ids)) { + return $this->model->newCollection(); + } + + $this->query->whereIn($this->model->getQualifiedKeyName(), $ids); + + return $this->get($columns); + } + + /** + * Find a model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function findOrFail($id, $columns = ['*']) + { + $result = $this->find($id, $columns); + + if (is_array($id)) { + if (count($result) == count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->model)); + } + + /** + * Find a model by its primary key or return fresh model instance. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model + */ + public function findOrNew($id, $columns = ['*']) + { + if (! is_null($model = $this->find($id, $columns))) { + return $model; + } + + return $this->model->newInstance(); + } + + /** + * Get the first record matching the attributes or instantiate it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrNew(array $attributes) + { + if (! is_null($instance = $this->where($attributes)->first())) { + return $instance; + } + + return $this->model->newInstance($attributes); + } + + /** + * Get the first record matching the attributes or create it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrCreate(array $attributes) + { + if (! is_null($instance = $this->where($attributes)->first())) { + return $instance; + } + + $instance = $this->model->newInstance($attributes); + + $instance->save(); + + return $instance; + } + + /** + * Create or update a record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $attributes, array $values = []) + { + $instance = $this->firstOrNew($attributes); + + $instance->fill($values)->save(); + + return $instance; + } + + /** + * Execute the query and get the first result. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static|null + */ + public function first($columns = ['*']) + { + return $this->take(1)->get($columns)->first(); + } + + /** + * Execute the query and get the first result or throw an exception. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function firstOrFail($columns = ['*']) + { + if (! is_null($model = $this->first($columns))) { + return $model; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->model)); + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection|static[] + */ + public function get($columns = ['*']) + { + $models = $this->getModels($columns); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded, which will solve the + // n+1 query issue for the developers to avoid running a lot of queries. + if (count($models) > 0) { + $models = $this->eagerLoadRelations($models); + } + + return $this->model->newCollection($models); + } + + /** + * Get a single column's value from the first result of a query. + * + * @param string $column + * @return mixed + */ + public function value($column) + { + $result = $this->first([$column]); + + if ($result) { + return $result->{$column}; + } + } + + /** + * Get a single column's value from the first result of a query. + * + * This is an alias for the "value" method. + * + * @param string $column + * @return mixed + * + * @deprecated since version 5.1. + */ + public function pluck($column) + { + return $this->value($column); + } + + /** + * Chunk the results of the query. + * + * @param int $count + * @param callable $callback + * @return bool + */ + public function chunk($count, callable $callback) + { + $results = $this->forPage($page = 1, $count)->get(); + + while (count($results) > 0) { + // On each chunk result set, we will pass them to the callback and then let the + // developer take care of everything within the callback, which allows us to + // keep the memory low for spinning through large result sets for working. + if (call_user_func($callback, $results) === false) { + return false; + } + + $page++; + + $results = $this->forPage($page, $count)->get(); + } + + return true; + } + + /** + * Get an array with the values of a given column. + * + * @param string $column + * @param string|null $key + * @return \Illuminate\Support\Collection + */ + public function lists($column, $key = null) + { + $results = $this->query->lists($column, $key); + + // If the model has a mutator for the requested column, we will spin through + // the results and mutate the values so that the mutated version of these + // columns are returned as you would expect from these Eloquent models. + if ($this->model->hasGetMutator($column)) { + foreach ($results as $key => &$value) { + $fill = [$column => $value]; + + $value = $this->model->newFromBuilder($fill)->$column; + } + } + + return collect($results); + } + + /** + * Paginate the given query. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * + * @throws \InvalidArgumentException + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $total = $this->query->getCountForPagination(); + + $this->query->forPage( + $page = $page ?: Paginator::resolveCurrentPage($pageName), + $perPage = $perPage ?: $this->model->getPerPage() + ); + + return new LengthAwarePaginator($this->get($columns), $total, $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page') + { + $page = Paginator::resolveCurrentPage($pageName); + + $perPage = $perPage ?: $this->model->getPerPage(); + + $this->skip(($page - 1) * $perPage)->take($perPage + 1); + + return new Paginator($this->get($columns), $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } + + /** + * Update a record in the database. + * + * @param array $values + * @return int + */ + public function update(array $values) + { + return $this->query->update($this->addUpdatedAtColumn($values)); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + public function increment($column, $amount = 1, array $extra = []) + { + $extra = $this->addUpdatedAtColumn($extra); + + return $this->query->increment($column, $amount, $extra); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + public function decrement($column, $amount = 1, array $extra = []) + { + $extra = $this->addUpdatedAtColumn($extra); + + return $this->query->decrement($column, $amount, $extra); + } + + /** + * Add the "updated at" column to an array of values. + * + * @param array $values + * @return array + */ + protected function addUpdatedAtColumn(array $values) + { + if (! $this->model->usesTimestamps()) { + return $values; + } + + $column = $this->model->getUpdatedAtColumn(); + + return Arr::add($values, $column, $this->model->freshTimestampString()); + } + + /** + * Delete a record from the database. + * + * @return mixed + */ + public function delete() + { + if (isset($this->onDelete)) { + return call_user_func($this->onDelete, $this); + } + + return $this->query->delete(); + } + + /** + * Run the default delete function on the builder. + * + * @return mixed + */ + public function forceDelete() + { + return $this->query->delete(); + } + + /** + * Register a replacement for the default delete function. + * + * @param \Closure $callback + * @return void + */ + public function onDelete(Closure $callback) + { + $this->onDelete = $callback; + } + + /** + * Get the hydrated models without eager loading. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model[] + */ + public function getModels($columns = ['*']) + { + $results = $this->query->get($columns); + + $connection = $this->model->getConnectionName(); + + return $this->model->hydrate($results, $connection)->all(); + } + + /** + * Eager load the relationships for the models. + * + * @param array $models + * @return array + */ + public function eagerLoadRelations(array $models) + { + foreach ($this->eagerLoad as $name => $constraints) { + // For nested eager loads we'll skip loading them here and they will be set as an + // eager load on the query to retrieve the relation so that they will be eager + // loaded on that query, because that is where they get hydrated as models. + if (strpos($name, '.') === false) { + $models = $this->loadRelation($models, $name, $constraints); + } + } + + return $models; + } + + /** + * Eagerly load the relationship on a set of models. + * + * @param array $models + * @param string $name + * @param \Closure $constraints + * @return array + */ + protected function loadRelation(array $models, $name, Closure $constraints) + { + // First we will "back up" the existing where conditions on the query so we can + // add our eager constraints. Then we will merge the wheres that were on the + // query back to it in order that any where conditions might be specified. + $relation = $this->getRelation($name); + + $relation->addEagerConstraints($models); + + call_user_func($constraints, $relation); + + $models = $relation->initRelation($models, $name); + + // Once we have the results, we just match those back up to their parent models + // using the relationship instance. Then we just return the finished arrays + // of models which have been eagerly hydrated and are readied for return. + $results = $relation->getEager(); + + return $relation->match($models, $results, $name); + } + + /** + * Get the relation instance for the given relation name. + * + * @param string $name + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function getRelation($name) + { + // We want to run a relationship query without any constrains so that we will + // not have to remove these where clauses manually which gets really hacky + // and is error prone while we remove the developer's own where clauses. + $relation = Relation::noConstraints(function () use ($name) { + return $this->getModel()->$name(); + }); + + $nested = $this->nestedRelations($name); + + // If there are nested relationships set on the query, we will put those onto + // the query instances so that they can be handled after this relationship + // is loaded. In this way they will all trickle down as they are loaded. + if (count($nested) > 0) { + $relation->getQuery()->with($nested); + } + + return $relation; + } + + /** + * Get the deeply nested relations for a given top-level relation. + * + * @param string $relation + * @return array + */ + protected function nestedRelations($relation) + { + $nested = []; + + // We are basically looking for any relationships that are nested deeper than + // the given top-level relationship. We will just check for any relations + // that start with the given top relations and adds them to our arrays. + foreach ($this->eagerLoad as $name => $constraints) { + if ($this->isNested($name, $relation)) { + $nested[substr($name, strlen($relation.'.'))] = $constraints; + } + } + + return $nested; + } + + /** + * Determine if the relationship is nested. + * + * @param string $name + * @param string $relation + * @return bool + */ + protected function isNested($name, $relation) + { + $dots = Str::contains($name, '.'); + + return $dots && Str::startsWith($name, $relation.'.'); + } + + /** + * Add a basic where clause to the query. + * + * @param string $column + * @param string $operator + * @param mixed $value + * @param string $boolean + * @return $this + */ + public function where($column, $operator = null, $value = null, $boolean = 'and') + { + if ($column instanceof Closure) { + $query = $this->model->newQueryWithoutScopes(); + + call_user_func($column, $query); + + $this->query->addNestedWhereQuery($query->getQuery(), $boolean); + } else { + call_user_func_array([$this->query, 'where'], func_get_args()); + } + + return $this; + } + + /** + * Add an "or where" clause to the query. + * + * @param string $column + * @param string $operator + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhere($column, $operator = null, $value = null) + { + return $this->where($column, $operator, $value, 'or'); + } + + /** + * Add a relationship count condition to the query. + * + * @param string $relation + * @param string $operator + * @param int $count + * @param string $boolean + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) + { + if (strpos($relation, '.') !== false) { + return $this->hasNested($relation, $operator, $count, $boolean, $callback); + } + + $relation = $this->getHasRelationQuery($relation); + + $query = $relation->getRelationCountQuery($relation->getRelated()->newQuery(), $this); + + if ($callback) { + call_user_func($callback, $query); + } + + return $this->addHasWhere($query, $relation, $operator, $count, $boolean); + } + + /** + * Add nested relationship count conditions to the query. + * + * @param string $relations + * @param string $operator + * @param int $count + * @param string $boolean + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) + { + $relations = explode('.', $relations); + + // In order to nest "has", we need to add count relation constraints on the + // callback Closure. We'll do this by simply passing the Closure its own + // reference to itself so it calls itself recursively on each segment. + $closure = function ($q) use (&$closure, &$relations, $operator, $count, $boolean, $callback) { + if (count($relations) > 1) { + $q->whereHas(array_shift($relations), $closure); + } else { + $q->has(array_shift($relations), $operator, $count, 'and', $callback); + } + }; + + return $this->has(array_shift($relations), '>=', 1, $boolean, $closure); + } + + /** + * Add a relationship count condition to the query. + * + * @param string $relation + * @param string $boolean + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function doesntHave($relation, $boolean = 'and', Closure $callback = null) + { + return $this->has($relation, '<', 1, $boolean, $callback); + } + + /** + * Add a relationship count condition to the query with where clauses. + * + * @param string $relation + * @param \Closure $callback + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'and', $callback); + } + + /** + * Add a relationship count condition to the query with where clauses. + * + * @param string $relation + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereDoesntHave($relation, Closure $callback = null) + { + return $this->doesntHave($relation, 'and', $callback); + } + + /** + * Add a relationship count condition to the query with an "or". + * + * @param string $relation + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orHas($relation, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'or'); + } + + /** + * Add a relationship count condition to the query with where clauses and an "or". + * + * @param string $relation + * @param \Closure $callback + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'or', $callback); + } + + /** + * Add the "has" condition where clause to the query. + * + * @param \Illuminate\Database\Eloquent\Builder $hasQuery + * @param \Illuminate\Database\Eloquent\Relations\Relation $relation + * @param string $operator + * @param int $count + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean) + { + $this->mergeWheresToHas($hasQuery, $relation); + + if (is_numeric($count)) { + $count = new Expression($count); + } + + return $this->where(new Expression('('.$hasQuery->toSql().')'), $operator, $count, $boolean); + } + + /** + * Merge the "wheres" from a relation query to a has query. + * + * @param \Illuminate\Database\Eloquent\Builder $hasQuery + * @param \Illuminate\Database\Eloquent\Relations\Relation $relation + * @return void + */ + protected function mergeWheresToHas(Builder $hasQuery, Relation $relation) + { + // Here we have the "has" query and the original relation. We need to copy over any + // where clauses the developer may have put in the relationship function over to + // the has query, and then copy the bindings from the "has" query to the main. + $relationQuery = $relation->getBaseQuery(); + + $hasQuery = $hasQuery->getModel()->removeGlobalScopes($hasQuery); + + $whereBindings = array_get($relationQuery->getRawBindings(), 'where', []); + + $hasQuery->mergeWheres($relationQuery->wheres, $whereBindings); + + $this->query->addBinding($hasQuery->getQuery()->getBindings(), 'where'); + } + + /** + * Get the "has relation" base query instance. + * + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + protected function getHasRelationQuery($relation) + { + return Relation::noConstraints(function () use ($relation) { + return $this->getModel()->$relation(); + }); + } + + /** + * Set the relationships that should be eager loaded. + * + * @param mixed $relations + * @return $this + */ + public function with($relations) + { + if (is_string($relations)) { + $relations = func_get_args(); + } + + $eagers = $this->parseRelations($relations); + + $this->eagerLoad = array_merge($this->eagerLoad, $eagers); + + return $this; + } + + /** + * Parse a list of relations into individuals. + * + * @param array $relations + * @return array + */ + protected function parseRelations(array $relations) + { + $results = []; + + foreach ($relations as $name => $constraints) { + // If the "relation" value is actually a numeric key, we can assume that no + // constraints have been specified for the eager load and we'll just put + // an empty Closure with the loader so that we can treat all the same. + if (is_numeric($name)) { + $f = function () { + // + }; + + list($name, $constraints) = [$constraints, $f]; + } + + // We need to separate out any nested includes. Which allows the developers + // to load deep relationships using "dots" without stating each level of + // the relationship with its own key in the array of eager load names. + $results = $this->parseNested($name, $results); + + $results[$name] = $constraints; + } + + return $results; + } + + /** + * Parse the nested relationships in a relation. + * + * @param string $name + * @param array $results + * @return array + */ + protected function parseNested($name, $results) + { + $progress = []; + + // If the relation has already been set on the result array, we will not set it + // again, since that would override any constraints that were already placed + // on the relationships. We will only set the ones that are not specified. + foreach (explode('.', $name) as $segment) { + $progress[] = $segment; + + if (! isset($results[$last = implode('.', $progress)])) { + $results[$last] = function () { + // + }; + } + } + + return $results; + } + + /** + * Call the given model scope on the underlying model. + * + * @param string $scope + * @param array $parameters + * @return \Illuminate\Database\Query\Builder + */ + protected function callScope($scope, $parameters) + { + array_unshift($parameters, $this); + + return call_user_func_array([$this->model, $scope], $parameters) ?: $this; + } + + /** + * Get the underlying query builder instance. + * + * @return \Illuminate\Database\Query\Builder|static + */ + public function getQuery() + { + return $this->query; + } + + /** + * Set the underlying query builder instance. + * + * @param \Illuminate\Database\Query\Builder $query + * @return $this + */ + public function setQuery($query) + { + $this->query = $query; + + return $this; + } + + /** + * Get the relationships being eagerly loaded. + * + * @return array + */ + public function getEagerLoads() + { + return $this->eagerLoad; + } + + /** + * Set the relationships being eagerly loaded. + * + * @param array $eagerLoad + * @return $this + */ + public function setEagerLoads(array $eagerLoad) + { + $this->eagerLoad = $eagerLoad; + + return $this; + } + + /** + * Get the model instance being queried. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getModel() + { + return $this->model; + } + + /** + * Set a model instance for the model being queried. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return $this + */ + public function setModel(Model $model) + { + $this->model = $model; + + $this->query->from($model->getTable()); + + return $this; + } + + /** + * Extend the builder with a given callback. + * + * @param string $name + * @param \Closure $callback + * @return void + */ + public function macro($name, Closure $callback) + { + $this->macros[$name] = $callback; + } + + /** + * Get the given macro by name. + * + * @param string $name + * @return \Closure + */ + public function getMacro($name) + { + return Arr::get($this->macros, $name); + } + + /** + * Dynamically handle calls into the query instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (isset($this->macros[$method])) { + array_unshift($parameters, $this); + + return call_user_func_array($this->macros[$method], $parameters); + } elseif (method_exists($this->model, $scope = 'scope'.ucfirst($method))) { + return $this->callScope($scope, $parameters); + } + + $result = call_user_func_array([$this->query, $method], $parameters); + + return in_array($method, $this->passthru) ? $result : $this; + } + + /** + * Force a clone of the underlying query builder when cloning. + * + * @return void + */ + public function __clone() + { + $this->query = clone $this->query; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Collection.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Collection.php new file mode 100644 index 0000000..19a449e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Collection.php @@ -0,0 +1,254 @@ +getKey(); + } + + return Arr::first($this->items, function ($itemKey, $model) use ($key) { + return $model->getKey() == $key; + }, $default); + } + + /** + * Load a set of relationships onto the collection. + * + * @param mixed $relations + * @return $this + */ + public function load($relations) + { + if (count($this->items) > 0) { + if (is_string($relations)) { + $relations = func_get_args(); + } + + $query = $this->first()->newQuery()->with($relations); + + $this->items = $query->eagerLoadRelations($this->items); + } + + return $this; + } + + /** + * Add an item to the collection. + * + * @param mixed $item + * @return $this + */ + public function add($item) + { + $this->items[] = $item; + + return $this; + } + + /** + * Determine if a key exists in the collection. + * + * @param mixed $key + * @param mixed $value + * @return bool + */ + public function contains($key, $value = null) + { + if (func_num_args() == 2) { + return parent::contains($key, $value); + } + + if ($this->useAsCallable($key)) { + return parent::contains($key); + } + + $key = $key instanceof Model ? $key->getKey() : $key; + + return parent::contains(function ($k, $m) use ($key) { + return $m->getKey() == $key; + }); + } + + /** + * Fetch a nested element of the collection. + * + * @param string $key + * @return static + * + * @deprecated since version 5.1. Use pluck instead. + */ + public function fetch($key) + { + return new static(Arr::fetch($this->toArray(), $key)); + } + + /** + * Get the array of primary keys. + * + * @return array + */ + public function modelKeys() + { + return array_map(function ($m) { + return $m->getKey(); + }, $this->items); + } + + /** + * Merge the collection with the given items. + * + * @param \ArrayAccess|array $items + * @return static + */ + public function merge($items) + { + $dictionary = $this->getDictionary(); + + foreach ($items as $item) { + $dictionary[$item->getKey()] = $item; + } + + return new static(array_values($dictionary)); + } + + /** + * Diff the collection with the given items. + * + * @param \ArrayAccess|array $items + * @return static + */ + public function diff($items) + { + $diff = new static; + + $dictionary = $this->getDictionary($items); + + foreach ($this->items as $item) { + if (! isset($dictionary[$item->getKey()])) { + $diff->add($item); + } + } + + return $diff; + } + + /** + * Intersect the collection with the given items. + * + * @param \ArrayAccess|array $items + * @return static + */ + public function intersect($items) + { + $intersect = new static; + + $dictionary = $this->getDictionary($items); + + foreach ($this->items as $item) { + if (isset($dictionary[$item->getKey()])) { + $intersect->add($item); + } + } + + return $intersect; + } + + /** + * Return only unique items from the collection. + * + * @param string|callable|null $key + * @return static + */ + public function unique($key = null) + { + if (! is_null($key)) { + return parent::unique($key); + } + + return new static(array_values($this->getDictionary())); + } + + /** + * Returns only the models from the collection with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function only($keys) + { + $dictionary = Arr::only($this->getDictionary(), $keys); + + return new static(array_values($dictionary)); + } + + /** + * Returns all models in the collection except the models with specified keys. + * + * @param mixed $keys + * @return static + */ + public function except($keys) + { + $dictionary = Arr::except($this->getDictionary(), $keys); + + return new static(array_values($dictionary)); + } + + /** + * Make the given, typically hidden, attributes visible across the entire collection. + * + * @param array|string $attributes + * @return $this + */ + public function withHidden($attributes) + { + $this->each(function ($model) use ($attributes) { + $model->withHidden($attributes); + }); + + return $this; + } + + /** + * Get a dictionary keyed by primary keys. + * + * @param \ArrayAccess|array $items + * @return array + */ + public function getDictionary($items = null) + { + $items = is_null($items) ? $this->items : $items; + + $dictionary = []; + + foreach ($items as $value) { + $dictionary[$value->getKey()] = $value; + } + + return $dictionary; + } + + /** + * Get a base Support collection instance from this collection. + * + * @return \Illuminate\Support\Collection + */ + public function toBase() + { + return new BaseCollection($this->items); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factory.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factory.php new file mode 100644 index 0000000..099f4b4 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factory.php @@ -0,0 +1,229 @@ +faker = $faker; + } + + /** + * The model definitions in the container. + * + * @var array + */ + protected $definitions = []; + + /** + * Create a new factory container. + * + * @param \Faker\Generator $faker + * @param string|null $pathToFactories + * @return static + */ + public static function construct(Faker $faker, $pathToFactories = null) + { + $pathToFactories = $pathToFactories ?: database_path('factories'); + + return (new static($faker))->load($pathToFactories); + } + + /** + * Define a class with a given short-name. + * + * @param string $class + * @param string $name + * @param callable $attributes + * @return void + */ + public function defineAs($class, $name, callable $attributes) + { + return $this->define($class, $attributes, $name); + } + + /** + * Define a class with a given set of attributes. + * + * @param string $class + * @param callable $attributes + * @param string $name + * @return void + */ + public function define($class, callable $attributes, $name = 'default') + { + $this->definitions[$class][$name] = $attributes; + } + + /** + * Create an instance of the given model and persist it to the database. + * + * @param string $class + * @param array $attributes + * @return mixed + */ + public function create($class, array $attributes = []) + { + return $this->of($class)->create($attributes); + } + + /** + * Create an instance of the given model and type and persist it to the database. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return mixed + */ + public function createAs($class, $name, array $attributes = []) + { + return $this->of($class, $name)->create($attributes); + } + + /** + * Load factories from path. + * + * @param string $path + * @return $this + */ + public function load($path) + { + $factory = $this; + + if (is_dir($path)) { + foreach (Finder::create()->files()->in($path) as $file) { + require $file->getRealPath(); + } + } + + return $factory; + } + + /** + * Create an instance of the given model. + * + * @param string $class + * @param array $attributes + * @return mixed + */ + public function make($class, array $attributes = []) + { + return $this->of($class)->make($attributes); + } + + /** + * Create an instance of the given model and type. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return mixed + */ + public function makeAs($class, $name, array $attributes = []) + { + return $this->of($class, $name)->make($attributes); + } + + /** + * Get the raw attribute array for a given named model. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return array + */ + public function rawOf($class, $name, array $attributes = []) + { + return $this->raw($class, $attributes, $name); + } + + /** + * Get the raw attribute array for a given model. + * + * @param string $class + * @param array $attributes + * @param string $name + * @return array + */ + public function raw($class, array $attributes = [], $name = 'default') + { + $raw = call_user_func($this->definitions[$class][$name], $this->faker); + + return array_merge($raw, $attributes); + } + + /** + * Create a builder for the given model. + * + * @param string $class + * @param string $name + * @return \Illuminate\Database\Eloquent\FactoryBuilder + */ + public function of($class, $name = 'default') + { + return new FactoryBuilder($class, $name, $this->definitions, $this->faker); + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->definitions[$offset]); + } + + /** + * Get the value of the given offset. + * + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->make($offset); + } + + /** + * Set the given offset to the given value. + * + * @param string $offset + * @param callable $value + * @return void + */ + public function offsetSet($offset, $value) + { + return $this->define($offset, $value); + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->definitions[$offset]); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php new file mode 100644 index 0000000..89d274e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php @@ -0,0 +1,135 @@ +name = $name; + $this->class = $class; + $this->faker = $faker; + $this->definitions = $definitions; + } + + /** + * Set the amount of models you wish to create / make. + * + * @param int $amount + * @return $this + */ + public function times($amount) + { + $this->amount = $amount; + + return $this; + } + + /** + * Create a collection of models and persist them to the database. + * + * @param array $attributes + * @return mixed + */ + public function create(array $attributes = []) + { + $results = $this->make($attributes); + + if ($this->amount === 1) { + $results->save(); + } else { + foreach ($results as $result) { + $result->save(); + } + } + + return $results; + } + + /** + * Create a collection of models. + * + * @param array $attributes + * @return mixed + */ + public function make(array $attributes = []) + { + if ($this->amount === 1) { + return $this->makeInstance($attributes); + } else { + $results = []; + + for ($i = 0; $i < $this->amount; $i++) { + $results[] = $this->makeInstance($attributes); + } + + return new Collection($results); + } + } + + /** + * Make an instance of the model with the given attributes. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + protected function makeInstance(array $attributes = []) + { + return Model::unguarded(function () use ($attributes) { + if (! isset($this->definitions[$this->class][$this->name])) { + throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}] [{$this->class}]."); + } + + $definition = call_user_func($this->definitions[$this->class][$this->name], $this->faker, $attributes); + + return new $this->class(array_merge($definition, $attributes)); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassAssignmentException.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassAssignmentException.php new file mode 100644 index 0000000..7c81aae --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassAssignmentException.php @@ -0,0 +1,10 @@ +bootIfNotBooted(); + + $this->syncOriginal(); + + $this->fill($attributes); + } + + /** + * Check if the model needs to be booted and if so, do it. + * + * @return void + */ + protected function bootIfNotBooted() + { + $class = get_class($this); + + if (! isset(static::$booted[$class])) { + static::$booted[$class] = true; + + $this->fireModelEvent('booting', false); + + static::boot(); + + $this->fireModelEvent('booted', false); + } + } + + /** + * The "booting" method of the model. + * + * @return void + */ + protected static function boot() + { + static::bootTraits(); + } + + /** + * Boot all of the bootable traits on the model. + * + * @return void + */ + protected static function bootTraits() + { + foreach (class_uses_recursive(get_called_class()) as $trait) { + if (method_exists(get_called_class(), $method = 'boot'.class_basename($trait))) { + forward_static_call([get_called_class(), $method]); + } + } + } + + /** + * Clear the list of booted models so they will be re-booted. + * + * @return void + */ + public static function clearBootedModels() + { + static::$booted = []; + } + + /** + * Register a new global scope on the model. + * + * @param \Illuminate\Database\Eloquent\ScopeInterface $scope + * @return void + */ + public static function addGlobalScope(ScopeInterface $scope) + { + static::$globalScopes[get_called_class()][get_class($scope)] = $scope; + } + + /** + * Determine if a model has a global scope. + * + * @param \Illuminate\Database\Eloquent\ScopeInterface $scope + * @return bool + */ + public static function hasGlobalScope($scope) + { + return ! is_null(static::getGlobalScope($scope)); + } + + /** + * Get a global scope registered with the model. + * + * @param \Illuminate\Database\Eloquent\ScopeInterface $scope + * @return \Illuminate\Database\Eloquent\ScopeInterface|null + */ + public static function getGlobalScope($scope) + { + return Arr::first(static::$globalScopes[get_called_class()], function ($key, $value) use ($scope) { + return $scope instanceof $value; + }); + } + + /** + * Get the global scopes for this class instance. + * + * @return array + */ + public function getGlobalScopes() + { + return Arr::get(static::$globalScopes, get_class($this), []); + } + + /** + * Register an observer with the Model. + * + * @param object|string $class + * @param int $priority + * @return void + */ + public static function observe($class, $priority = 0) + { + $instance = new static; + + $className = is_string($class) ? $class : get_class($class); + + // When registering a model observer, we will spin through the possible events + // and determine if this observer has that method. If it does, we will hook + // it into the model's event system, making it convenient to watch these. + foreach ($instance->getObservableEvents() as $event) { + if (method_exists($class, $event)) { + static::registerModelEvent($event, $className.'@'.$event, $priority); + } + } + } + + /** + * Fill the model with an array of attributes. + * + * @param array $attributes + * @return $this + * + * @throws \Illuminate\Database\Eloquent\MassAssignmentException + */ + public function fill(array $attributes) + { + $totallyGuarded = $this->totallyGuarded(); + + foreach ($this->fillableFromArray($attributes) as $key => $value) { + $key = $this->removeTableFromKey($key); + + // The developers may choose to place some attributes in the "fillable" + // array, which means only those attributes may be set through mass + // assignment to the model, and all others will just be ignored. + if ($this->isFillable($key)) { + $this->setAttribute($key, $value); + } elseif ($totallyGuarded) { + throw new MassAssignmentException($key); + } + } + + return $this; + } + + /** + * Fill the model with an array of attributes. Force mass assignment. + * + * @param array $attributes + * @return $this + */ + public function forceFill(array $attributes) + { + // Since some versions of PHP have a bug that prevents it from properly + // binding the late static context in a closure, we will first store + // the model in a variable, which we will then use in the closure. + $model = $this; + + return static::unguarded(function () use ($model, $attributes) { + return $model->fill($attributes); + }); + } + + /** + * Get the fillable attributes of a given array. + * + * @param array $attributes + * @return array + */ + protected function fillableFromArray(array $attributes) + { + if (count($this->fillable) > 0 && ! static::$unguarded) { + return array_intersect_key($attributes, array_flip($this->fillable)); + } + + return $attributes; + } + + /** + * Create a new instance of the given model. + * + * @param array $attributes + * @param bool $exists + * @return static + */ + public function newInstance($attributes = [], $exists = false) + { + // This method just provides a convenient way for us to generate fresh model + // instances of this current model. It is particularly useful during the + // hydration of new objects via the Eloquent query builder instances. + $model = new static((array) $attributes); + + $model->exists = $exists; + + return $model; + } + + /** + * Create a new model instance that is existing. + * + * @param array $attributes + * @param string|null $connection + * @return static + */ + public function newFromBuilder($attributes = [], $connection = null) + { + $model = $this->newInstance([], true); + + $model->setRawAttributes((array) $attributes, true); + + $model->setConnection($connection ?: $this->connection); + + return $model; + } + + /** + * Create a collection of models from plain arrays. + * + * @param array $items + * @param string|null $connection + * @return \Illuminate\Database\Eloquent\Collection + */ + public static function hydrate(array $items, $connection = null) + { + $instance = (new static)->setConnection($connection); + + $items = array_map(function ($item) use ($instance) { + return $instance->newFromBuilder($item); + }, $items); + + return $instance->newCollection($items); + } + + /** + * Create a collection of models from a raw query. + * + * @param string $query + * @param array $bindings + * @param string|null $connection + * @return \Illuminate\Database\Eloquent\Collection + */ + public static function hydrateRaw($query, $bindings = [], $connection = null) + { + $instance = (new static)->setConnection($connection); + + $items = $instance->getConnection()->select($query, $bindings); + + return static::hydrate($items, $connection); + } + + /** + * Save a new model and return the instance. + * + * @param array $attributes + * @return static + */ + public static function create(array $attributes = []) + { + $model = new static($attributes); + + $model->save(); + + return $model; + } + + /** + * Save a new model and return the instance. Allow mass-assignment. + * + * @param array $attributes + * @return static + */ + public static function forceCreate(array $attributes) + { + // Since some versions of PHP have a bug that prevents it from properly + // binding the late static context in a closure, we will first store + // the model in a variable, which we will then use in the closure. + $model = new static; + + return static::unguarded(function () use ($model, $attributes) { + return $model->create($attributes); + }); + } + + /** + * Begin querying the model. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function query() + { + return (new static)->newQuery(); + } + + /** + * Begin querying the model on a given connection. + * + * @param string|null $connection + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function on($connection = null) + { + // First we will just create a fresh instance of this model, and then we can + // set the connection on the model so that it is be used for the queries + // we execute, as well as being set on each relationship we retrieve. + $instance = new static; + + $instance->setConnection($connection); + + return $instance->newQuery(); + } + + /** + * Begin querying the model on the write connection. + * + * @return \Illuminate\Database\Query\Builder + */ + public static function onWriteConnection() + { + $instance = new static; + + return $instance->newQuery()->useWritePdo(); + } + + /** + * Get all of the models from the database. + * + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Collection|static[] + */ + public static function all($columns = ['*']) + { + $columns = is_array($columns) ? $columns : func_get_args(); + + $instance = new static; + + return $instance->newQuery()->get($columns); + } + + /** + * Reload a fresh model instance from the database. + * + * @param array $with + * @return static|null + */ + public function fresh(array $with = []) + { + if (! $this->exists) { + return; + } + + $key = $this->getKeyName(); + + return static::with($with)->where($key, $this->getKey())->first(); + } + + /** + * Eager load relations on the model. + * + * @param array|string $relations + * @return $this + */ + public function load($relations) + { + if (is_string($relations)) { + $relations = func_get_args(); + } + + $query = $this->newQuery()->with($relations); + + $query->eagerLoadRelations([$this]); + + return $this; + } + + /** + * Begin querying a model with eager loading. + * + * @param array|string $relations + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function with($relations) + { + if (is_string($relations)) { + $relations = func_get_args(); + } + + $instance = new static; + + return $instance->newQuery()->with($relations); + } + + /** + * Append attributes to query when building a query. + * + * @param array|string $attributes + * @return $this + */ + public function append($attributes) + { + if (is_string($attributes)) { + $attributes = func_get_args(); + } + + $this->appends = array_unique( + array_merge($this->appends, $attributes) + ); + + return $this; + } + + /** + * Define a one-to-one relationship. + * + * @param string $related + * @param string $foreignKey + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function hasOne($related, $foreignKey = null, $localKey = null) + { + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $instance = new $related; + + $localKey = $localKey ?: $this->getKeyName(); + + return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey); + } + + /** + * Define a polymorphic one-to-one relationship. + * + * @param string $related + * @param string $name + * @param string $type + * @param string $id + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\MorphOne + */ + public function morphOne($related, $name, $type = null, $id = null, $localKey = null) + { + $instance = new $related; + + list($type, $id) = $this->getMorphs($name, $type, $id); + + $table = $instance->getTable(); + + $localKey = $localKey ?: $this->getKeyName(); + + return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + } + + /** + * Define an inverse one-to-one or many relationship. + * + * @param string $related + * @param string $foreignKey + * @param string $otherKey + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null) + { + // If no relation name was given, we will use this debug backtrace to extract + // the calling method's name and use that as the relationship name as most + // of the time this will be what we desire to use for the relationships. + if (is_null($relation)) { + list($current, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + $relation = $caller['function']; + } + + // If no foreign key was supplied, we can use a backtrace to guess the proper + // foreign key name by using the name of the relationship function, which + // when combined with an "_id" should conventionally match the columns. + if (is_null($foreignKey)) { + $foreignKey = Str::snake($relation).'_id'; + } + + $instance = new $related; + + // Once we have the foreign key names, we'll just create a new Eloquent query + // for the related models and returns the relationship instance which will + // actually be responsible for retrieving and hydrating every relations. + $query = $instance->newQuery(); + + $otherKey = $otherKey ?: $instance->getKeyName(); + + return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation); + } + + /** + * Define a polymorphic, inverse one-to-one or many relationship. + * + * @param string $name + * @param string $type + * @param string $id + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function morphTo($name = null, $type = null, $id = null) + { + // If no name is provided, we will use the backtrace to get the function name + // since that is most likely the name of the polymorphic interface. We can + // use that to get both the class and foreign key that will be utilized. + if (is_null($name)) { + list($current, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + $name = Str::snake($caller['function']); + } + + list($type, $id) = $this->getMorphs($name, $type, $id); + + // If the type value is null it is probably safe to assume we're eager loading + // the relationship. In this case we'll just pass in a dummy query where we + // need to remove any eager loads that may already be defined on a model. + if (empty($class = $this->$type)) { + return new MorphTo( + $this->newQuery()->setEagerLoads([]), $this, $id, null, $type, $name + ); + } + + // If we are not eager loading the relationship we will essentially treat this + // as a belongs-to style relationship since morph-to extends that class and + // we will pass in the appropriate values so that it behaves as expected. + else { + $class = $this->getActualClassNameForMorph($class); + + $instance = new $class; + + return new MorphTo( + $instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name + ); + } + } + + /** + * Retrieve the fully qualified class name from a slug. + * + * @param string $class + * @return string + */ + public function getActualClassNameForMorph($class) + { + return Arr::get(Relation::morphMap(), $class, $class); + } + + /** + * Define a one-to-many relationship. + * + * @param string $related + * @param string $foreignKey + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function hasMany($related, $foreignKey = null, $localKey = null) + { + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $instance = new $related; + + $localKey = $localKey ?: $this->getKeyName(); + + return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey); + } + + /** + * Define a has-many-through relationship. + * + * @param string $related + * @param string $through + * @param string|null $firstKey + * @param string|null $secondKey + * @param string|null $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null) + { + $through = new $through; + + $firstKey = $firstKey ?: $this->getForeignKey(); + + $secondKey = $secondKey ?: $through->getForeignKey(); + + $localKey = $localKey ?: $this->getKeyName(); + + return new HasManyThrough((new $related)->newQuery(), $this, $through, $firstKey, $secondKey, $localKey); + } + + /** + * Define a polymorphic one-to-many relationship. + * + * @param string $related + * @param string $name + * @param string $type + * @param string $id + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function morphMany($related, $name, $type = null, $id = null, $localKey = null) + { + $instance = new $related; + + // Here we will gather up the morph type and ID for the relationship so that we + // can properly query the intermediate table of a relation. Finally, we will + // get the table and create the relationship instances for the developers. + list($type, $id) = $this->getMorphs($name, $type, $id); + + $table = $instance->getTable(); + + $localKey = $localKey ?: $this->getKeyName(); + + return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + } + + /** + * Define a many-to-many relationship. + * + * @param string $related + * @param string $table + * @param string $foreignKey + * @param string $otherKey + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function belongsToMany($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null) + { + // If no relationship name was passed, we will pull backtraces to get the + // name of the calling function. We will use that function name as the + // title of this relation since that is a great convention to apply. + if (is_null($relation)) { + $relation = $this->getBelongsToManyCaller(); + } + + // First, we'll need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we'll make the query + // instances as well as the relationship instances we need for this. + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $instance = new $related; + + $otherKey = $otherKey ?: $instance->getForeignKey(); + + // If no table name was provided, we can guess it by concatenating the two + // models using underscores in alphabetical order. The two model names + // are transformed to snake case from their default CamelCase also. + if (is_null($table)) { + $table = $this->joiningTable($related); + } + + // Now we're ready to create a new query builder for the related model and + // the relationship instances for the relation. The relations will set + // appropriate query constraint and entirely manages the hydrations. + $query = $instance->newQuery(); + + return new BelongsToMany($query, $this, $table, $foreignKey, $otherKey, $relation); + } + + /** + * Define a polymorphic many-to-many relationship. + * + * @param string $related + * @param string $name + * @param string $table + * @param string $foreignKey + * @param string $otherKey + * @param bool $inverse + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function morphToMany($related, $name, $table = null, $foreignKey = null, $otherKey = null, $inverse = false) + { + $caller = $this->getBelongsToManyCaller(); + + // First, we will need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we will make the query + // instances, as well as the relationship instances we need for these. + $foreignKey = $foreignKey ?: $name.'_id'; + + $instance = new $related; + + $otherKey = $otherKey ?: $instance->getForeignKey(); + + // Now we're ready to create a new query builder for this related model and + // the relationship instances for this relation. This relations will set + // appropriate query constraints then entirely manages the hydrations. + $query = $instance->newQuery(); + + $table = $table ?: Str::plural($name); + + return new MorphToMany( + $query, $this, $name, $table, $foreignKey, + $otherKey, $caller, $inverse + ); + } + + /** + * Define a polymorphic, inverse many-to-many relationship. + * + * @param string $related + * @param string $name + * @param string $table + * @param string $foreignKey + * @param string $otherKey + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function morphedByMany($related, $name, $table = null, $foreignKey = null, $otherKey = null) + { + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + // For the inverse of the polymorphic many-to-many relations, we will change + // the way we determine the foreign and other keys, as it is the opposite + // of the morph-to-many method since we're figuring out these inverses. + $otherKey = $otherKey ?: $name.'_id'; + + return $this->morphToMany($related, $name, $table, $foreignKey, $otherKey, true); + } + + /** + * Get the relationship name of the belongs to many. + * + * @return string + */ + protected function getBelongsToManyCaller() + { + $self = __FUNCTION__; + + $caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($key, $trace) use ($self) { + $caller = $trace['function']; + + return ! in_array($caller, Model::$manyMethods) && $caller != $self; + }); + + return ! is_null($caller) ? $caller['function'] : null; + } + + /** + * Get the joining table name for a many-to-many relation. + * + * @param string $related + * @return string + */ + public function joiningTable($related) + { + // The joining table name, by convention, is simply the snake cased models + // sorted alphabetically and concatenated with an underscore, so we can + // just sort the models and join them together to get the table name. + $base = Str::snake(class_basename($this)); + + $related = Str::snake(class_basename($related)); + + $models = [$related, $base]; + + // Now that we have the model names in an array we can just sort them and + // use the implode function to join them together with an underscores, + // which is typically used by convention within the database system. + sort($models); + + return strtolower(implode('_', $models)); + } + + /** + * Destroy the models for the given IDs. + * + * @param array|int $ids + * @return int + */ + public static function destroy($ids) + { + // We'll initialize a count here so we will return the total number of deletes + // for the operation. The developers can then check this number as a boolean + // type value or get this total count of records deleted for logging, etc. + $count = 0; + + $ids = is_array($ids) ? $ids : func_get_args(); + + $instance = new static; + + // We will actually pull the models from the database table and call delete on + // each of them individually so that their events get fired properly with a + // correct set of attributes in case the developers wants to check these. + $key = $instance->getKeyName(); + + foreach ($instance->whereIn($key, $ids)->get() as $model) { + if ($model->delete()) { + $count++; + } + } + + return $count; + } + + /** + * Delete the model from the database. + * + * @return bool|null + * + * @throws \Exception + */ + public function delete() + { + if (is_null($this->getKeyName())) { + throw new Exception('No primary key defined on model.'); + } + + if ($this->exists) { + if ($this->fireModelEvent('deleting') === false) { + return false; + } + + // Here, we'll touch the owning models, verifying these timestamps get updated + // for the models. This will allow any caching to get broken on the parents + // by the timestamp. Then we will go ahead and delete the model instance. + $this->touchOwners(); + + $this->performDeleteOnModel(); + + $this->exists = false; + + // Once the model has been deleted, we will fire off the deleted event so that + // the developers may hook into post-delete operations. We will then return + // a boolean true as the delete is presumably successful on the database. + $this->fireModelEvent('deleted', false); + + return true; + } + } + + /** + * Force a hard delete on a soft deleted model. + * + * This method protects developers from running forceDelete when trait is missing. + * + * @return bool|null + */ + public function forceDelete() + { + return $this->delete(); + } + + /** + * Perform the actual delete query on this model instance. + * + * @return void + */ + protected function performDeleteOnModel() + { + $this->setKeysForSaveQuery($this->newQueryWithoutScopes())->delete(); + } + + /** + * Register a saving model event with the dispatcher. + * + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + public static function saving($callback, $priority = 0) + { + static::registerModelEvent('saving', $callback, $priority); + } + + /** + * Register a saved model event with the dispatcher. + * + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + public static function saved($callback, $priority = 0) + { + static::registerModelEvent('saved', $callback, $priority); + } + + /** + * Register an updating model event with the dispatcher. + * + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + public static function updating($callback, $priority = 0) + { + static::registerModelEvent('updating', $callback, $priority); + } + + /** + * Register an updated model event with the dispatcher. + * + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + public static function updated($callback, $priority = 0) + { + static::registerModelEvent('updated', $callback, $priority); + } + + /** + * Register a creating model event with the dispatcher. + * + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + public static function creating($callback, $priority = 0) + { + static::registerModelEvent('creating', $callback, $priority); + } + + /** + * Register a created model event with the dispatcher. + * + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + public static function created($callback, $priority = 0) + { + static::registerModelEvent('created', $callback, $priority); + } + + /** + * Register a deleting model event with the dispatcher. + * + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + public static function deleting($callback, $priority = 0) + { + static::registerModelEvent('deleting', $callback, $priority); + } + + /** + * Register a deleted model event with the dispatcher. + * + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + public static function deleted($callback, $priority = 0) + { + static::registerModelEvent('deleted', $callback, $priority); + } + + /** + * Remove all of the event listeners for the model. + * + * @return void + */ + public static function flushEventListeners() + { + if (! isset(static::$dispatcher)) { + return; + } + + $instance = new static; + + foreach ($instance->getObservableEvents() as $event) { + static::$dispatcher->forget("eloquent.{$event}: ".get_called_class()); + } + } + + /** + * Register a model event with the dispatcher. + * + * @param string $event + * @param \Closure|string $callback + * @param int $priority + * @return void + */ + protected static function registerModelEvent($event, $callback, $priority = 0) + { + if (isset(static::$dispatcher)) { + $name = get_called_class(); + + static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback, $priority); + } + } + + /** + * Get the observable event names. + * + * @return array + */ + public function getObservableEvents() + { + return array_merge( + [ + 'creating', 'created', 'updating', 'updated', + 'deleting', 'deleted', 'saving', 'saved', + 'restoring', 'restored', + ], + $this->observables + ); + } + + /** + * Set the observable event names. + * + * @param array $observables + * @return $this + */ + public function setObservableEvents(array $observables) + { + $this->observables = $observables; + + return $this; + } + + /** + * Add an observable event name. + * + * @param array|mixed $observables + * @return void + */ + public function addObservableEvents($observables) + { + $observables = is_array($observables) ? $observables : func_get_args(); + + $this->observables = array_unique(array_merge($this->observables, $observables)); + } + + /** + * Remove an observable event name. + * + * @param array|mixed $observables + * @return void + */ + public function removeObservableEvents($observables) + { + $observables = is_array($observables) ? $observables : func_get_args(); + + $this->observables = array_diff($this->observables, $observables); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @return int + */ + protected function increment($column, $amount = 1) + { + return $this->incrementOrDecrement($column, $amount, 'increment'); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @return int + */ + protected function decrement($column, $amount = 1) + { + return $this->incrementOrDecrement($column, $amount, 'decrement'); + } + + /** + * Run the increment or decrement method on the model. + * + * @param string $column + * @param int $amount + * @param string $method + * @return int + */ + protected function incrementOrDecrement($column, $amount, $method) + { + $query = $this->newQuery(); + + if (! $this->exists) { + return $query->{$method}($column, $amount); + } + + $this->incrementOrDecrementAttributeValue($column, $amount, $method); + + return $query->where($this->getKeyName(), $this->getKey())->{$method}($column, $amount); + } + + /** + * Increment the underlying attribute value and sync with original. + * + * @param string $column + * @param int $amount + * @param string $method + * @return void + */ + protected function incrementOrDecrementAttributeValue($column, $amount, $method) + { + $this->{$column} = $this->{$column} + ($method == 'increment' ? $amount : $amount * -1); + + $this->syncOriginalAttribute($column); + } + + /** + * Update the model in the database. + * + * @param array $attributes + * @return bool|int + */ + public function update(array $attributes = []) + { + if (! $this->exists) { + return false; + } + + return $this->fill($attributes)->save(); + } + + /** + * Save the model and all of its relationships. + * + * @return bool + */ + public function push() + { + if (! $this->save()) { + return false; + } + + // To sync all of the relationships to the database, we will simply spin through + // the relationships and save each model via this "push" method, which allows + // us to recurse into all of these nested relations for the model instance. + foreach ($this->relations as $models) { + $models = $models instanceof Collection + ? $models->all() : [$models]; + + foreach (array_filter($models) as $model) { + if (! $model->push()) { + return false; + } + } + } + + return true; + } + + /** + * Save the model to the database. + * + * @param array $options + * @return bool + */ + public function save(array $options = []) + { + $query = $this->newQueryWithoutScopes(); + + // If the "saving" event returns false we'll bail out of the save and return + // false, indicating that the save failed. This provides a chance for any + // listeners to cancel save operations if validations fail or whatever. + if ($this->fireModelEvent('saving') === false) { + return false; + } + + // If the model already exists in the database we can just update our record + // that is already in this database using the current IDs in this "where" + // clause to only update this model. Otherwise, we'll just insert them. + if ($this->exists) { + $saved = $this->performUpdate($query, $options); + } + + // If the model is brand new, we'll insert it into our database and set the + // ID attribute on the model to the value of the newly inserted row's ID + // which is typically an auto-increment value managed by the database. + else { + $saved = $this->performInsert($query, $options); + } + + if ($saved) { + $this->finishSave($options); + } + + return $saved; + } + + /** + * Save the model to the database using transaction. + * + * @param array $options + * @return bool + * + * @throws \Throwable + */ + public function saveOrFail(array $options = []) + { + return $this->getConnection()->transaction(function () use ($options) { + return $this->save($options); + }); + } + + /** + * Finish processing on a successful save operation. + * + * @param array $options + * @return void + */ + protected function finishSave(array $options) + { + $this->fireModelEvent('saved', false); + + $this->syncOriginal(); + + if (Arr::get($options, 'touch', true)) { + $this->touchOwners(); + } + } + + /** + * Perform a model update operation. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $options + * @return bool + */ + protected function performUpdate(Builder $query, array $options = []) + { + $dirty = $this->getDirty(); + + if (count($dirty) > 0) { + // If the updating event returns false, we will cancel the update operation so + // developers can hook Validation systems into their models and cancel this + // operation if the model does not pass validation. Otherwise, we update. + if ($this->fireModelEvent('updating') === false) { + return false; + } + + // First we need to create a fresh query instance and touch the creation and + // update timestamp on the model which are maintained by us for developer + // convenience. Then we will just continue saving the model instances. + if ($this->timestamps && Arr::get($options, 'timestamps', true)) { + $this->updateTimestamps(); + } + + // Once we have run the update operation, we will fire the "updated" event for + // this model instance. This will allow developers to hook into these after + // models are updated, giving them a chance to do any special processing. + $dirty = $this->getDirty(); + + if (count($dirty) > 0) { + $numRows = $this->setKeysForSaveQuery($query)->update($dirty); + + $this->fireModelEvent('updated', false); + } + } + + return true; + } + + /** + * Perform a model insert operation. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $options + * @return bool + */ + protected function performInsert(Builder $query, array $options = []) + { + if ($this->fireModelEvent('creating') === false) { + return false; + } + + // First we'll need to create a fresh query instance and touch the creation and + // update timestamps on this model, which are maintained by us for developer + // convenience. After, we will just continue saving these model instances. + if ($this->timestamps && Arr::get($options, 'timestamps', true)) { + $this->updateTimestamps(); + } + + // If the model has an incrementing key, we can use the "insertGetId" method on + // the query builder, which will give us back the final inserted ID for this + // table from the database. Not all tables have to be incrementing though. + $attributes = $this->attributes; + + if ($this->incrementing) { + $this->insertAndSetId($query, $attributes); + } + + // If the table isn't incrementing we'll simply insert these attributes as they + // are. These attribute arrays must contain an "id" column previously placed + // there by the developer as the manually determined key for these models. + else { + $query->insert($attributes); + } + + // We will go ahead and set the exists property to true, so that it is set when + // the created event is fired, just in case the developer tries to update it + // during the event. This will allow them to do so and run an update here. + $this->exists = true; + + $this->wasRecentlyCreated = true; + + $this->fireModelEvent('created', false); + + return true; + } + + /** + * Insert the given attributes and set the ID on the model. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $attributes + * @return void + */ + protected function insertAndSetId(Builder $query, $attributes) + { + $id = $query->insertGetId($attributes, $keyName = $this->getKeyName()); + + $this->setAttribute($keyName, $id); + } + + /** + * Touch the owning relations of the model. + * + * @return void + */ + public function touchOwners() + { + foreach ($this->touches as $relation) { + $this->$relation()->touch(); + + if ($this->$relation instanceof self) { + $this->$relation->fireModelEvent('saved', false); + + $this->$relation->touchOwners(); + } elseif ($this->$relation instanceof Collection) { + $this->$relation->each(function (Model $relation) { + $relation->touchOwners(); + }); + } + } + } + + /** + * Determine if the model touches a given relation. + * + * @param string $relation + * @return bool + */ + public function touches($relation) + { + return in_array($relation, $this->touches); + } + + /** + * Fire the given event for the model. + * + * @param string $event + * @param bool $halt + * @return mixed + */ + protected function fireModelEvent($event, $halt = true) + { + if (! isset(static::$dispatcher)) { + return true; + } + + // We will append the names of the class to the event to distinguish it from + // other model events that are fired, allowing us to listen on each model + // event set individually instead of catching event for all the models. + $event = "eloquent.{$event}: ".get_class($this); + + $method = $halt ? 'until' : 'fire'; + + return static::$dispatcher->$method($event, $this); + } + + /** + * Set the keys for a save update query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSaveQuery(Builder $query) + { + $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery()); + + return $query; + } + + /** + * Get the primary key value for a save query. + * + * @return mixed + */ + protected function getKeyForSaveQuery() + { + if (isset($this->original[$this->getKeyName()])) { + return $this->original[$this->getKeyName()]; + } + + return $this->getAttribute($this->getKeyName()); + } + + /** + * Update the model's update timestamp. + * + * @return bool + */ + public function touch() + { + if (! $this->timestamps) { + return false; + } + + $this->updateTimestamps(); + + return $this->save(); + } + + /** + * Update the creation and update timestamps. + * + * @return void + */ + protected function updateTimestamps() + { + $time = $this->freshTimestamp(); + + if (! $this->isDirty(static::UPDATED_AT)) { + $this->setUpdatedAt($time); + } + + if (! $this->exists && ! $this->isDirty(static::CREATED_AT)) { + $this->setCreatedAt($time); + } + } + + /** + * Set the value of the "created at" attribute. + * + * @param mixed $value + * @return $this + */ + public function setCreatedAt($value) + { + $this->{static::CREATED_AT} = $value; + + return $this; + } + + /** + * Set the value of the "updated at" attribute. + * + * @param mixed $value + * @return $this + */ + public function setUpdatedAt($value) + { + $this->{static::UPDATED_AT} = $value; + + return $this; + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function getCreatedAtColumn() + { + return static::CREATED_AT; + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function getUpdatedAtColumn() + { + return static::UPDATED_AT; + } + + /** + * Get a fresh timestamp for the model. + * + * @return \Carbon\Carbon + */ + public function freshTimestamp() + { + return new Carbon; + } + + /** + * Get a fresh timestamp for the model. + * + * @return string + */ + public function freshTimestampString() + { + return $this->fromDateTime($this->freshTimestamp()); + } + + /** + * Get a new query builder for the model's table. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQuery() + { + $builder = $this->newQueryWithoutScopes(); + + return $this->applyGlobalScopes($builder); + } + + /** + * Get a new query instance without a given scope. + * + * @param \Illuminate\Database\Eloquent\ScopeInterface $scope + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryWithoutScope($scope) + { + $this->getGlobalScope($scope)->remove($builder = $this->newQuery(), $this); + + return $builder; + } + + /** + * Get a new query builder that doesn't have any global scopes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newQueryWithoutScopes() + { + $builder = $this->newEloquentBuilder( + $this->newBaseQueryBuilder() + ); + + // Once we have the query builders, we will set the model instances so the + // builder can easily access any information it may need from the model + // while it is constructing and executing various queries against it. + return $builder->setModel($this)->with($this->with); + } + + /** + * Apply all of the global scopes to an Eloquent builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return \Illuminate\Database\Eloquent\Builder + */ + public function applyGlobalScopes($builder) + { + foreach ($this->getGlobalScopes() as $scope) { + $scope->apply($builder, $this); + } + + return $builder; + } + + /** + * Remove all of the global scopes from an Eloquent builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return \Illuminate\Database\Eloquent\Builder + */ + public function removeGlobalScopes($builder) + { + foreach ($this->getGlobalScopes() as $scope) { + $scope->remove($builder, $this); + } + + return $builder; + } + + /** + * Create a new Eloquent query builder for the model. + * + * @param \Illuminate\Database\Query\Builder $query + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newEloquentBuilder($query) + { + return new Builder($query); + } + + /** + * Get a new query builder instance for the connection. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function newBaseQueryBuilder() + { + $conn = $this->getConnection(); + + $grammar = $conn->getQueryGrammar(); + + return new QueryBuilder($conn, $grammar, $conn->getPostProcessor()); + } + + /** + * Create a new Eloquent Collection instance. + * + * @param array $models + * @return \Illuminate\Database\Eloquent\Collection + */ + public function newCollection(array $models = []) + { + return new Collection($models); + } + + /** + * Create a new pivot model instance. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @param array $attributes + * @param string $table + * @param bool $exists + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newPivot(Model $parent, array $attributes, $table, $exists) + { + return new Pivot($parent, $attributes, $table, $exists); + } + + /** + * Get the table associated with the model. + * + * @return string + */ + public function getTable() + { + if (isset($this->table)) { + return $this->table; + } + + return str_replace('\\', '', Str::snake(Str::plural(class_basename($this)))); + } + + /** + * Set the table associated with the model. + * + * @param string $table + * @return $this + */ + public function setTable($table) + { + $this->table = $table; + + return $this; + } + + /** + * Get the value of the model's primary key. + * + * @return mixed + */ + public function getKey() + { + return $this->getAttribute($this->getKeyName()); + } + + /** + * Get the queueable identity for the entity. + * + * @return mixed + */ + public function getQueueableId() + { + return $this->getKey(); + } + + /** + * Get the primary key for the model. + * + * @return string + */ + public function getKeyName() + { + return $this->primaryKey; + } + + /** + * Set the primary key for the model. + * + * @param string $key + * @return $this + */ + public function setKeyName($key) + { + $this->primaryKey = $key; + + return $this; + } + + /** + * Get the table qualified key name. + * + * @return string + */ + public function getQualifiedKeyName() + { + return $this->getTable().'.'.$this->getKeyName(); + } + + /** + * Get the value of the model's route key. + * + * @return mixed + */ + public function getRouteKey() + { + return $this->getAttribute($this->getRouteKeyName()); + } + + /** + * Get the route key for the model. + * + * @return string + */ + public function getRouteKeyName() + { + return $this->getKeyName(); + } + + /** + * Determine if the model uses timestamps. + * + * @return bool + */ + public function usesTimestamps() + { + return $this->timestamps; + } + + /** + * Get the polymorphic relationship columns. + * + * @param string $name + * @param string $type + * @param string $id + * @return array + */ + protected function getMorphs($name, $type, $id) + { + $type = $type ?: $name.'_type'; + + $id = $id ?: $name.'_id'; + + return [$type, $id]; + } + + /** + * Get the class name for polymorphic relations. + * + * @return string + */ + public function getMorphClass() + { + $morphMap = Relation::morphMap(); + + $class = get_class($this); + + if (! empty($morphMap) && in_array($class, $morphMap)) { + return array_search($class, $morphMap, true); + } + + return $this->morphClass ?: $class; + } + + /** + * Get the number of models to return per page. + * + * @return int + */ + public function getPerPage() + { + return $this->perPage; + } + + /** + * Set the number of models to return per page. + * + * @param int $perPage + * @return $this + */ + public function setPerPage($perPage) + { + $this->perPage = $perPage; + + return $this; + } + + /** + * Get the default foreign key name for the model. + * + * @return string + */ + public function getForeignKey() + { + return Str::snake(class_basename($this)).'_id'; + } + + /** + * Get the hidden attributes for the model. + * + * @return array + */ + public function getHidden() + { + return $this->hidden; + } + + /** + * Set the hidden attributes for the model. + * + * @param array $hidden + * @return $this + */ + public function setHidden(array $hidden) + { + $this->hidden = $hidden; + + return $this; + } + + /** + * Add hidden attributes for the model. + * + * @param array|string|null $attributes + * @return void + */ + public function addHidden($attributes = null) + { + $attributes = is_array($attributes) ? $attributes : func_get_args(); + + $this->hidden = array_merge($this->hidden, $attributes); + } + + /** + * Make the given, typically hidden, attributes visible. + * + * @param array|string $attributes + * @return $this + */ + public function withHidden($attributes) + { + $this->hidden = array_diff($this->hidden, (array) $attributes); + + return $this; + } + + /** + * Get the visible attributes for the model. + * + * @return array + */ + public function getVisible() + { + return $this->visible; + } + + /** + * Set the visible attributes for the model. + * + * @param array $visible + * @return $this + */ + public function setVisible(array $visible) + { + $this->visible = $visible; + + return $this; + } + + /** + * Add visible attributes for the model. + * + * @param array|string|null $attributes + * @return void + */ + public function addVisible($attributes = null) + { + $attributes = is_array($attributes) ? $attributes : func_get_args(); + + $this->visible = array_merge($this->visible, $attributes); + } + + /** + * Set the accessors to append to model arrays. + * + * @param array $appends + * @return $this + */ + public function setAppends(array $appends) + { + $this->appends = $appends; + + return $this; + } + + /** + * Get the fillable attributes for the model. + * + * @return array + */ + public function getFillable() + { + return $this->fillable; + } + + /** + * Set the fillable attributes for the model. + * + * @param array $fillable + * @return $this + */ + public function fillable(array $fillable) + { + $this->fillable = $fillable; + + return $this; + } + + /** + * Get the guarded attributes for the model. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded; + } + + /** + * Set the guarded attributes for the model. + * + * @param array $guarded + * @return $this + */ + public function guard(array $guarded) + { + $this->guarded = $guarded; + + return $this; + } + + /** + * Disable all mass assignable restrictions. + * + * @param bool $state + * @return void + */ + public static function unguard($state = true) + { + static::$unguarded = $state; + } + + /** + * Enable the mass assignment restrictions. + * + * @return void + */ + public static function reguard() + { + static::$unguarded = false; + } + + /** + * Determine if current state is "unguarded". + * + * @return bool + */ + public static function isUnguarded() + { + return static::$unguarded; + } + + /** + * Run the given callable while being unguarded. + * + * @param callable $callback + * @return mixed + */ + public static function unguarded(callable $callback) + { + if (static::$unguarded) { + return $callback(); + } + + static::unguard(); + + try { + return $callback(); + } finally { + static::reguard(); + } + } + + /** + * Determine if the given attribute may be mass assigned. + * + * @param string $key + * @return bool + */ + public function isFillable($key) + { + if (static::$unguarded) { + return true; + } + + // If the key is in the "fillable" array, we can of course assume that it's + // a fillable attribute. Otherwise, we will check the guarded array when + // we need to determine if the attribute is black-listed on the model. + if (in_array($key, $this->fillable)) { + return true; + } + + if ($this->isGuarded($key)) { + return false; + } + + return empty($this->fillable) && ! Str::startsWith($key, '_'); + } + + /** + * Determine if the given key is guarded. + * + * @param string $key + * @return bool + */ + public function isGuarded($key) + { + return in_array($key, $this->guarded) || $this->guarded == ['*']; + } + + /** + * Determine if the model is totally guarded. + * + * @return bool + */ + public function totallyGuarded() + { + return count($this->fillable) == 0 && $this->guarded == ['*']; + } + + /** + * Remove the table name from a given key. + * + * @param string $key + * @return string + */ + protected function removeTableFromKey($key) + { + if (! Str::contains($key, '.')) { + return $key; + } + + return last(explode('.', $key)); + } + + /** + * Get the relationships that are touched on save. + * + * @return array + */ + public function getTouchedRelations() + { + return $this->touches; + } + + /** + * Set the relationships that are touched on save. + * + * @param array $touches + * @return $this + */ + public function setTouchedRelations(array $touches) + { + $this->touches = $touches; + + return $this; + } + + /** + * Get the value indicating whether the IDs are incrementing. + * + * @return bool + */ + public function getIncrementing() + { + return $this->incrementing; + } + + /** + * Set whether IDs are incrementing. + * + * @param bool $value + * @return $this + */ + public function setIncrementing($value) + { + $this->incrementing = $value; + + return $this; + } + + /** + * Convert the model instance to JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the model instance to an array. + * + * @return array + */ + public function toArray() + { + $attributes = $this->attributesToArray(); + + return array_merge($attributes, $this->relationsToArray()); + } + + /** + * Convert the model's attributes to an array. + * + * @return array + */ + public function attributesToArray() + { + $attributes = $this->getArrayableAttributes(); + + // If an attribute is a date, we will cast it to a string after converting it + // to a DateTime / Carbon instance. This is so we will get some consistent + // formatting while accessing attributes vs. arraying / JSONing a model. + foreach ($this->getDates() as $key) { + if (! isset($attributes[$key])) { + continue; + } + + $attributes[$key] = $this->serializeDate( + $this->asDateTime($attributes[$key]) + ); + } + + $mutatedAttributes = $this->getMutatedAttributes(); + + // We want to spin through all the mutated attributes for this model and call + // the mutator for the attribute. We cache off every mutated attributes so + // we don't have to constantly check on attributes that actually change. + foreach ($mutatedAttributes as $key) { + if (! array_key_exists($key, $attributes)) { + continue; + } + + $attributes[$key] = $this->mutateAttributeForArray( + $key, $attributes[$key] + ); + } + + // Next we will handle any casts that have been setup for this model and cast + // the values to their appropriate type. If the attribute has a mutator we + // will not perform the cast on those attributes to avoid any confusion. + foreach ($this->casts as $key => $value) { + if (! array_key_exists($key, $attributes) || + in_array($key, $mutatedAttributes)) { + continue; + } + + $attributes[$key] = $this->castAttribute( + $key, $attributes[$key] + ); + } + + // Here we will grab all of the appended, calculated attributes to this model + // as these attributes are not really in the attributes array, but are run + // when we need to array or JSON the model for convenience to the coder. + foreach ($this->getArrayableAppends() as $key) { + $attributes[$key] = $this->mutateAttributeForArray($key, null); + } + + return $attributes; + } + + /** + * Get an attribute array of all arrayable attributes. + * + * @return array + */ + protected function getArrayableAttributes() + { + return $this->getArrayableItems($this->attributes); + } + + /** + * Get all of the appendable values that are arrayable. + * + * @return array + */ + protected function getArrayableAppends() + { + if (! count($this->appends)) { + return []; + } + + return $this->getArrayableItems( + array_combine($this->appends, $this->appends) + ); + } + + /** + * Get the model's relationships in array form. + * + * @return array + */ + public function relationsToArray() + { + $attributes = []; + + foreach ($this->getArrayableRelations() as $key => $value) { + // If the values implements the Arrayable interface we can just call this + // toArray method on the instances which will convert both models and + // collections to their proper array form and we'll set the values. + if ($value instanceof Arrayable) { + $relation = $value->toArray(); + } + + // If the value is null, we'll still go ahead and set it in this list of + // attributes since null is used to represent empty relationships if + // if it a has one or belongs to type relationships on the models. + elseif (is_null($value)) { + $relation = $value; + } + + // If the relationships snake-casing is enabled, we will snake case this + // key so that the relation attribute is snake cased in this returned + // array to the developers, making this consistent with attributes. + if (static::$snakeAttributes) { + $key = Str::snake($key); + } + + // If the relation value has been set, we will set it on this attributes + // list for returning. If it was not arrayable or null, we'll not set + // the value on the array because it is some type of invalid value. + if (isset($relation) || is_null($value)) { + $attributes[$key] = $relation; + } + + unset($relation); + } + + return $attributes; + } + + /** + * Get an attribute array of all arrayable relations. + * + * @return array + */ + protected function getArrayableRelations() + { + return $this->getArrayableItems($this->relations); + } + + /** + * Get an attribute array of all arrayable values. + * + * @param array $values + * @return array + */ + protected function getArrayableItems(array $values) + { + if (count($this->getVisible()) > 0) { + return array_intersect_key($values, array_flip($this->getVisible())); + } + + return array_diff_key($values, array_flip($this->getHidden())); + } + + /** + * Get an attribute from the model. + * + * @param string $key + * @return mixed + */ + public function getAttribute($key) + { + if (array_key_exists($key, $this->attributes) || $this->hasGetMutator($key)) { + return $this->getAttributeValue($key); + } + + return $this->getRelationValue($key); + } + + /** + * Get a plain attribute (not a relationship). + * + * @param string $key + * @return mixed + */ + public function getAttributeValue($key) + { + $value = $this->getAttributeFromArray($key); + + // If the attribute has a get mutator, we will call that then return what + // it returns as the value, which is useful for transforming values on + // retrieval from the model to a form that is more useful for usage. + if ($this->hasGetMutator($key)) { + return $this->mutateAttribute($key, $value); + } + + // If the attribute exists within the cast array, we will convert it to + // an appropriate native PHP type dependant upon the associated value + // given with the key in the pair. Dayle made this comment line up. + if ($this->hasCast($key)) { + $value = $this->castAttribute($key, $value); + } + + // If the attribute is listed as a date, we will convert it to a DateTime + // instance on retrieval, which makes it quite convenient to work with + // date fields without having to create a mutator for each property. + elseif (in_array($key, $this->getDates())) { + if (! is_null($value)) { + return $this->asDateTime($value); + } + } + + return $value; + } + + /** + * Get a relationship. + * + * @param string $key + * @return mixed + */ + public function getRelationValue($key) + { + // If the key already exists in the relationships array, it just means the + // relationship has already been loaded, so we'll just return it out of + // here because there is no need to query within the relations twice. + if ($this->relationLoaded($key)) { + return $this->relations[$key]; + } + + // If the "attribute" exists as a method on the model, we will just assume + // it is a relationship and will load and return results from the query + // and hydrate the relationship's value on the "relationships" array. + if (method_exists($this, $key)) { + return $this->getRelationshipFromMethod($key); + } + } + + /** + * Get an attribute from the $attributes array. + * + * @param string $key + * @return mixed + */ + protected function getAttributeFromArray($key) + { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + } + + /** + * Get a relationship value from a method. + * + * @param string $method + * @return mixed + * + * @throws \LogicException + */ + protected function getRelationshipFromMethod($method) + { + $relations = $this->$method(); + + if (! $relations instanceof Relation) { + throw new LogicException('Relationship method must return an object of type ' + .'Illuminate\Database\Eloquent\Relations\Relation'); + } + + return $this->relations[$method] = $relations->getResults(); + } + + /** + * Determine if a get mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasGetMutator($key) + { + return method_exists($this, 'get'.Str::studly($key).'Attribute'); + } + + /** + * Get the value of an attribute using its mutator. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function mutateAttribute($key, $value) + { + return $this->{'get'.Str::studly($key).'Attribute'}($value); + } + + /** + * Get the value of an attribute using its mutator for array conversion. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function mutateAttributeForArray($key, $value) + { + $value = $this->mutateAttribute($key, $value); + + return $value instanceof Arrayable ? $value->toArray() : $value; + } + + /** + * Determine whether an attribute should be cast to a native type. + * + * @param string $key + * @return bool + */ + protected function hasCast($key) + { + return array_key_exists($key, $this->casts); + } + + /** + * Determine whether a value is Date / DateTime castable for inbound manipulation. + * + * @param string $key + * @return bool + */ + protected function isDateCastable($key) + { + return $this->hasCast($key) && + in_array($this->getCastType($key), ['date', 'datetime'], true); + } + + /** + * Determine whether a value is JSON castable for inbound manipulation. + * + * @param string $key + * @return bool + */ + protected function isJsonCastable($key) + { + return $this->hasCast($key) && + in_array($this->getCastType($key), ['array', 'json', 'object', 'collection'], true); + } + + /** + * Get the type of cast for a model attribute. + * + * @param string $key + * @return string + */ + protected function getCastType($key) + { + return trim(strtolower($this->casts[$key])); + } + + /** + * Cast an attribute to a native PHP type. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function castAttribute($key, $value) + { + if (is_null($value)) { + return $value; + } + + switch ($this->getCastType($key)) { + case 'int': + case 'integer': + return (int) $value; + case 'real': + case 'float': + case 'double': + return (float) $value; + case 'string': + return (string) $value; + case 'bool': + case 'boolean': + return (bool) $value; + case 'object': + return $this->fromJson($value, true); + case 'array': + case 'json': + return $this->fromJson($value); + case 'collection': + return new BaseCollection($this->fromJson($value)); + case 'date': + case 'datetime': + return $this->asDateTime($value); + default: + return $value; + } + } + + /** + * Set a given attribute on the model. + * + * @param string $key + * @param mixed $value + * @return $this + */ + public function setAttribute($key, $value) + { + // First we will check for the presence of a mutator for the set operation + // which simply lets the developers tweak the attribute as it is set on + // the model, such as "json_encoding" an listing of data for storage. + if ($this->hasSetMutator($key)) { + $method = 'set'.Str::studly($key).'Attribute'; + + return $this->{$method}($value); + } + + // If an attribute is listed as a "date", we'll convert it from a DateTime + // instance into a form proper for storage on the database tables using + // the connection grammar's date format. We will auto set the values. + elseif ($value && (in_array($key, $this->getDates()) || $this->isDateCastable($key))) { + $value = $this->fromDateTime($value); + } + + if ($this->isJsonCastable($key) && ! is_null($value)) { + $value = $this->asJson($value); + } + + $this->attributes[$key] = $value; + + return $this; + } + + /** + * Determine if a set mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasSetMutator($key) + { + return method_exists($this, 'set'.Str::studly($key).'Attribute'); + } + + /** + * Get the attributes that should be converted to dates. + * + * @return array + */ + public function getDates() + { + $defaults = [static::CREATED_AT, static::UPDATED_AT]; + + return $this->timestamps ? array_merge($this->dates, $defaults) : $this->dates; + } + + /** + * Convert a DateTime to a storable string. + * + * @param \DateTime|int $value + * @return string + */ + public function fromDateTime($value) + { + $format = $this->getDateFormat(); + + $value = $this->asDateTime($value); + + return $value->format($format); + } + + /** + * Return a timestamp as DateTime object. + * + * @param mixed $value + * @return \Carbon\Carbon + */ + protected function asDateTime($value) + { + // If this value is already a Carbon instance, we shall just return it as is. + // This prevents us having to reinstantiate a Carbon instance when we know + // it already is one, which wouldn't be fulfilled by the DateTime check. + if ($value instanceof Carbon) { + return $value; + } + + // If the value is already a DateTime instance, we will just skip the rest of + // these checks since they will be a waste of time, and hinder performance + // when checking the field. We will just return the DateTime right away. + if ($value instanceof DateTime) { + return Carbon::instance($value); + } + + // If this value is an integer, we will assume it is a UNIX timestamp's value + // and format a Carbon object from this timestamp. This allows flexibility + // when defining your date fields as they might be UNIX timestamps here. + if (is_numeric($value)) { + return Carbon::createFromTimestamp($value); + } + + // If the value is in simply year, month, day format, we will instantiate the + // Carbon instances from that format. Again, this provides for simple date + // fields on the database, while still supporting Carbonized conversion. + if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $value)) { + return Carbon::createFromFormat('Y-m-d', $value)->startOfDay(); + } + + // Finally, we will just assume this date is in the format used by default on + // the database connection and use that format to create the Carbon object + // that is returned back out to the developers after we convert it here. + return Carbon::createFromFormat($this->getDateFormat(), $value); + } + + /** + * Prepare a date for array / JSON serialization. + * + * @param \DateTime $date + * @return string + */ + protected function serializeDate(DateTime $date) + { + return $date->format($this->getDateFormat()); + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + protected function getDateFormat() + { + return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat(); + } + + /** + * Set the date format used by the model. + * + * @param string $format + * @return $this + */ + public function setDateFormat($format) + { + $this->dateFormat = $format; + + return $this; + } + + /** + * Encode the given value as JSON. + * + * @param mixed $value + * @return string + */ + protected function asJson($value) + { + return json_encode($value); + } + + /** + * Decode the given JSON back into an array or object. + * + * @param string $value + * @param bool $asObject + * @return mixed + */ + public function fromJson($value, $asObject = false) + { + return json_decode($value, ! $asObject); + } + + /** + * Clone the model into a new, non-existing instance. + * + * @param array|null $except + * @return \Illuminate\Database\Eloquent\Model + */ + public function replicate(array $except = null) + { + $except = $except ?: [ + $this->getKeyName(), + $this->getCreatedAtColumn(), + $this->getUpdatedAtColumn(), + ]; + + $attributes = Arr::except($this->attributes, $except); + + with($instance = new static)->setRawAttributes($attributes); + + return $instance->setRelations($this->relations); + } + + /** + * Get all of the current attributes on the model. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set the array of model attributes. No checking is done. + * + * @param array $attributes + * @param bool $sync + * @return $this + */ + public function setRawAttributes(array $attributes, $sync = false) + { + $this->attributes = $attributes; + + if ($sync) { + $this->syncOriginal(); + } + + return $this; + } + + /** + * Get the model's original attribute values. + * + * @param string|null $key + * @param mixed $default + * @return array + */ + public function getOriginal($key = null, $default = null) + { + return Arr::get($this->original, $key, $default); + } + + /** + * Sync the original attributes with the current. + * + * @return $this + */ + public function syncOriginal() + { + $this->original = $this->attributes; + + return $this; + } + + /** + * Sync a single original attribute with its current value. + * + * @param string $attribute + * @return $this + */ + public function syncOriginalAttribute($attribute) + { + $this->original[$attribute] = $this->attributes[$attribute]; + + return $this; + } + + /** + * Determine if the model or given attribute(s) have been modified. + * + * @param array|string|null $attributes + * @return bool + */ + public function isDirty($attributes = null) + { + $dirty = $this->getDirty(); + + if (is_null($attributes)) { + return count($dirty) > 0; + } + + if (! is_array($attributes)) { + $attributes = func_get_args(); + } + + foreach ($attributes as $attribute) { + if (array_key_exists($attribute, $dirty)) { + return true; + } + } + + return false; + } + + /** + * Get the attributes that have been changed since last sync. + * + * @return array + */ + public function getDirty() + { + $dirty = []; + + foreach ($this->attributes as $key => $value) { + if (! array_key_exists($key, $this->original)) { + $dirty[$key] = $value; + } elseif ($value !== $this->original[$key] && + ! $this->originalIsNumericallyEquivalent($key)) { + $dirty[$key] = $value; + } + } + + return $dirty; + } + + /** + * Determine if the new and old values for a given key are numerically equivalent. + * + * @param string $key + * @return bool + */ + protected function originalIsNumericallyEquivalent($key) + { + $current = $this->attributes[$key]; + + $original = $this->original[$key]; + + return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0; + } + + /** + * Get all the loaded relations for the instance. + * + * @return array + */ + public function getRelations() + { + return $this->relations; + } + + /** + * Get a specified relationship. + * + * @param string $relation + * @return mixed + */ + public function getRelation($relation) + { + return $this->relations[$relation]; + } + + /** + * Determine if the given relation is loaded. + * + * @param string $key + * @return bool + */ + public function relationLoaded($key) + { + return array_key_exists($key, $this->relations); + } + + /** + * Set the specific relationship in the model. + * + * @param string $relation + * @param mixed $value + * @return $this + */ + public function setRelation($relation, $value) + { + $this->relations[$relation] = $value; + + return $this; + } + + /** + * Set the entire relations array on the model. + * + * @param array $relations + * @return $this + */ + public function setRelations(array $relations) + { + $this->relations = $relations; + + return $this; + } + + /** + * Get the database connection for the model. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return static::resolveConnection($this->connection); + } + + /** + * Get the current connection name for the model. + * + * @return string + */ + public function getConnectionName() + { + return $this->connection; + } + + /** + * Set the connection associated with the model. + * + * @param string $name + * @return $this + */ + public function setConnection($name) + { + $this->connection = $name; + + return $this; + } + + /** + * Resolve a connection instance. + * + * @param string|null $connection + * @return \Illuminate\Database\Connection + */ + public static function resolveConnection($connection = null) + { + return static::$resolver->connection($connection); + } + + /** + * Get the connection resolver instance. + * + * @return \Illuminate\Database\ConnectionResolverInterface + */ + public static function getConnectionResolver() + { + return static::$resolver; + } + + /** + * Set the connection resolver instance. + * + * @param \Illuminate\Database\ConnectionResolverInterface $resolver + * @return void + */ + public static function setConnectionResolver(Resolver $resolver) + { + static::$resolver = $resolver; + } + + /** + * Unset the connection resolver for models. + * + * @return void + */ + public static function unsetConnectionResolver() + { + static::$resolver = null; + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public static function getEventDispatcher() + { + return static::$dispatcher; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @return void + */ + public static function setEventDispatcher(Dispatcher $dispatcher) + { + static::$dispatcher = $dispatcher; + } + + /** + * Unset the event dispatcher for models. + * + * @return void + */ + public static function unsetEventDispatcher() + { + static::$dispatcher = null; + } + + /** + * Get the mutated attributes for a given instance. + * + * @return array + */ + public function getMutatedAttributes() + { + $class = get_class($this); + + if (! isset(static::$mutatorCache[$class])) { + static::cacheMutatedAttributes($class); + } + + return static::$mutatorCache[$class]; + } + + /** + * Extract and cache all the mutated attributes of a class. + * + * @param string $class + * @return void + */ + public static function cacheMutatedAttributes($class) + { + $mutatedAttributes = []; + + // Here we will extract all of the mutated attributes so that we can quickly + // spin through them after we export models to their array form, which we + // need to be fast. This'll let us know the attributes that can mutate. + if (preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches)) { + foreach ($matches[1] as $match) { + if (static::$snakeAttributes) { + $match = Str::snake($match); + } + + $mutatedAttributes[] = lcfirst($match); + } + } + + static::$mutatorCache[$class] = $mutatedAttributes; + } + + /** + * Dynamically retrieve attributes on the model. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->getAttribute($key); + } + + /** + * Dynamically set attributes on the model. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->setAttribute($key, $value); + } + + /** + * Determine if the given attribute exists. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->$offset); + } + + /** + * Get the value for a given offset. + * + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->$offset; + } + + /** + * Set the value for a given offset. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->$offset = $value; + } + + /** + * Unset the value for a given offset. + * + * @param mixed $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->$offset); + } + + /** + * Determine if an attribute or relation exists on the model. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + if (isset($this->attributes[$key]) || isset($this->relations[$key])) { + return true; + } + + if (method_exists($this, $key) && $this->$key && isset($this->relations[$key])) { + return true; + } + + return $this->hasGetMutator($key) && ! is_null($this->getAttributeValue($key)); + } + + /** + * Unset an attribute on the model. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->attributes[$key], $this->relations[$key]); + } + + /** + * Handle dynamic method calls into the model. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (in_array($method, ['increment', 'decrement'])) { + return call_user_func_array([$this, $method], $parameters); + } + + $query = $this->newQuery(); + + return call_user_func_array([$query, $method], $parameters); + } + + /** + * Handle dynamic static method calls into the method. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + $instance = new static; + + return call_user_func_array([$instance, $method], $parameters); + } + + /** + * Convert the model to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * When a model is being unserialized, check if it needs to be booted. + * + * @return void + */ + public function __wakeup() + { + $this->bootIfNotBooted(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ModelNotFoundException.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ModelNotFoundException.php new file mode 100644 index 0000000..102683a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ModelNotFoundException.php @@ -0,0 +1,40 @@ +model = $model; + + $this->message = "No query results for model [{$model}]."; + + return $this; + } + + /** + * Get the affected Eloquent model. + * + * @return string + */ + public function getModel() + { + return $this->model; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/QueueEntityResolver.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/QueueEntityResolver.php new file mode 100644 index 0000000..0e630c7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/QueueEntityResolver.php @@ -0,0 +1,27 @@ +find($id); + + if ($instance) { + return $instance; + } + + throw new EntityNotFoundException($type, $id); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php new file mode 100644 index 0000000..968749f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php @@ -0,0 +1,308 @@ +otherKey = $otherKey; + $this->relation = $relation; + $this->foreignKey = $foreignKey; + + parent::__construct($query, $parent); + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + return $this->query->first(); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) { + // For belongs to relationships, which are essentially the inverse of has one + // or has many relationships, we need to actually query on the primary key + // of the related models matching on the foreign key that's on a parent. + $table = $this->related->getTable(); + + $this->query->where($table.'.'.$this->otherKey, '=', $this->parent->{$this->foreignKey}); + } + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query, Builder $parent) + { + if ($parent->getQuery()->from == $query->getQuery()->from) { + return $this->getRelationCountQueryForSelfRelation($query, $parent); + } + + $query->select(new Expression('count(*)')); + + $otherKey = $this->wrap($query->getModel()->getTable().'.'.$this->otherKey); + + return $query->where($this->getQualifiedForeignKey(), '=', new Expression($otherKey)); + } + + /** + * Add the constraints for a relationship count query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQueryForSelfRelation(Builder $query, Builder $parent) + { + $query->select(new Expression('count(*)')); + + $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); + + $query->getModel()->setTable($hash); + + $key = $this->wrap($this->getQualifiedForeignKey()); + + return $query->where($hash.'.'.$query->getModel()->getKeyName(), '=', new Expression($key)); + } + + /** + * Get a relationship join table hash. + * + * @return string + */ + public function getRelationCountHash() + { + return 'self_'.md5(microtime(true)); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + // We'll grab the primary key name of the related models since it could be set to + // a non-standard name and not "id". We will then construct the constraint for + // our eagerly loading query so it returns the proper models from execution. + $key = $this->related->getTable().'.'.$this->otherKey; + + $this->query->whereIn($key, $this->getEagerModelKeys($models)); + } + + /** + * Gather the keys from an array of related models. + * + * @param array $models + * @return array + */ + protected function getEagerModelKeys(array $models) + { + $keys = []; + + // First we need to gather all of the keys from the parent models so we know what + // to query for via the eager loading query. We will add them to an array then + // execute a "where in" statement to gather up all of those related records. + foreach ($models as $model) { + if (! is_null($value = $model->{$this->foreignKey})) { + $keys[] = $value; + } + } + + // If there are no keys that were not null we will just return an array with 0 in + // it so the query doesn't fail, but will not return any results, which should + // be what this developer is expecting in a case where this happens to them. + if (count($keys) == 0) { + return [0]; + } + + return array_values(array_unique($keys)); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, null); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $foreign = $this->foreignKey; + + $other = $this->otherKey; + + // First we will get to build a dictionary of the child models by their primary + // key of the relationship, then we can easily match the children back onto + // the parents using that dictionary and the primary key of the children. + $dictionary = []; + + foreach ($results as $result) { + $dictionary[$result->getAttribute($other)] = $result; + } + + // Once we have the dictionary constructed, we can loop through all the parents + // and match back onto their children using these keys of the dictionary and + // the primary key of the children to map them onto the correct instances. + foreach ($models as $model) { + if (isset($dictionary[$model->$foreign])) { + $model->setRelation($relation, $dictionary[$model->$foreign]); + } + } + + return $models; + } + + /** + * Associate the model instance to the given parent. + * + * @param \Illuminate\Database\Eloquent\Model|int $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function associate($model) + { + $otherKey = ($model instanceof Model ? $model->getAttribute($this->otherKey) : $model); + + $this->parent->setAttribute($this->foreignKey, $otherKey); + + if ($model instanceof Model) { + $this->parent->setRelation($this->relation, $model); + } + + return $this->parent; + } + + /** + * Dissociate previously associated model from the given parent. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function dissociate() + { + $this->parent->setAttribute($this->foreignKey, null); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * Update the parent model on the relationship. + * + * @param array $attributes + * @return mixed + */ + public function update(array $attributes) + { + $instance = $this->getResults(); + + return $instance->fill($attributes)->save(); + } + + /** + * Get the foreign key of the relationship. + * + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the fully qualified foreign key of the relationship. + * + * @return string + */ + public function getQualifiedForeignKey() + { + return $this->parent->getTable().'.'.$this->foreignKey; + } + + /** + * Get the associated key of the relationship. + * + * @return string + */ + public function getOtherKey() + { + return $this->otherKey; + } + + /** + * Get the fully qualified associated key of the relationship. + * + * @return string + */ + public function getQualifiedOtherKeyName() + { + return $this->related->getTable().'.'.$this->otherKey; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php new file mode 100644 index 0000000..809e1e7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -0,0 +1,1255 @@ +table = $table; + $this->otherKey = $otherKey; + $this->foreignKey = $foreignKey; + $this->relationName = $relationName; + + parent::__construct($query, $parent); + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + return $this->get(); + } + + /** + * Set a where clause for a pivot table column. + * + * @param string $column + * @param string $operator + * @param mixed $value + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function wherePivot($column, $operator = null, $value = null, $boolean = 'and') + { + $this->pivotWheres[] = func_get_args(); + + return $this->where($this->table.'.'.$column, $operator, $value, $boolean); + } + + /** + * Set an or where clause for a pivot table column. + * + * @param string $column + * @param string $operator + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function orWherePivot($column, $operator = null, $value = null) + { + return $this->wherePivot($column, $operator, $value, 'or'); + } + + /** + * Execute the query and get the first result. + * + * @param array $columns + * @return mixed + */ + public function first($columns = ['*']) + { + $results = $this->take(1)->get($columns); + + return count($results) > 0 ? $results->first() : null; + } + + /** + * Execute the query and get the first result or throw an exception. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function firstOrFail($columns = ['*']) + { + if (! is_null($model = $this->first($columns))) { + return $model; + } + + throw new ModelNotFoundException; + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function get($columns = ['*']) + { + // First we'll add the proper select columns onto the query so it is run with + // the proper columns. Then, we will get the results and hydrate out pivot + // models with the result of those columns as a separate model relation. + $columns = $this->query->getQuery()->columns ? [] : $columns; + + $select = $this->getSelectColumns($columns); + + $models = $this->query->addSelect($select)->getModels(); + + $this->hydratePivotRelation($models); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded. This will solve the + // n + 1 query problem for the developer and also increase performance. + if (count($models) > 0) { + $models = $this->query->eagerLoadRelations($models); + } + + return $this->related->newCollection($models); + } + + /** + * Get a paginator for the "select" statement. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $this->query->addSelect($this->getSelectColumns($columns)); + + $paginator = $this->query->paginate($perPage, $columns, $pageName, $page); + + $this->hydratePivotRelation($paginator->items()); + + return $paginator; + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page') + { + $this->query->addSelect($this->getSelectColumns($columns)); + + $paginator = $this->query->simplePaginate($perPage, $columns, $pageName); + + $this->hydratePivotRelation($paginator->items()); + + return $paginator; + } + + /** + * Chunk the results of the query. + * + * @param int $count + * @param callable $callback + * @return bool + */ + public function chunk($count, callable $callback) + { + $this->query->addSelect($this->getSelectColumns()); + + return $this->query->chunk($count, function ($results) use ($callback) { + $this->hydratePivotRelation($results->all()); + + return $callback($results); + }); + } + + /** + * Hydrate the pivot table relationship on the models. + * + * @param array $models + * @return void + */ + protected function hydratePivotRelation(array $models) + { + // To hydrate the pivot relationship, we will just gather the pivot attributes + // and create a new Pivot model, which is basically a dynamic model that we + // will set the attributes, table, and connections on so it they be used. + foreach ($models as $model) { + $pivot = $this->newExistingPivot($this->cleanPivotAttributes($model)); + + $model->setRelation('pivot', $pivot); + } + } + + /** + * Get the pivot attributes from a model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return array + */ + protected function cleanPivotAttributes(Model $model) + { + $values = []; + + foreach ($model->getAttributes() as $key => $value) { + // To get the pivots attributes we will just take any of the attributes which + // begin with "pivot_" and add those to this arrays, as well as unsetting + // them from the parent's models since they exist in a different table. + if (strpos($key, 'pivot_') === 0) { + $values[substr($key, 6)] = $value; + + unset($model->$key); + } + } + + return $values; + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + $this->setJoin(); + + if (static::$constraints) { + $this->setWhere(); + } + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query, Builder $parent) + { + if ($parent->getQuery()->from == $query->getQuery()->from) { + return $this->getRelationCountQueryForSelfJoin($query, $parent); + } + + $this->setJoin($query); + + return parent::getRelationCountQuery($query, $parent); + } + + /** + * Add the constraints for a relationship count query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQueryForSelfJoin(Builder $query, Builder $parent) + { + $query->select(new Expression('count(*)')); + + $query->from($this->related->getTable().' as '.$hash = $this->getRelationCountHash()); + + $this->related->setTable($hash); + + $this->setJoin($query); + + return parent::getRelationCountQuery($query, $parent); + } + + /** + * Get a relationship join table hash. + * + * @return string + */ + public function getRelationCountHash() + { + return 'self_'.md5(microtime(true)); + } + + /** + * Set the select clause for the relation query. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function getSelectColumns(array $columns = ['*']) + { + if ($columns == ['*']) { + $columns = [$this->related->getTable().'.*']; + } + + return array_merge($columns, $this->getAliasedPivotColumns()); + } + + /** + * Get the pivot columns for the relation. + * + * @return array + */ + protected function getAliasedPivotColumns() + { + $defaults = [$this->foreignKey, $this->otherKey]; + + // We need to alias all of the pivot columns with the "pivot_" prefix so we + // can easily extract them out of the models and put them into the pivot + // relationships when they are retrieved and hydrated into the models. + $columns = []; + + foreach (array_merge($defaults, $this->pivotColumns) as $column) { + $columns[] = $this->table.'.'.$column.' as pivot_'.$column; + } + + return array_unique($columns); + } + + /** + * Determine whether the given column is defined as a pivot column. + * + * @param string $column + * @return bool + */ + protected function hasPivotColumn($column) + { + return in_array($column, $this->pivotColumns); + } + + /** + * Set the join clause for the relation query. + * + * @param \Illuminate\Database\Eloquent\Builder|null $query + * @return $this + */ + protected function setJoin($query = null) + { + $query = $query ?: $this->query; + + // We need to join to the intermediate table on the related model's primary + // key column with the intermediate table's foreign key for the related + // model instance. Then we can set the "where" for the parent models. + $baseTable = $this->related->getTable(); + + $key = $baseTable.'.'.$this->related->getKeyName(); + + $query->join($this->table, $key, '=', $this->getOtherKey()); + + return $this; + } + + /** + * Set the where clause for the relation query. + * + * @return $this + */ + protected function setWhere() + { + $foreign = $this->getForeignKey(); + + $this->query->where($foreign, '=', $this->parent->getKey()); + + return $this; + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $this->query->whereIn($this->getForeignKey(), $this->getKeys($models)); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $dictionary = $this->buildDictionary($results); + + // Once we have an array dictionary of child objects we can easily match the + // children back to their parent using the dictionary and the keys on the + // the parent models. Then we will return the hydrated models back out. + foreach ($models as $model) { + if (isset($dictionary[$key = $model->getKey()])) { + $collection = $this->related->newCollection($dictionary[$key]); + + $model->setRelation($relation, $collection); + } + } + + return $models; + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + $foreign = $this->foreignKey; + + // First we will build a dictionary of child models keyed by the foreign key + // of the relation so that we will easily and quickly match them to their + // parents without having a possibly slow inner loops for every models. + $dictionary = []; + + foreach ($results as $result) { + $dictionary[$result->pivot->$foreign][] = $result; + } + + return $dictionary; + } + + /** + * Touch all of the related models for the relationship. + * + * E.g.: Touch all roles associated with this user. + * + * @return void + */ + public function touch() + { + $key = $this->getRelated()->getKeyName(); + + $columns = $this->getRelatedFreshUpdate(); + + // If we actually have IDs for the relation, we will run the query to update all + // the related model's timestamps, to make sure these all reflect the changes + // to the parent models. This will help us keep any caching synced up here. + $ids = $this->getRelatedIds(); + + if (count($ids) > 0) { + $this->getRelated()->newQuery()->whereIn($key, $ids)->update($columns); + } + } + + /** + * Get all of the IDs for the related models. + * + * @return \Illuminate\Support\Collection + */ + public function getRelatedIds() + { + $related = $this->getRelated(); + + $fullKey = $related->getQualifiedKeyName(); + + return $this->getQuery()->select($fullKey)->lists($related->getKeyName()); + } + + /** + * Save a new model and attach it to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function save(Model $model, array $joining = [], $touch = true) + { + $model->save(['touch' => false]); + + $this->attach($model->getKey(), $joining, $touch); + + return $model; + } + + /** + * Save an array of new models and attach them to the parent model. + * + * @param \Illuminate\Support\Collection|array $models + * @param array $joinings + * @return array + */ + public function saveMany($models, array $joinings = []) + { + foreach ($models as $key => $model) { + $this->save($model, (array) Arr::get($joinings, $key), false); + } + + $this->touchIfTouching(); + + return $models; + } + + /** + * Find a related model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null + */ + public function find($id, $columns = ['*']) + { + if (is_array($id)) { + return $this->findMany($id, $columns); + } + + $this->where($this->getRelated()->getQualifiedKeyName(), '=', $id); + + return $this->first($columns); + } + + /** + * Find multiple related models by their primary keys. + * + * @param mixed $ids + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function findMany($ids, $columns = ['*']) + { + if (empty($ids)) { + return $this->getRelated()->newCollection(); + } + + $this->whereIn($this->getRelated()->getQualifiedKeyName(), $ids); + + return $this->get($columns); + } + + /** + * Find a related model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function findOrFail($id, $columns = ['*']) + { + $result = $this->find($id, $columns); + + if (is_array($id)) { + if (count($result) == count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->parent)); + } + + /** + * Find a related model by its primary key or return new instance of the related model. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model + */ + public function findOrNew($id, $columns = ['*']) + { + if (is_null($instance = $this->find($id, $columns))) { + $instance = $this->getRelated()->newInstance(); + } + + return $instance; + } + + /** + * Get the first related model record matching the attributes or instantiate it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrNew(array $attributes) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->related->newInstance($attributes); + } + + return $instance; + } + + /** + * Get the first related record matching the attributes or create it. + * + * @param array $attributes + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrCreate(array $attributes, array $joining = [], $touch = true) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->create($attributes, $joining, $touch); + } + + return $instance; + } + + /** + * Create or update a related record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true) + { + if (is_null($instance = $this->where($attributes)->first())) { + return $this->create($values, $joining, $touch); + } + + $instance->fill($values); + + $instance->save(['touch' => false]); + + return $instance; + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes, array $joining = [], $touch = true) + { + $instance = $this->related->newInstance($attributes); + + // Once we save the related model, we need to attach it to the base model via + // through intermediate table so we'll use the existing "attach" method to + // accomplish this which will insert the record and any more attributes. + $instance->save(['touch' => false]); + + $this->attach($instance->getKey(), $joining, $touch); + + return $instance; + } + + /** + * Create an array of new instances of the related models. + * + * @param array $records + * @param array $joinings + * @return array + */ + public function createMany(array $records, array $joinings = []) + { + $instances = []; + + foreach ($records as $key => $record) { + $instances[] = $this->create($record, (array) Arr::get($joinings, $key), false); + } + + $this->touchIfTouching(); + + return $instances; + } + + /** + * Sync the intermediate tables with a list of IDs or collection of models. + * + * @param \Illuminate\Database\Eloquent\Collection|array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], 'detached' => [], 'updated' => [], + ]; + + if ($ids instanceof Collection) { + $ids = $ids->modelKeys(); + } + + // First we need to attach any of the associated models that are not currently + // in this joining table. We'll spin through the given IDs, checking to see + // if they exist in the array of current ones, and if not we will insert. + $current = $this->newPivotQuery()->lists($this->otherKey); + + $records = $this->formatSyncList($ids); + + $detach = array_diff($current, array_keys($records)); + + // Next, we will take the differences of the currents and given IDs and detach + // all of the entities that exist in the "current" array but are not in the + // the array of the IDs given to the method which will complete the sync. + if ($detaching && count($detach) > 0) { + $this->detach($detach); + + $changes['detached'] = (array) array_map(function ($v) { + return is_numeric($v) ? (int) $v : (string) $v; + }, $detach); + } + + // Now we are finally ready to attach the new records. Note that we'll disable + // touching until after the entire operation is complete so we don't fire a + // ton of touch operations until we are totally done syncing the records. + $changes = array_merge( + $changes, $this->attachNew($records, $current, false) + ); + + if (count($changes['attached']) || count($changes['updated'])) { + $this->touchIfTouching(); + } + + return $changes; + } + + /** + * Format the sync list so that it is keyed by ID. + * + * @param array $records + * @return array + */ + protected function formatSyncList(array $records) + { + $results = []; + + foreach ($records as $id => $attributes) { + if (! is_array($attributes)) { + list($id, $attributes) = [$attributes, []]; + } + + $results[$id] = $attributes; + } + + return $results; + } + + /** + * Attach all of the IDs that aren't in the current array. + * + * @param array $records + * @param array $current + * @param bool $touch + * @return array + */ + protected function attachNew(array $records, array $current, $touch = true) + { + $changes = ['attached' => [], 'updated' => []]; + + foreach ($records as $id => $attributes) { + // If the ID is not in the list of existing pivot IDs, we will insert a new pivot + // record, otherwise, we will just update this existing record on this joining + // table, so that the developers will easily update these records pain free. + if (! in_array($id, $current)) { + $this->attach($id, $attributes, $touch); + + $changes['attached'][] = is_numeric($id) ? (int) $id : (string) $id; + } + + // Now we'll try to update an existing pivot record with the attributes that were + // given to the method. If the model is actually updated we will add it to the + // list of updated pivot records so we return them back out to the consumer. + elseif (count($attributes) > 0 && + $this->updateExistingPivot($id, $attributes, $touch)) { + $changes['updated'][] = is_numeric($id) ? (int) $id : (string) $id; + } + } + + return $changes; + } + + /** + * Update an existing pivot record on the table. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * @return int + */ + public function updateExistingPivot($id, array $attributes, $touch = true) + { + if (in_array($this->updatedAt(), $this->pivotColumns)) { + $attributes = $this->setTimestampsOnAttach($attributes, true); + } + + $updated = $this->newPivotStatementForId($id)->update($attributes); + + if ($touch) { + $this->touchIfTouching(); + } + + return $updated; + } + + /** + * Attach a model to the parent. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * @return void + */ + public function attach($id, array $attributes = [], $touch = true) + { + if ($id instanceof Model) { + $id = $id->getKey(); + } + + $query = $this->newPivotStatement(); + + $query->insert($this->createAttachRecords((array) $id, $attributes)); + + if ($touch) { + $this->touchIfTouching(); + } + } + + /** + * Create an array of records to insert into the pivot table. + * + * @param array $ids + * @param array $attributes + * @return array + */ + protected function createAttachRecords($ids, array $attributes) + { + $records = []; + + $timed = ($this->hasPivotColumn($this->createdAt()) || + $this->hasPivotColumn($this->updatedAt())); + + // To create the attachment records, we will simply spin through the IDs given + // and create a new record to insert for each ID. Each ID may actually be a + // key in the array, with extra attributes to be placed in other columns. + foreach ($ids as $key => $value) { + $records[] = $this->attacher($key, $value, $attributes, $timed); + } + + return $records; + } + + /** + * Create a full attachment record payload. + * + * @param int $key + * @param mixed $value + * @param array $attributes + * @param bool $timed + * @return array + */ + protected function attacher($key, $value, $attributes, $timed) + { + list($id, $extra) = $this->getAttachId($key, $value, $attributes); + + // To create the attachment records, we will simply spin through the IDs given + // and create a new record to insert for each ID. Each ID may actually be a + // key in the array, with extra attributes to be placed in other columns. + $record = $this->createAttachRecord($id, $timed); + + return array_merge($record, $extra); + } + + /** + * Get the attach record ID and extra attributes. + * + * @param mixed $key + * @param mixed $value + * @param array $attributes + * @return array + */ + protected function getAttachId($key, $value, array $attributes) + { + if (is_array($value)) { + return [$key, array_merge($value, $attributes)]; + } + + return [$value, $attributes]; + } + + /** + * Create a new pivot attachment record. + * + * @param int $id + * @param bool $timed + * @return array + */ + protected function createAttachRecord($id, $timed) + { + $record[$this->foreignKey] = $this->parent->getKey(); + + $record[$this->otherKey] = $id; + + // If the record needs to have creation and update timestamps, we will make + // them by calling the parent model's "freshTimestamp" method which will + // provide us with a fresh timestamp in this model's preferred format. + if ($timed) { + $record = $this->setTimestampsOnAttach($record); + } + + return $record; + } + + /** + * Set the creation and update timestamps on an attach record. + * + * @param array $record + * @param bool $exists + * @return array + */ + protected function setTimestampsOnAttach(array $record, $exists = false) + { + $fresh = $this->parent->freshTimestamp(); + + if (! $exists && $this->hasPivotColumn($this->createdAt())) { + $record[$this->createdAt()] = $fresh; + } + + if ($this->hasPivotColumn($this->updatedAt())) { + $record[$this->updatedAt()] = $fresh; + } + + return $record; + } + + /** + * Detach models from the relationship. + * + * @param int|array $ids + * @param bool $touch + * @return int + */ + public function detach($ids = [], $touch = true) + { + if ($ids instanceof Model) { + $ids = (array) $ids->getKey(); + } + + $query = $this->newPivotQuery(); + + // If associated IDs were passed to the method we will only delete those + // associations, otherwise all of the association ties will be broken. + // We'll return the numbers of affected rows when we do the deletes. + $ids = (array) $ids; + + if (count($ids) > 0) { + $query->whereIn($this->otherKey, (array) $ids); + } + + // Once we have all of the conditions set on the statement, we are ready + // to run the delete on the pivot table. Then, if the touch parameter + // is true, we will go ahead and touch all related models to sync. + $results = $query->delete(); + + if ($touch) { + $this->touchIfTouching(); + } + + return $results; + } + + /** + * If we're touching the parent model, touch. + * + * @return void + */ + public function touchIfTouching() + { + if ($this->touchingParent()) { + $this->getParent()->touch(); + } + + if ($this->getParent()->touches($this->relationName)) { + $this->touch(); + } + } + + /** + * Determine if we should touch the parent on sync. + * + * @return bool + */ + protected function touchingParent() + { + return $this->getRelated()->touches($this->guessInverseRelation()); + } + + /** + * Attempt to guess the name of the inverse of the relation. + * + * @return string + */ + protected function guessInverseRelation() + { + return Str::camel(Str::plural(class_basename($this->getParent()))); + } + + /** + * Create a new query builder for the pivot table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function newPivotQuery() + { + $query = $this->newPivotStatement(); + + foreach ($this->pivotWheres as $whereArgs) { + call_user_func_array([$query, 'where'], $whereArgs); + } + + return $query->where($this->foreignKey, $this->parent->getKey()); + } + + /** + * Get a new plain query builder for the pivot table. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newPivotStatement() + { + return $this->query->getQuery()->newQuery()->from($this->table); + } + + /** + * Get a new pivot statement for a given "other" ID. + * + * @param mixed $id + * @return \Illuminate\Database\Query\Builder + */ + public function newPivotStatementForId($id) + { + return $this->newPivotQuery()->where($this->otherKey, $id); + } + + /** + * Create a new pivot model instance. + * + * @param array $attributes + * @param bool $exists + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newPivot(array $attributes = [], $exists = false) + { + $pivot = $this->related->newPivot($this->parent, $attributes, $this->table, $exists); + + return $pivot->setPivotKeys($this->foreignKey, $this->otherKey); + } + + /** + * Create a new existing pivot model instance. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newExistingPivot(array $attributes = []) + { + return $this->newPivot($attributes, true); + } + + /** + * Set the columns on the pivot table to retrieve. + * + * @param array|mixed $columns + * @return $this + */ + public function withPivot($columns) + { + $columns = is_array($columns) ? $columns : func_get_args(); + + $this->pivotColumns = array_merge($this->pivotColumns, $columns); + + return $this; + } + + /** + * Specify that the pivot table has creation and update timestamps. + * + * @param mixed $createdAt + * @param mixed $updatedAt + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function withTimestamps($createdAt = null, $updatedAt = null) + { + $this->pivotCreatedAt = $createdAt; + $this->pivotUpdatedAt = $updatedAt; + + return $this->withPivot($this->createdAt(), $this->updatedAt()); + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function createdAt() + { + return $this->pivotCreatedAt ?: $this->parent->getCreatedAtColumn(); + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function updatedAt() + { + return $this->pivotUpdatedAt ?: $this->parent->getUpdatedAtColumn(); + } + + /** + * Get the related model's updated at column name. + * + * @return string + */ + public function getRelatedFreshUpdate() + { + return [$this->related->getUpdatedAtColumn() => $this->related->freshTimestamp()]; + } + + /** + * Get the key for comparing against the parent key in "has" query. + * + * @return string + */ + public function getHasCompareKey() + { + return $this->getForeignKey(); + } + + /** + * Get the fully qualified foreign key for the relation. + * + * @return string + */ + public function getForeignKey() + { + return $this->table.'.'.$this->foreignKey; + } + + /** + * Get the fully qualified "other key" for the relation. + * + * @return string + */ + public function getOtherKey() + { + return $this->table.'.'.$this->otherKey; + } + + /** + * Get the intermediate table for the relationship. + * + * @return string + */ + public function getTable() + { + return $this->table; + } + + /** + * Get the relationship name for the relationship. + * + * @return string + */ + public function getRelationName() + { + return $this->relationName; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php new file mode 100644 index 0000000..6149e47 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php @@ -0,0 +1,47 @@ +query->get(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchMany($models, $results, $relation); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php new file mode 100644 index 0000000..2ae4eea --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -0,0 +1,410 @@ +localKey = $localKey; + $this->firstKey = $firstKey; + $this->secondKey = $secondKey; + $this->farParent = $farParent; + + parent::__construct($query, $parent); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + $parentTable = $this->parent->getTable(); + + $localValue = $this->farParent[$this->localKey]; + + $this->setJoin(); + + if (static::$constraints) { + $this->query->where($parentTable.'.'.$this->firstKey, '=', $localValue); + } + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query, Builder $parent) + { + $parentTable = $this->parent->getTable(); + + $this->setJoin($query); + + $query->select(new Expression('count(*)')); + + $key = $this->wrap($parentTable.'.'.$this->firstKey); + + return $query->where($this->getHasCompareKey(), '=', new Expression($key)); + } + + /** + * Set the join clause on the query. + * + * @param \Illuminate\Database\Eloquent\Builder|null $query + * @return void + */ + protected function setJoin(Builder $query = null) + { + $query = $query ?: $this->query; + + $foreignKey = $this->related->getTable().'.'.$this->secondKey; + + $query->join($this->parent->getTable(), $this->getQualifiedParentKeyName(), '=', $foreignKey); + + if ($this->parentSoftDeletes()) { + $query->whereNull($this->parent->getQualifiedDeletedAtColumn()); + } + } + + /** + * Determine whether close parent of the relation uses Soft Deletes. + * + * @return bool + */ + public function parentSoftDeletes() + { + return in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses_recursive(get_class($this->parent))); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $table = $this->parent->getTable(); + + $this->query->whereIn($table.'.'.$this->firstKey, $this->getKeys($models)); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $dictionary = $this->buildDictionary($results); + + // Once we have the dictionary we can simply spin through the parent models to + // link them up with their children using the keyed dictionary to make the + // matching very convenient and easy work. Then we'll just return them. + foreach ($models as $model) { + $key = $model->getKey(); + + if (isset($dictionary[$key])) { + $value = $this->related->newCollection($dictionary[$key]); + + $model->setRelation($relation, $value); + } + } + + return $models; + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + $dictionary = []; + + $foreign = $this->firstKey; + + // First we will create a dictionary of models keyed by the foreign key of the + // relationship as this will allow us to quickly access all of the related + // models without having to do nested looping which will be quite slow. + foreach ($results as $result) { + $dictionary[$result->{$foreign}][] = $result; + } + + return $dictionary; + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + return $this->get(); + } + + /** + * Execute the query and get the first related model. + * + * @param array $columns + * @return mixed + */ + public function first($columns = ['*']) + { + $results = $this->take(1)->get($columns); + + return count($results) > 0 ? $results->first() : null; + } + + /** + * Execute the query and get the first result or throw an exception. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function firstOrFail($columns = ['*']) + { + if (! is_null($model = $this->first($columns))) { + return $model; + } + + throw new ModelNotFoundException; + } + + /** + * Find a related model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null + */ + public function find($id, $columns = ['*']) + { + if (is_array($id)) { + return $this->findMany($id, $columns); + } + + $this->where($this->getRelated()->getQualifiedKeyName(), '=', $id); + + return $this->first($columns); + } + + /** + * Find multiple related models by their primary keys. + * + * @param mixed $ids + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function findMany($ids, $columns = ['*']) + { + if (empty($ids)) { + return $this->getRelated()->newCollection(); + } + + $this->whereIn($this->getRelated()->getQualifiedKeyName(), $ids); + + return $this->get($columns); + } + + /** + * Find a related model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function findOrFail($id, $columns = ['*']) + { + $result = $this->find($id, $columns); + + if (is_array($id)) { + if (count($result) == count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->parent)); + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function get($columns = ['*']) + { + // First we'll add the proper select columns onto the query so it is run with + // the proper columns. Then, we will get the results and hydrate out pivot + // models with the result of those columns as a separate model relation. + $columns = $this->query->getQuery()->columns ? [] : $columns; + + $select = $this->getSelectColumns($columns); + + $models = $this->query->addSelect($select)->getModels(); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded. This will solve the + // n + 1 query problem for the developer and also increase performance. + if (count($models) > 0) { + $models = $this->query->eagerLoadRelations($models); + } + + return $this->related->newCollection($models); + } + + /** + * Set the select clause for the relation query. + * + * @param array $columns + * @return array + */ + protected function getSelectColumns(array $columns = ['*']) + { + if ($columns == ['*']) { + $columns = [$this->related->getTable().'.*']; + } + + return array_merge($columns, [$this->parent->getTable().'.'.$this->firstKey]); + } + + /** + * Get a paginator for the "select" statement. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @param int $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $this->query->addSelect($this->getSelectColumns($columns)); + + return $this->query->paginate($perPage, $columns, $pageName, $page); + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page') + { + $this->query->addSelect($this->getSelectColumns($columns)); + + return $this->query->simplePaginate($perPage, $columns, $pageName); + } + + /** + * Get the key for comparing against the parent key in "has" query. + * + * @return string + */ + public function getHasCompareKey() + { + return $this->farParent->getQualifiedKeyName(); + } + + /** + * Get the qualified foreign key on the related model. + * + * @return string + */ + public function getForeignKey() + { + return $this->related->getTable().'.'.$this->secondKey; + } + + /** + * Get the qualified foreign key on the "through" model. + * + * @return string + */ + public function getThroughKey() + { + return $this->parent->getTable().'.'.$this->firstKey; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOne.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOne.php new file mode 100644 index 0000000..52ad2a6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOne.php @@ -0,0 +1,47 @@ +query->first(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, null); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchOne($models, $results, $relation); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php new file mode 100644 index 0000000..dcf4de6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -0,0 +1,405 @@ +localKey = $localKey; + $this->foreignKey = $foreignKey; + + parent::__construct($query, $parent); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) { + $this->query->where($this->foreignKey, '=', $this->getParentKey()); + + $this->query->whereNotNull($this->foreignKey); + } + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query, Builder $parent) + { + if ($parent->getQuery()->from == $query->getQuery()->from) { + return $this->getRelationCountQueryForSelfRelation($query, $parent); + } + + return parent::getRelationCountQuery($query, $parent); + } + + /** + * Add the constraints for a relationship count query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQueryForSelfRelation(Builder $query, Builder $parent) + { + $query->select(new Expression('count(*)')); + + $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); + + $query->getModel()->setTable($hash); + + $key = $this->wrap($this->getQualifiedParentKeyName()); + + return $query->where($hash.'.'.$this->getPlainForeignKey(), '=', new Expression($key)); + } + + /** + * Get a relationship join table hash. + * + * @return string + */ + public function getRelationCountHash() + { + return 'self_'.md5(microtime(true)); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $this->query->whereIn($this->foreignKey, $this->getKeys($models, $this->localKey)); + } + + /** + * Match the eagerly loaded results to their single parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function matchOne(array $models, Collection $results, $relation) + { + return $this->matchOneOrMany($models, $results, $relation, 'one'); + } + + /** + * Match the eagerly loaded results to their many parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function matchMany(array $models, Collection $results, $relation) + { + return $this->matchOneOrMany($models, $results, $relation, 'many'); + } + + /** + * Match the eagerly loaded results to their many parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @param string $type + * @return array + */ + protected function matchOneOrMany(array $models, Collection $results, $relation, $type) + { + $dictionary = $this->buildDictionary($results); + + // Once we have the dictionary we can simply spin through the parent models to + // link them up with their children using the keyed dictionary to make the + // matching very convenient and easy work. Then we'll just return them. + foreach ($models as $model) { + $key = $model->getAttribute($this->localKey); + + if (isset($dictionary[$key])) { + $value = $this->getRelationValue($dictionary, $key, $type); + + $model->setRelation($relation, $value); + } + } + + return $models; + } + + /** + * Get the value of a relationship by one or many type. + * + * @param array $dictionary + * @param string $key + * @param string $type + * @return mixed + */ + protected function getRelationValue(array $dictionary, $key, $type) + { + $value = $dictionary[$key]; + + return $type == 'one' ? reset($value) : $this->related->newCollection($value); + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + $dictionary = []; + + $foreign = $this->getPlainForeignKey(); + + // First we will create a dictionary of models keyed by the foreign key of the + // relationship as this will allow us to quickly access all of the related + // models without having to do nested looping which will be quite slow. + foreach ($results as $result) { + $dictionary[$result->{$foreign}][] = $result; + } + + return $dictionary; + } + + /** + * Attach a model instance to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model|false + */ + public function save(Model $model) + { + $model->setAttribute($this->getPlainForeignKey(), $this->getParentKey()); + + return $model->save() ? $model : false; + } + + /** + * Attach a collection of models to the parent instance. + * + * @param \Illuminate\Database\Eloquent\Collection|array $models + * @return \Illuminate\Database\Eloquent\Collection|array + */ + public function saveMany($models) + { + foreach ($models as $model) { + $this->save($model); + } + + return $models; + } + + /** + * Find a model by its primary key or return new instance of the related model. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model + */ + public function findOrNew($id, $columns = ['*']) + { + if (is_null($instance = $this->find($id, $columns))) { + $instance = $this->related->newInstance(); + + $instance->setAttribute($this->getPlainForeignKey(), $this->getParentKey()); + } + + return $instance; + } + + /** + * Get the first related model record matching the attributes or instantiate it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrNew(array $attributes) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->related->newInstance($attributes); + + $instance->setAttribute($this->getPlainForeignKey(), $this->getParentKey()); + } + + return $instance; + } + + /** + * Get the first related record matching the attributes or create it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrCreate(array $attributes) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->create($attributes); + } + + return $instance; + } + + /** + * Create or update a related record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $attributes, array $values = []) + { + $instance = $this->firstOrNew($attributes); + + $instance->fill($values); + + $instance->save(); + + return $instance; + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes) + { + // Here we will set the raw attributes to avoid hitting the "fill" method so + // that we do not have to worry about a mass accessor rules blocking sets + // on the models. Otherwise, some of these attributes will not get set. + $instance = $this->related->newInstance($attributes); + + $instance->setAttribute($this->getPlainForeignKey(), $this->getParentKey()); + + $instance->save(); + + return $instance; + } + + /** + * Create an array of new instances of the related model. + * + * @param array $records + * @return array + */ + public function createMany(array $records) + { + $instances = []; + + foreach ($records as $record) { + $instances[] = $this->create($record); + } + + return $instances; + } + + /** + * Perform an update on all the related models. + * + * @param array $attributes + * @return int + */ + public function update(array $attributes) + { + if ($this->related->usesTimestamps()) { + $attributes[$this->relatedUpdatedAt()] = $this->related->freshTimestampString(); + } + + return $this->query->update($attributes); + } + + /** + * Get the key for comparing against the parent key in "has" query. + * + * @return string + */ + public function getHasCompareKey() + { + return $this->getForeignKey(); + } + + /** + * Get the foreign key for the relationship. + * + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the plain foreign key. + * + * @return string + */ + public function getPlainForeignKey() + { + $segments = explode('.', $this->getForeignKey()); + + return $segments[count($segments) - 1]; + } + + /** + * Get the key value of the parent's local key. + * + * @return mixed + */ + public function getParentKey() + { + return $this->parent->getAttribute($this->localKey); + } + + /** + * Get the fully qualified parent key name. + * + * @return string + */ + public function getQualifiedParentKeyName() + { + return $this->parent->getTable().'.'.$this->localKey; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php new file mode 100644 index 0000000..e2a5c5a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php @@ -0,0 +1,47 @@ +query->get(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchMany($models, $results, $relation); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOne.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOne.php new file mode 100644 index 0000000..339a68c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOne.php @@ -0,0 +1,47 @@ +query->first(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, null); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchOne($models, $results, $relation); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php new file mode 100644 index 0000000..658452c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php @@ -0,0 +1,233 @@ +morphType = $type; + + $this->morphClass = $parent->getMorphClass(); + + parent::__construct($query, $parent, $id, $localKey); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) { + parent::addConstraints(); + + $this->query->where($this->morphType, $this->morphClass); + } + } + + /** + * Get the relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query, Builder $parent) + { + $query = parent::getRelationCountQuery($query, $parent); + + return $query->where($this->morphType, $this->morphClass); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + parent::addEagerConstraints($models); + + $this->query->where($this->morphType, $this->morphClass); + } + + /** + * Attach a model instance to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function save(Model $model) + { + $model->setAttribute($this->getPlainMorphType(), $this->morphClass); + + return parent::save($model); + } + + /** + * Find a related model by its primary key or return new instance of the related model. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model + */ + public function findOrNew($id, $columns = ['*']) + { + if (is_null($instance = $this->find($id, $columns))) { + $instance = $this->related->newInstance(); + + // When saving a polymorphic relationship, we need to set not only the foreign + // key, but also the foreign key type, which is typically the class name of + // the parent model. This makes the polymorphic item unique in the table. + $this->setForeignAttributesForCreate($instance); + } + + return $instance; + } + + /** + * Get the first related model record matching the attributes or instantiate it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrNew(array $attributes) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->related->newInstance($attributes); + + // When saving a polymorphic relationship, we need to set not only the foreign + // key, but also the foreign key type, which is typically the class name of + // the parent model. This makes the polymorphic item unique in the table. + $this->setForeignAttributesForCreate($instance); + } + + return $instance; + } + + /** + * Get the first related record matching the attributes or create it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrCreate(array $attributes) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->create($attributes); + } + + return $instance; + } + + /** + * Create or update a related record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $attributes, array $values = []) + { + $instance = $this->firstOrNew($attributes); + + $instance->fill($values); + + $instance->save(); + + return $instance; + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes) + { + $instance = $this->related->newInstance($attributes); + + // When saving a polymorphic relationship, we need to set not only the foreign + // key, but also the foreign key type, which is typically the class name of + // the parent model. This makes the polymorphic item unique in the table. + $this->setForeignAttributesForCreate($instance); + + $instance->save(); + + return $instance; + } + + /** + * Set the foreign ID and type for creating a related model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return void + */ + protected function setForeignAttributesForCreate(Model $model) + { + $model->{$this->getPlainForeignKey()} = $this->getParentKey(); + + $model->{last(explode('.', $this->morphType))} = $this->morphClass; + } + + /** + * Get the foreign key "type" name. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * Get the plain morph type name without the table. + * + * @return string + */ + public function getPlainMorphType() + { + return last(explode('.', $this->morphType)); + } + + /** + * Get the class name of the parent model. + * + * @return string + */ + public function getMorphClass() + { + return $this->morphClass; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php new file mode 100644 index 0000000..b7a2f34 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php @@ -0,0 +1,79 @@ +where($this->morphType, $this->morphClass); + + return parent::setKeysForSaveQuery($query); + } + + /** + * Delete the pivot model record from the database. + * + * @return int + */ + public function delete() + { + $query = $this->getDeleteQuery(); + + $query->where($this->morphType, $this->morphClass); + + return $query->delete(); + } + + /** + * Set the morph type for the pivot. + * + * @param string $morphType + * @return $this + */ + public function setMorphType($morphType) + { + $this->morphType = $morphType; + + return $this; + } + + /** + * Set the morph class for the pivot. + * + * @param string $morphClass + * @return \Illuminate\Database\Eloquent\Relations\MorphPivot + */ + public function setMorphClass($morphClass) + { + $this->morphClass = $morphClass; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphTo.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphTo.php new file mode 100644 index 0000000..94b6feb --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphTo.php @@ -0,0 +1,268 @@ +morphType = $type; + + parent::__construct($query, $parent, $foreignKey, $otherKey, $relation); + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + if (! $this->otherKey) { + return; + } + + return $this->query->first(); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $this->buildDictionary($this->models = Collection::make($models)); + } + + /** + * Build a dictionary with the models. + * + * @param \Illuminate\Database\Eloquent\Collection $models + * @return void + */ + protected function buildDictionary(Collection $models) + { + foreach ($models as $model) { + if ($model->{$this->morphType}) { + $this->dictionary[$model->{$this->morphType}][$model->{$this->foreignKey}][] = $model; + } + } + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $models; + } + + /** + * Associate the model instance to the given parent. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function associate($model) + { + $this->parent->setAttribute($this->foreignKey, $model->getKey()); + + $this->parent->setAttribute($this->morphType, $model->getMorphClass()); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * Dissociate previously associated model from the given parent. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function dissociate() + { + $this->parent->setAttribute($this->foreignKey, null); + + $this->parent->setAttribute($this->morphType, null); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * Get the results of the relationship. + * + * Called via eager load method of Eloquent query builder. + * + * @return mixed + */ + public function getEager() + { + foreach (array_keys($this->dictionary) as $type) { + $this->matchToMorphParents($type, $this->getResultsByType($type)); + } + + return $this->models; + } + + /** + * Match the results for a given type to their parents. + * + * @param string $type + * @param \Illuminate\Database\Eloquent\Collection $results + * @return void + */ + protected function matchToMorphParents($type, Collection $results) + { + foreach ($results as $result) { + if (isset($this->dictionary[$type][$result->getKey()])) { + foreach ($this->dictionary[$type][$result->getKey()] as $model) { + $model->setRelation($this->relation, $result); + } + } + } + } + + /** + * Get all of the relation results for a type. + * + * @param string $type + * @return \Illuminate\Database\Eloquent\Collection + */ + protected function getResultsByType($type) + { + $instance = $this->createModelByType($type); + + $key = $instance->getTable().'.'.$instance->getKeyName(); + + $query = $instance->newQuery()->with($this->getQuery()->getEagerLoads()); + + $query = $this->useWithTrashed($query); + + return $query->whereIn($key, $this->gatherKeysByType($type)->all())->get(); + } + + /** + * Gather all of the foreign keys for a given type. + * + * @param string $type + * @return array + */ + protected function gatherKeysByType($type) + { + $foreign = $this->foreignKey; + + return collect($this->dictionary[$type])->map(function ($models) use ($foreign) { + return head($models)->{$foreign}; + })->values()->unique(); + } + + /** + * Create a new model instance by type. + * + * @param string $type + * @return \Illuminate\Database\Eloquent\Model + */ + public function createModelByType($type) + { + $class = $this->parent->getActualClassNameForMorph($type); + + return new $class; + } + + /** + * Get the foreign key "type" name. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * Get the dictionary used by the relationship. + * + * @return array + */ + public function getDictionary() + { + return $this->dictionary; + } + + /** + * Fetch soft-deleted model instances with query. + * + * @return $this + */ + public function withTrashed() + { + $this->withTrashed = true; + + $this->query = $this->useWithTrashed($this->query); + + return $this; + } + + /** + * Return trashed models with query if told so. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function useWithTrashed(Builder $query) + { + if ($this->withTrashed && $query->getMacro('withTrashed') !== null) { + return $query->withTrashed(); + } + + return $query; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php new file mode 100644 index 0000000..373005d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php @@ -0,0 +1,160 @@ +inverse = $inverse; + $this->morphType = $name.'_type'; + $this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass(); + + parent::__construct($query, $parent, $table, $foreignKey, $otherKey, $relationName); + } + + /** + * Set the where clause for the relation query. + * + * @return $this + */ + protected function setWhere() + { + parent::setWhere(); + + $this->query->where($this->table.'.'.$this->morphType, $this->morphClass); + + return $this; + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query, Builder $parent) + { + $query = parent::getRelationCountQuery($query, $parent); + + return $query->where($this->table.'.'.$this->morphType, $this->morphClass); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + parent::addEagerConstraints($models); + + $this->query->where($this->table.'.'.$this->morphType, $this->morphClass); + } + + /** + * Create a new pivot attachment record. + * + * @param int $id + * @param bool $timed + * @return array + */ + protected function createAttachRecord($id, $timed) + { + $record = parent::createAttachRecord($id, $timed); + + return Arr::add($record, $this->morphType, $this->morphClass); + } + + /** + * Create a new query builder for the pivot table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function newPivotQuery() + { + $query = parent::newPivotQuery(); + + return $query->where($this->morphType, $this->morphClass); + } + + /** + * Create a new pivot model instance. + * + * @param array $attributes + * @param bool $exists + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newPivot(array $attributes = [], $exists = false) + { + $pivot = new MorphPivot($this->parent, $attributes, $this->table, $exists); + + $pivot->setPivotKeys($this->foreignKey, $this->otherKey) + ->setMorphType($this->morphType) + ->setMorphClass($this->morphClass); + + return $pivot; + } + + /** + * Get the foreign key "type" name. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * Get the class name of the parent model. + * + * @return string + */ + public function getMorphClass() + { + return $this->morphClass; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Pivot.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Pivot.php new file mode 100644 index 0000000..4d80279 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Pivot.php @@ -0,0 +1,174 @@ +setTable($table); + + $this->setConnection($parent->getConnectionName()); + + $this->forceFill($attributes); + + $this->syncOriginal(); + + // We store off the parent instance so we will access the timestamp column names + // for the model, since the pivot model timestamps aren't easily configurable + // from the developer's point of view. We can use the parents to get these. + $this->parent = $parent; + + $this->exists = $exists; + + $this->timestamps = $this->hasTimestampAttributes(); + } + + /** + * Set the keys for a save update query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSaveQuery(Builder $query) + { + $query->where($this->foreignKey, $this->getAttribute($this->foreignKey)); + + return $query->where($this->otherKey, $this->getAttribute($this->otherKey)); + } + + /** + * Delete the pivot model record from the database. + * + * @return int + */ + public function delete() + { + return $this->getDeleteQuery()->delete(); + } + + /** + * Get the query builder for a delete operation on the pivot. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function getDeleteQuery() + { + $foreign = $this->getAttribute($this->foreignKey); + + $query = $this->newQuery()->where($this->foreignKey, $foreign); + + return $query->where($this->otherKey, $this->getAttribute($this->otherKey)); + } + + /** + * Get the foreign key column name. + * + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the "other key" column name. + * + * @return string + */ + public function getOtherKey() + { + return $this->otherKey; + } + + /** + * Set the key names for the pivot model instance. + * + * @param string $foreignKey + * @param string $otherKey + * @return $this + */ + public function setPivotKeys($foreignKey, $otherKey) + { + $this->foreignKey = $foreignKey; + + $this->otherKey = $otherKey; + + return $this; + } + + /** + * Determine if the pivot model has timestamp attributes. + * + * @return bool + */ + public function hasTimestampAttributes() + { + return array_key_exists($this->getCreatedAtColumn(), $this->attributes); + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function getCreatedAtColumn() + { + return $this->parent->getCreatedAtColumn(); + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function getUpdatedAtColumn() + { + return $this->parent->getUpdatedAtColumn(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php new file mode 100644 index 0000000..739cd37 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -0,0 +1,348 @@ +query = $query; + $this->parent = $parent; + $this->related = $query->getModel(); + + $this->addConstraints(); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + abstract public function addConstraints(); + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + abstract public function addEagerConstraints(array $models); + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + abstract public function initRelation(array $models, $relation); + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + abstract public function match(array $models, Collection $results, $relation); + + /** + * Get the results of the relationship. + * + * @return mixed + */ + abstract public function getResults(); + + /** + * Get the relationship for eager loading. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getEager() + { + return $this->get(); + } + + /** + * Touch all of the related models for the relationship. + * + * @return void + */ + public function touch() + { + $column = $this->getRelated()->getUpdatedAtColumn(); + + $this->rawUpdate([$column => $this->getRelated()->freshTimestampString()]); + } + + /** + * Run a raw update against the base query. + * + * @param array $attributes + * @return int + */ + public function rawUpdate(array $attributes = []) + { + return $this->query->update($attributes); + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parent + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query, Builder $parent) + { + $query->select(new Expression('count(*)')); + + $key = $this->wrap($this->getQualifiedParentKeyName()); + + return $query->where($this->getHasCompareKey(), '=', new Expression($key)); + } + + /** + * Run a callback with constraints disabled on the relation. + * + * @param \Closure $callback + * @return mixed + */ + public static function noConstraints(Closure $callback) + { + $previous = static::$constraints; + + static::$constraints = false; + + // When resetting the relation where clause, we want to shift the first element + // off of the bindings, leaving only the constraints that the developers put + // as "extra" on the relationships, and not original relation constraints. + try { + $results = call_user_func($callback); + } finally { + static::$constraints = $previous; + } + + return $results; + } + + /** + * Get all of the primary keys for an array of models. + * + * @param array $models + * @param string $key + * @return array + */ + protected function getKeys(array $models, $key = null) + { + return array_unique(array_values(array_map(function ($value) use ($key) { + return $key ? $value->getAttribute($key) : $value->getKey(); + }, $models))); + } + + /** + * Get the underlying query for the relation. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getQuery() + { + return $this->query; + } + + /** + * Get the base query builder driving the Eloquent builder. + * + * @return \Illuminate\Database\Query\Builder + */ + public function getBaseQuery() + { + return $this->query->getQuery(); + } + + /** + * Get the parent model of the relation. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * Get the fully qualified parent key name. + * + * @return string + */ + public function getQualifiedParentKeyName() + { + return $this->parent->getQualifiedKeyName(); + } + + /** + * Get the related model of the relation. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getRelated() + { + return $this->related; + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function createdAt() + { + return $this->parent->getCreatedAtColumn(); + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function updatedAt() + { + return $this->parent->getUpdatedAtColumn(); + } + + /** + * Get the name of the related model's "updated at" column. + * + * @return string + */ + public function relatedUpdatedAt() + { + return $this->related->getUpdatedAtColumn(); + } + + /** + * Wrap the given value with the parent query's grammar. + * + * @param string $value + * @return string + */ + public function wrap($value) + { + return $this->parent->newQueryWithoutScopes()->getQuery()->getGrammar()->wrap($value); + } + + /** + * Set or get the morph map for polymorphic relations. + * + * @param array|null $map + * @param bool $merge + * @return array + */ + public static function morphMap(array $map = null, $merge = true) + { + $map = static::buildMorphMapFromModels($map); + + if (is_array($map)) { + static::$morphMap = $merge ? array_merge(static::$morphMap, $map) : $map; + } + + return static::$morphMap; + } + + /** + * Builds a table-keyed array from model class names. + * + * @param string[]|null $models + * @return array|null + */ + protected static function buildMorphMapFromModels(array $models = null) + { + if (is_null($models) || Arr::isAssoc($models)) { + return $models; + } + + $tables = array_map(function ($model) { + return (new $model)->getTable(); + }, $models); + + return array_combine($tables, $models); + } + + /** + * Handle dynamic method calls to the relationship. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $result = call_user_func_array([$this->query, $method], $parameters); + + if ($result === $this->query) { + return $this; + } + + return $result; + } + + /** + * Force a clone of the underlying query builder when cloning. + * + * @return void + */ + public function __clone() + { + $this->query = clone $this->query; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ScopeInterface.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ScopeInterface.php new file mode 100644 index 0000000..fac2ca2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ScopeInterface.php @@ -0,0 +1,25 @@ +forceDeleting = true; + + $this->delete(); + + $this->forceDeleting = false; + } + + /** + * Perform the actual delete query on this model instance. + * + * @return mixed + */ + protected function performDeleteOnModel() + { + if ($this->forceDeleting) { + return $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey())->forceDelete(); + } + + return $this->runSoftDelete(); + } + + /** + * Perform the actual delete query on this model instance. + * + * @return void + */ + protected function runSoftDelete() + { + $query = $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey()); + + $this->{$this->getDeletedAtColumn()} = $time = $this->freshTimestamp(); + + $query->update([$this->getDeletedAtColumn() => $this->fromDateTime($time)]); + } + + /** + * Restore a soft-deleted model instance. + * + * @return bool|null + */ + public function restore() + { + // If the restoring event does not return false, we will proceed with this + // restore operation. Otherwise, we bail out so the developer will stop + // the restore totally. We will clear the deleted timestamp and save. + if ($this->fireModelEvent('restoring') === false) { + return false; + } + + $this->{$this->getDeletedAtColumn()} = null; + + // Once we have saved the model, we will fire the "restored" event so this + // developer will do anything they need to after a restore operation is + // totally finished. Then we will return the result of the save call. + $this->exists = true; + + $result = $this->save(); + + $this->fireModelEvent('restored', false); + + return $result; + } + + /** + * Determine if the model instance has been soft-deleted. + * + * @return bool + */ + public function trashed() + { + return ! is_null($this->{$this->getDeletedAtColumn()}); + } + + /** + * Get a new query builder that includes soft deletes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function withTrashed() + { + return (new static)->newQueryWithoutScope(new SoftDeletingScope); + } + + /** + * Get a new query builder that only includes soft deletes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function onlyTrashed() + { + $instance = new static; + + $column = $instance->getQualifiedDeletedAtColumn(); + + return $instance->newQueryWithoutScope(new SoftDeletingScope)->whereNotNull($column); + } + + /** + * Register a restoring model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function restoring($callback) + { + static::registerModelEvent('restoring', $callback); + } + + /** + * Register a restored model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function restored($callback) + { + static::registerModelEvent('restored', $callback); + } + + /** + * Get the name of the "deleted at" column. + * + * @return string + */ + public function getDeletedAtColumn() + { + return defined('static::DELETED_AT') ? static::DELETED_AT : 'deleted_at'; + } + + /** + * Get the fully qualified "deleted at" column. + * + * @return string + */ + public function getQualifiedDeletedAtColumn() + { + return $this->getTable().'.'.$this->getDeletedAtColumn(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletingScope.php b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletingScope.php new file mode 100644 index 0000000..f8786cf --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletingScope.php @@ -0,0 +1,155 @@ +whereNull($model->getQualifiedDeletedAtColumn()); + + $this->extend($builder); + } + + /** + * Remove the scope from the given Eloquent query builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Model $model + * @return void + */ + public function remove(Builder $builder, Model $model) + { + $column = $model->getQualifiedDeletedAtColumn(); + + $query = $builder->getQuery(); + + $query->wheres = collect($query->wheres)->reject(function ($where) use ($column) { + return $this->isSoftDeleteConstraint($where, $column); + })->values()->all(); + } + + /** + * Extend the query builder with the needed functions. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + public function extend(Builder $builder) + { + foreach ($this->extensions as $extension) { + $this->{"add{$extension}"}($builder); + } + + $builder->onDelete(function (Builder $builder) { + $column = $this->getDeletedAtColumn($builder); + + return $builder->update([ + $column => $builder->getModel()->freshTimestampString(), + ]); + }); + } + + /** + * Get the "deleted at" column for the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return string + */ + protected function getDeletedAtColumn(Builder $builder) + { + if (count($builder->getQuery()->joins) > 0) { + return $builder->getModel()->getQualifiedDeletedAtColumn(); + } else { + return $builder->getModel()->getDeletedAtColumn(); + } + } + + /** + * Add the force delete extension to the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function addForceDelete(Builder $builder) + { + $builder->macro('forceDelete', function (Builder $builder) { + return $builder->getQuery()->delete(); + }); + } + + /** + * Add the restore extension to the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function addRestore(Builder $builder) + { + $builder->macro('restore', function (Builder $builder) { + $builder->withTrashed(); + + return $builder->update([$builder->getModel()->getDeletedAtColumn() => null]); + }); + } + + /** + * Add the with-trashed extension to the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function addWithTrashed(Builder $builder) + { + $builder->macro('withTrashed', function (Builder $builder) { + $this->remove($builder, $builder->getModel()); + + return $builder; + }); + } + + /** + * Add the only-trashed extension to the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function addOnlyTrashed(Builder $builder) + { + $builder->macro('onlyTrashed', function (Builder $builder) { + $model = $builder->getModel(); + + $this->remove($builder, $model); + + $builder->getQuery()->whereNotNull($model->getQualifiedDeletedAtColumn()); + + return $builder; + }); + } + + /** + * Determine if the given where clause is a soft delete constraint. + * + * @param array $where + * @param string $column + * @return bool + */ + protected function isSoftDeleteConstraint(array $where, $column) + { + return $where['type'] == 'Null' && $where['column'] == $column; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Grammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Grammar.php new file mode 100644 index 0000000..71645d8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Grammar.php @@ -0,0 +1,188 @@ +isExpression($table)) { + return $this->getValue($table); + } + + return $this->wrap($this->tablePrefix.$table, true); + } + + /** + * Wrap a value in keyword identifiers. + * + * @param \Illuminate\Database\Query\Expression|string $value + * @param bool $prefixAlias + * @return string + */ + public function wrap($value, $prefixAlias = false) + { + if ($this->isExpression($value)) { + return $this->getValue($value); + } + + // If the value being wrapped has a column alias we will need to separate out + // the pieces so we can wrap each of the segments of the expression on it + // own, and then joins them both back together with the "as" connector. + if (strpos(strtolower($value), ' as ') !== false) { + $segments = explode(' ', $value); + + if ($prefixAlias) { + $segments[2] = $this->tablePrefix.$segments[2]; + } + + return $this->wrap($segments[0]).' as '.$this->wrapValue($segments[2]); + } + + $wrapped = []; + + $segments = explode('.', $value); + + // If the value is not an aliased table expression, we'll just wrap it like + // normal, so if there is more than one segment, we will wrap the first + // segments as if it was a table and the rest as just regular values. + foreach ($segments as $key => $segment) { + if ($key == 0 && count($segments) > 1) { + $wrapped[] = $this->wrapTable($segment); + } else { + $wrapped[] = $this->wrapValue($segment); + } + } + + return implode('.', $wrapped); + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + if ($value === '*') { + return $value; + } + + return '"'.str_replace('"', '""', $value).'"'; + } + + /** + * Convert an array of column names into a delimited string. + * + * @param array $columns + * @return string + */ + public function columnize(array $columns) + { + return implode(', ', array_map([$this, 'wrap'], $columns)); + } + + /** + * Create query parameter place-holders for an array. + * + * @param array $values + * @return string + */ + public function parameterize(array $values) + { + return implode(', ', array_map([$this, 'parameter'], $values)); + } + + /** + * Get the appropriate query parameter place-holder for a value. + * + * @param mixed $value + * @return string + */ + public function parameter($value) + { + return $this->isExpression($value) ? $this->getValue($value) : '?'; + } + + /** + * Get the value of a raw expression. + * + * @param \Illuminate\Database\Query\Expression $expression + * @return string + */ + public function getValue($expression) + { + return $expression->getValue(); + } + + /** + * Determine if the given value is a raw expression. + * + * @param mixed $value + * @return bool + */ + public function isExpression($value) + { + return $value instanceof Expression; + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + public function getDateFormat() + { + return 'Y-m-d H:i:s'; + } + + /** + * Get the grammar's table prefix. + * + * @return string + */ + public function getTablePrefix() + { + return $this->tablePrefix; + } + + /** + * Set the grammar's table prefix. + * + * @param string $prefix + * @return $this + */ + public function setTablePrefix($prefix) + { + $this->tablePrefix = $prefix; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/MigrationServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Database/MigrationServiceProvider.php new file mode 100644 index 0000000..ffcea49 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/MigrationServiceProvider.php @@ -0,0 +1,221 @@ +registerRepository(); + + // Once we have registered the migrator instance we will go ahead and register + // all of the migration related commands that are used by the "Artisan" CLI + // so that they may be easily accessed for registering with the consoles. + $this->registerMigrator(); + + $this->registerCommands(); + } + + /** + * Register the migration repository service. + * + * @return void + */ + protected function registerRepository() + { + $this->app->singleton('migration.repository', function ($app) { + $table = $app['config']['database.migrations']; + + return new DatabaseMigrationRepository($app['db'], $table); + }); + } + + /** + * Register the migrator service. + * + * @return void + */ + protected function registerMigrator() + { + // The migrator is responsible for actually running and rollback the migration + // files in the application. We'll pass in our database connection resolver + // so the migrator can resolve any of these connections when it needs to. + $this->app->singleton('migrator', function ($app) { + $repository = $app['migration.repository']; + + return new Migrator($repository, $app['db'], $app['files']); + }); + } + + /** + * Register all of the migration commands. + * + * @return void + */ + protected function registerCommands() + { + $commands = ['Migrate', 'Rollback', 'Reset', 'Refresh', 'Install', 'Make', 'Status']; + + // We'll simply spin through the list of commands that are migration related + // and register each one of them with an application container. They will + // be resolved in the Artisan start file and registered on the console. + foreach ($commands as $command) { + $this->{'register'.$command.'Command'}(); + } + + // Once the commands are registered in the application IoC container we will + // register them with the Artisan start event so that these are available + // when the Artisan application actually starts up and is getting used. + $this->commands( + 'command.migrate', 'command.migrate.make', + 'command.migrate.install', 'command.migrate.rollback', + 'command.migrate.reset', 'command.migrate.refresh', + 'command.migrate.status' + ); + } + + /** + * Register the "migrate" migration command. + * + * @return void + */ + protected function registerMigrateCommand() + { + $this->app->singleton('command.migrate', function ($app) { + return new MigrateCommand($app['migrator']); + }); + } + + /** + * Register the "rollback" migration command. + * + * @return void + */ + protected function registerRollbackCommand() + { + $this->app->singleton('command.migrate.rollback', function ($app) { + return new RollbackCommand($app['migrator']); + }); + } + + /** + * Register the "reset" migration command. + * + * @return void + */ + protected function registerResetCommand() + { + $this->app->singleton('command.migrate.reset', function ($app) { + return new ResetCommand($app['migrator']); + }); + } + + /** + * Register the "refresh" migration command. + * + * @return void + */ + protected function registerRefreshCommand() + { + $this->app->singleton('command.migrate.refresh', function () { + return new RefreshCommand; + }); + } + + /** + * Register the "status" migration command. + * + * @return void + */ + protected function registerStatusCommand() + { + $this->app->singleton('command.migrate.status', function ($app) { + return new StatusCommand($app['migrator']); + }); + } + + /** + * Register the "install" migration command. + * + * @return void + */ + protected function registerInstallCommand() + { + $this->app->singleton('command.migrate.install', function ($app) { + return new InstallCommand($app['migration.repository']); + }); + } + + /** + * Register the "make" migration command. + * + * @return void + */ + protected function registerMakeCommand() + { + $this->registerCreator(); + + $this->app->singleton('command.migrate.make', function ($app) { + // Once we have the migration creator registered, we will create the command + // and inject the creator. The creator is responsible for the actual file + // creation of the migrations, and may be extended by these developers. + $creator = $app['migration.creator']; + + $composer = $app['composer']; + + return new MigrateMakeCommand($creator, $composer); + }); + } + + /** + * Register the migration creator. + * + * @return void + */ + protected function registerCreator() + { + $this->app->singleton('migration.creator', function ($app) { + return new MigrationCreator($app['files']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'migrator', 'migration.repository', 'command.migrate', + 'command.migrate.rollback', 'command.migrate.reset', + 'command.migrate.refresh', 'command.migrate.install', + 'command.migrate.status', 'migration.creator', + 'command.migrate.make', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php new file mode 100644 index 0000000..13f3da4 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php @@ -0,0 +1,184 @@ +table = $table; + $this->resolver = $resolver; + } + + /** + * Get the ran migrations. + * + * @return array + */ + public function getRan() + { + return $this->table() + ->orderBy('batch', 'asc') + ->orderBy('migration', 'asc') + ->lists('migration'); + } + + /** + * Get the last migration batch. + * + * @return array + */ + public function getLast() + { + $query = $this->table()->where('batch', $this->getLastBatchNumber()); + + return $query->orderBy('migration', 'desc')->get(); + } + + /** + * Log that a migration was run. + * + * @param string $file + * @param int $batch + * @return void + */ + public function log($file, $batch) + { + $record = ['migration' => $file, 'batch' => $batch]; + + $this->table()->insert($record); + } + + /** + * Remove a migration from the log. + * + * @param object $migration + * @return void + */ + public function delete($migration) + { + $this->table()->where('migration', $migration->migration)->delete(); + } + + /** + * Get the next migration batch number. + * + * @return int + */ + public function getNextBatchNumber() + { + return $this->getLastBatchNumber() + 1; + } + + /** + * Get the last migration batch number. + * + * @return int + */ + public function getLastBatchNumber() + { + return $this->table()->max('batch'); + } + + /** + * Create the migration repository data store. + * + * @return void + */ + public function createRepository() + { + $schema = $this->getConnection()->getSchemaBuilder(); + + $schema->create($this->table, function ($table) { + // The migrations table is responsible for keeping track of which of the + // migrations have actually run for the application. We'll create the + // table to hold the migration file's path as well as the batch ID. + $table->string('migration'); + + $table->integer('batch'); + }); + } + + /** + * Determine if the migration repository exists. + * + * @return bool + */ + public function repositoryExists() + { + $schema = $this->getConnection()->getSchemaBuilder(); + + return $schema->hasTable($this->table); + } + + /** + * Get a query builder for the migration table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function table() + { + return $this->getConnection()->table($this->table); + } + + /** + * Get the connection resolver instance. + * + * @return \Illuminate\Database\ConnectionResolverInterface + */ + public function getConnectionResolver() + { + return $this->resolver; + } + + /** + * Resolve the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->resolver->connection($this->connection); + } + + /** + * Set the information source to gather data. + * + * @param string $name + * @return void + */ + public function setSource($name) + { + $this->connection = $name; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migration.php b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migration.php new file mode 100644 index 0000000..699154c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migration.php @@ -0,0 +1,23 @@ +connection; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationCreator.php b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationCreator.php new file mode 100644 index 0000000..beacfe1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationCreator.php @@ -0,0 +1,181 @@ +files = $files; + } + + /** + * Create a new migration at the given path. + * + * @param string $name + * @param string $path + * @param string $table + * @param bool $create + * @return string + */ + public function create($name, $path, $table = null, $create = false) + { + $path = $this->getPath($name, $path); + + // First we will get the stub file for the migration, which serves as a type + // of template for the migration. Once we have those we will populate the + // various place-holders, save the file, and run the post create event. + $stub = $this->getStub($table, $create); + + $this->files->put($path, $this->populateStub($name, $stub, $table)); + + $this->firePostCreateHooks(); + + return $path; + } + + /** + * Get the migration stub file. + * + * @param string $table + * @param bool $create + * @return string + */ + protected function getStub($table, $create) + { + if (is_null($table)) { + return $this->files->get($this->getStubPath().'/blank.stub'); + } + + // We also have stubs for creating new tables and modifying existing tables + // to save the developer some typing when they are creating a new tables + // or modifying existing tables. We'll grab the appropriate stub here. + else { + $stub = $create ? 'create.stub' : 'update.stub'; + + return $this->files->get($this->getStubPath()."/{$stub}"); + } + } + + /** + * Populate the place-holders in the migration stub. + * + * @param string $name + * @param string $stub + * @param string $table + * @return string + */ + protected function populateStub($name, $stub, $table) + { + $stub = str_replace('DummyClass', $this->getClassName($name), $stub); + + // Here we will replace the table place-holders with the table specified by + // the developer, which is useful for quickly creating a tables creation + // or update migration from the console instead of typing it manually. + if (! is_null($table)) { + $stub = str_replace('DummyTable', $table, $stub); + } + + return $stub; + } + + /** + * Get the class name of a migration name. + * + * @param string $name + * @return string + */ + protected function getClassName($name) + { + return Str::studly($name); + } + + /** + * Fire the registered post create hooks. + * + * @return void + */ + protected function firePostCreateHooks() + { + foreach ($this->postCreate as $callback) { + call_user_func($callback); + } + } + + /** + * Register a post migration create hook. + * + * @param \Closure $callback + * @return void + */ + public function afterCreate(Closure $callback) + { + $this->postCreate[] = $callback; + } + + /** + * Get the full path name to the migration. + * + * @param string $name + * @param string $path + * @return string + */ + protected function getPath($name, $path) + { + return $path.'/'.$this->getDatePrefix().'_'.$name.'.php'; + } + + /** + * Get the date prefix for the migration. + * + * @return string + */ + protected function getDatePrefix() + { + return date('Y_m_d_His'); + } + + /** + * Get the path to the stubs. + * + * @return string + */ + public function getStubPath() + { + return __DIR__.'/stubs'; + } + + /** + * Get the filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationRepositoryInterface.php b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationRepositoryInterface.php new file mode 100644 index 0000000..5450a7a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationRepositoryInterface.php @@ -0,0 +1,66 @@ +files = $files; + $this->resolver = $resolver; + $this->repository = $repository; + } + + /** + * Run the outstanding migrations at a given path. + * + * @param string $path + * @param bool $pretend + * @return void + */ + public function run($path, $pretend = false) + { + $this->notes = []; + + $files = $this->getMigrationFiles($path); + + // Once we grab all of the migration files for the path, we will compare them + // against the migrations that have already been run for this package then + // run each of the outstanding migrations against a database connection. + $ran = $this->repository->getRan(); + + $migrations = array_diff($files, $ran); + + $this->requireFiles($path, $migrations); + + $this->runMigrationList($migrations, $pretend); + } + + /** + * Run an array of migrations. + * + * @param array $migrations + * @param bool $pretend + * @return void + */ + public function runMigrationList($migrations, $pretend = false) + { + // First we will just make sure that there are any migrations to run. If there + // aren't, we will just make a note of it to the developer so they're aware + // that all of the migrations have been run against this database system. + if (count($migrations) == 0) { + $this->note('Nothing to migrate.'); + + return; + } + + $batch = $this->repository->getNextBatchNumber(); + + // Once we have the array of migrations, we will spin through them and run the + // migrations "up" so the changes are made to the databases. We'll then log + // that the migration was run so we don't repeat it next time we execute. + foreach ($migrations as $file) { + $this->runUp($file, $batch, $pretend); + } + } + + /** + * Run "up" a migration instance. + * + * @param string $file + * @param int $batch + * @param bool $pretend + * @return void + */ + protected function runUp($file, $batch, $pretend) + { + // First we will resolve a "real" instance of the migration class from this + // migration file name. Once we have the instances we can run the actual + // command such as "up" or "down", or we can just simulate the action. + $migration = $this->resolve($file); + + if ($pretend) { + return $this->pretendToRun($migration, 'up'); + } + + $migration->up(); + + // Once we have run a migrations class, we will log that it was run in this + // repository so that we don't try to run it next time we do a migration + // in the application. A migration repository keeps the migrate order. + $this->repository->log($file, $batch); + + $this->note("Migrated: $file"); + } + + /** + * Rollback the last migration operation. + * + * @param bool $pretend + * @return int + */ + public function rollback($pretend = false) + { + $this->notes = []; + + // We want to pull in the last batch of migrations that ran on the previous + // migration operation. We'll then reverse those migrations and run each + // of them "down" to reverse the last migration "operation" which ran. + $migrations = $this->repository->getLast(); + + $count = count($migrations); + + if ($count === 0) { + $this->note('Nothing to rollback.'); + } else { + // We need to reverse these migrations so that they are "downed" in reverse + // to what they run on "up". It lets us backtrack through the migrations + // and properly reverse the entire database schema operation that ran. + foreach ($migrations as $migration) { + $this->runDown((object) $migration, $pretend); + } + } + + return $count; + } + + /** + * Rolls all of the currently applied migrations back. + * + * @param bool $pretend + * @return int + */ + public function reset($pretend = false) + { + $this->notes = []; + + $migrations = array_reverse($this->repository->getRan()); + + $count = count($migrations); + + if ($count === 0) { + $this->note('Nothing to rollback.'); + } else { + foreach ($migrations as $migration) { + $this->runDown((object) ['migration' => $migration], $pretend); + } + } + + return $count; + } + + /** + * Run "down" a migration instance. + * + * @param object $migration + * @param bool $pretend + * @return void + */ + protected function runDown($migration, $pretend) + { + $file = $migration->migration; + + // First we will get the file name of the migration so we can resolve out an + // instance of the migration. Once we get an instance we can either run a + // pretend execution of the migration or we can run the real migration. + $instance = $this->resolve($file); + + if ($pretend) { + return $this->pretendToRun($instance, 'down'); + } + + $instance->down(); + + // Once we have successfully run the migration "down" we will remove it from + // the migration repository so it will be considered to have not been run + // by the application then will be able to fire by any later operation. + $this->repository->delete($migration); + + $this->note("Rolled back: $file"); + } + + /** + * Get all of the migration files in a given path. + * + * @param string $path + * @return array + */ + public function getMigrationFiles($path) + { + $files = $this->files->glob($path.'/*_*.php'); + + // Once we have the array of files in the directory we will just remove the + // extension and take the basename of the file which is all we need when + // finding the migrations that haven't been run against the databases. + if ($files === false) { + return []; + } + + $files = array_map(function ($file) { + return str_replace('.php', '', basename($file)); + }, $files); + + // Once we have all of the formatted file names we will sort them and since + // they all start with a timestamp this should give us the migrations in + // the order they were actually created by the application developers. + sort($files); + + return $files; + } + + /** + * Require in all the migration files in a given path. + * + * @param string $path + * @param array $files + * @return void + */ + public function requireFiles($path, array $files) + { + foreach ($files as $file) { + $this->files->requireOnce($path.'/'.$file.'.php'); + } + } + + /** + * Pretend to run the migrations. + * + * @param object $migration + * @param string $method + * @return void + */ + protected function pretendToRun($migration, $method) + { + foreach ($this->getQueries($migration, $method) as $query) { + $name = get_class($migration); + + $this->note("{$name}: {$query['query']}"); + } + } + + /** + * Get all of the queries that would be run for a migration. + * + * @param object $migration + * @param string $method + * @return array + */ + protected function getQueries($migration, $method) + { + $connection = $migration->getConnection(); + + // Now that we have the connections we can resolve it and pretend to run the + // queries against the database returning the array of raw SQL statements + // that would get fired against the database system for this migration. + $db = $this->resolveConnection($connection); + + return $db->pretend(function () use ($migration, $method) { + $migration->$method(); + }); + } + + /** + * Resolve a migration instance from a file. + * + * @param string $file + * @return object + */ + public function resolve($file) + { + $file = implode('_', array_slice(explode('_', $file), 4)); + + $class = Str::studly($file); + + return new $class; + } + + /** + * Raise a note event for the migrator. + * + * @param string $message + * @return void + */ + protected function note($message) + { + $this->notes[] = $message; + } + + /** + * Get the notes for the last operation. + * + * @return array + */ + public function getNotes() + { + return $this->notes; + } + + /** + * Resolve the database connection instance. + * + * @param string $connection + * @return \Illuminate\Database\Connection + */ + public function resolveConnection($connection) + { + return $this->resolver->connection($connection); + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setConnection($name) + { + if (! is_null($name)) { + $this->resolver->setDefaultConnection($name); + } + + $this->repository->setSource($name); + + $this->connection = $name; + } + + /** + * Get the migration repository instance. + * + * @return \Illuminate\Database\Migrations\MigrationRepositoryInterface + */ + public function getRepository() + { + return $this->repository; + } + + /** + * Determine if the migration repository exists. + * + * @return bool + */ + public function repositoryExists() + { + return $this->repository->repositoryExists(); + } + + /** + * Get the file system instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/blank.stub b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/blank.stub new file mode 100644 index 0000000..4ff5ee5 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/blank.stub @@ -0,0 +1,27 @@ +increments('id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('DummyTable'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/update.stub b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/update.stub new file mode 100644 index 0000000..8114a81 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/update.stub @@ -0,0 +1,31 @@ +schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new MySqlBuilder($this); + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\MySqlGrammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new QueryGrammar); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\MySqlGrammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\MySqlProcessor + */ + protected function getDefaultPostProcessor() + { + return new MySqlProcessor; + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Doctrine\DBAL\Driver\PDOMySql\Driver + */ + protected function getDoctrineDriver() + { + return new DoctrineDriver; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/PostgresConnection.php b/core/vendor/laravel/framework/src/Illuminate/Database/PostgresConnection.php new file mode 100644 index 0000000..779888d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/PostgresConnection.php @@ -0,0 +1,66 @@ +schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new PostgresBuilder($this); + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\PostgresGrammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new QueryGrammar); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\PostgresGrammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\PostgresProcessor + */ + protected function getDefaultPostProcessor() + { + return new PostgresProcessor; + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Doctrine\DBAL\Driver\PDOPgSql\Driver + */ + protected function getDoctrineDriver() + { + return new DoctrineDriver; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php new file mode 100644 index 0000000..929ec01 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php @@ -0,0 +1,2125 @@ + [], + 'join' => [], + 'where' => [], + 'having' => [], + 'order' => [], + 'union' => [], + ]; + + /** + * An aggregate function and column to be run. + * + * @var array + */ + public $aggregate; + + /** + * The columns that should be returned. + * + * @var array + */ + public $columns; + + /** + * Indicates if the query returns distinct results. + * + * @var bool + */ + public $distinct = false; + + /** + * The table which the query is targeting. + * + * @var string + */ + public $from; + + /** + * The table joins for the query. + * + * @var array + */ + public $joins; + + /** + * The where constraints for the query. + * + * @var array + */ + public $wheres; + + /** + * The groupings for the query. + * + * @var array + */ + public $groups; + + /** + * The having constraints for the query. + * + * @var array + */ + public $havings; + + /** + * The orderings for the query. + * + * @var array + */ + public $orders; + + /** + * The maximum number of records to return. + * + * @var int + */ + public $limit; + + /** + * The number of records to skip. + * + * @var int + */ + public $offset; + + /** + * The query union statements. + * + * @var array + */ + public $unions; + + /** + * The maximum number of union records to return. + * + * @var int + */ + public $unionLimit; + + /** + * The number of union records to skip. + * + * @var int + */ + public $unionOffset; + + /** + * The orderings for the union query. + * + * @var array + */ + public $unionOrders; + + /** + * Indicates whether row locking is being used. + * + * @var string|bool + */ + public $lock; + + /** + * The field backups currently in use. + * + * @var array + */ + protected $backups = []; + + /** + * The binding backups currently in use. + * + * @var array + */ + protected $bindingBackups = []; + + /** + * All of the available clause operators. + * + * @var array + */ + protected $operators = [ + '=', '<', '>', '<=', '>=', '<>', '!=', + 'like', 'like binary', 'not like', 'between', 'ilike', + '&', '|', '^', '<<', '>>', + 'rlike', 'regexp', 'not regexp', + '~', '~*', '!~', '!~*', 'similar to', + 'not similar to', + ]; + + /** + * Whether use write pdo for select. + * + * @var bool + */ + protected $useWritePdo = false; + + /** + * Create a new query builder instance. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\Query\Grammars\Grammar $grammar + * @param \Illuminate\Database\Query\Processors\Processor $processor + * @return void + */ + public function __construct(ConnectionInterface $connection, + Grammar $grammar, + Processor $processor) + { + $this->grammar = $grammar; + $this->processor = $processor; + $this->connection = $connection; + } + + /** + * Set the columns to be selected. + * + * @param array|mixed $columns + * @return $this + */ + public function select($columns = ['*']) + { + $this->columns = is_array($columns) ? $columns : func_get_args(); + + return $this; + } + + /** + * Add a new "raw" select expression to the query. + * + * @param string $expression + * @param array $bindings + * @return \Illuminate\Database\Query\Builder|static + */ + public function selectRaw($expression, array $bindings = []) + { + $this->addSelect(new Expression($expression)); + + if ($bindings) { + $this->addBinding($bindings, 'select'); + } + + return $this; + } + + /** + * Add a subselect expression to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|string $query + * @param string $as + * @return \Illuminate\Database\Query\Builder|static + */ + public function selectSub($query, $as) + { + if ($query instanceof Closure) { + $callback = $query; + + $callback($query = $this->newQuery()); + } + + if ($query instanceof self) { + $bindings = $query->getBindings(); + + $query = $query->toSql(); + } elseif (is_string($query)) { + $bindings = []; + } else { + throw new InvalidArgumentException; + } + + return $this->selectRaw('('.$query.') as '.$this->grammar->wrap($as), $bindings); + } + + /** + * Add a new select column to the query. + * + * @param array|mixed $column + * @return $this + */ + public function addSelect($column) + { + $column = is_array($column) ? $column : func_get_args(); + + $this->columns = array_merge((array) $this->columns, $column); + + return $this; + } + + /** + * Force the query to only return distinct results. + * + * @return $this + */ + public function distinct() + { + $this->distinct = true; + + return $this; + } + + /** + * Set the table which the query is targeting. + * + * @param string $table + * @return $this + */ + public function from($table) + { + $this->from = $table; + + return $this; + } + + /** + * Add a join clause to the query. + * + * @param string $table + * @param string $one + * @param string $operator + * @param string $two + * @param string $type + * @param bool $where + * @return $this + */ + public function join($table, $one, $operator = null, $two = null, $type = 'inner', $where = false) + { + // If the first "column" of the join is really a Closure instance the developer + // is trying to build a join with a complex "on" clause containing more than + // one condition, so we'll add the join and call a Closure with the query. + if ($one instanceof Closure) { + $join = new JoinClause($type, $table); + + call_user_func($one, $join); + + $this->joins[] = $join; + + $this->addBinding($join->bindings, 'join'); + } + + // If the column is simply a string, we can assume the join simply has a basic + // "on" clause with a single condition. So we will just build the join with + // this simple join clauses attached to it. There is not a join callback. + else { + $join = new JoinClause($type, $table); + + $this->joins[] = $join->on( + $one, $operator, $two, 'and', $where + ); + + $this->addBinding($join->bindings, 'join'); + } + + return $this; + } + + /** + * Add a "join where" clause to the query. + * + * @param string $table + * @param string $one + * @param string $operator + * @param string $two + * @param string $type + * @return \Illuminate\Database\Query\Builder|static + */ + public function joinWhere($table, $one, $operator, $two, $type = 'inner') + { + return $this->join($table, $one, $operator, $two, $type, true); + } + + /** + * Add a left join to the query. + * + * @param string $table + * @param string $first + * @param string $operator + * @param string $second + * @return \Illuminate\Database\Query\Builder|static + */ + public function leftJoin($table, $first, $operator = null, $second = null) + { + return $this->join($table, $first, $operator, $second, 'left'); + } + + /** + * Add a "join where" clause to the query. + * + * @param string $table + * @param string $one + * @param string $operator + * @param string $two + * @return \Illuminate\Database\Query\Builder|static + */ + public function leftJoinWhere($table, $one, $operator, $two) + { + return $this->joinWhere($table, $one, $operator, $two, 'left'); + } + + /** + * Add a right join to the query. + * + * @param string $table + * @param string $first + * @param string $operator + * @param string $second + * @return \Illuminate\Database\Query\Builder|static + */ + public function rightJoin($table, $first, $operator = null, $second = null) + { + return $this->join($table, $first, $operator, $second, 'right'); + } + + /** + * Add a "right join where" clause to the query. + * + * @param string $table + * @param string $one + * @param string $operator + * @param string $two + * @return \Illuminate\Database\Query\Builder|static + */ + public function rightJoinWhere($table, $one, $operator, $two) + { + return $this->joinWhere($table, $one, $operator, $two, 'right'); + } + + /** + * Add a basic where clause to the query. + * + * @param string|array|\Closure $column + * @param string $operator + * @param mixed $value + * @param string $boolean + * @return $this + * + * @throws \InvalidArgumentException + */ + public function where($column, $operator = null, $value = null, $boolean = 'and') + { + // If the column is an array, we will assume it is an array of key-value pairs + // and can add them each as a where clause. We will maintain the boolean we + // received when the method was called and pass it into the nested where. + if (is_array($column)) { + return $this->whereNested(function ($query) use ($column) { + foreach ($column as $key => $value) { + $query->where($key, '=', $value); + } + }, $boolean); + } + + // Here we will make some assumptions about the operator. If only 2 values are + // passed to the method, we will assume that the operator is an equals sign + // and keep going. Otherwise, we'll require the operator to be passed in. + if (func_num_args() == 2) { + list($value, $operator) = [$operator, '=']; + } elseif ($this->invalidOperatorAndValue($operator, $value)) { + throw new InvalidArgumentException('Illegal operator and value combination.'); + } + + // If the columns is actually a Closure instance, we will assume the developer + // wants to begin a nested where statement which is wrapped in parenthesis. + // We'll add that Closure to the query then return back out immediately. + if ($column instanceof Closure) { + return $this->whereNested($column, $boolean); + } + + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if (! in_array(strtolower($operator), $this->operators, true)) { + list($value, $operator) = [$operator, '=']; + } + + // If the value is a Closure, it means the developer is performing an entire + // sub-select within the query and we will need to compile the sub-select + // within the where clause to get the appropriate query record results. + if ($value instanceof Closure) { + return $this->whereSub($column, $operator, $value, $boolean); + } + + // If the value is "null", we will just assume the developer wants to add a + // where null clause to the query. So, we will allow a short-cut here to + // that method for convenience so the developer doesn't have to check. + if (is_null($value)) { + return $this->whereNull($column, $boolean, $operator != '='); + } + + // Now that we are working with just a simple query we can put the elements + // in our array and add the query binding to our array of bindings that + // will be bound to each SQL statements when it is finally executed. + $type = 'Basic'; + + $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean'); + + if (! $value instanceof Expression) { + $this->addBinding($value, 'where'); + } + + return $this; + } + + /** + * Add an "or where" clause to the query. + * + * @param string $column + * @param string $operator + * @param mixed $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhere($column, $operator = null, $value = null) + { + return $this->where($column, $operator, $value, 'or'); + } + + /** + * Determine if the given operator and value combination is legal. + * + * @param string $operator + * @param mixed $value + * @return bool + */ + protected function invalidOperatorAndValue($operator, $value) + { + $isOperator = in_array($operator, $this->operators); + + return $isOperator && $operator != '=' && is_null($value); + } + + /** + * Add a raw where clause to the query. + * + * @param string $sql + * @param array $bindings + * @param string $boolean + * @return $this + */ + public function whereRaw($sql, array $bindings = [], $boolean = 'and') + { + $type = 'raw'; + + $this->wheres[] = compact('type', 'sql', 'boolean'); + + $this->addBinding($bindings, 'where'); + + return $this; + } + + /** + * Add a raw or where clause to the query. + * + * @param string $sql + * @param array $bindings + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereRaw($sql, array $bindings = []) + { + return $this->whereRaw($sql, $bindings, 'or'); + } + + /** + * Add a where between statement to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereBetween($column, array $values, $boolean = 'and', $not = false) + { + $type = 'between'; + + $this->wheres[] = compact('column', 'type', 'boolean', 'not'); + + $this->addBinding($values, 'where'); + + return $this; + } + + /** + * Add an or where between statement to the query. + * + * @param string $column + * @param array $values + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereBetween($column, array $values) + { + return $this->whereBetween($column, $values, 'or'); + } + + /** + * Add a where not between statement to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNotBetween($column, array $values, $boolean = 'and') + { + return $this->whereBetween($column, $values, $boolean, true); + } + + /** + * Add an or where not between statement to the query. + * + * @param string $column + * @param array $values + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNotBetween($column, array $values) + { + return $this->whereNotBetween($column, $values, 'or'); + } + + /** + * Add a nested where statement to the query. + * + * @param \Closure $callback + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNested(Closure $callback, $boolean = 'and') + { + // To handle nested queries we'll actually create a brand new query instance + // and pass it off to the Closure that we have. The Closure can simply do + // do whatever it wants to a query then we will store it for compiling. + $query = $this->newQuery(); + + $query->from($this->from); + + call_user_func($callback, $query); + + return $this->addNestedWhereQuery($query, $boolean); + } + + /** + * Add another query builder as a nested where to the query builder. + * + * @param \Illuminate\Database\Query\Builder|static $query + * @param string $boolean + * @return $this + */ + public function addNestedWhereQuery($query, $boolean = 'and') + { + if (count($query->wheres)) { + $type = 'Nested'; + + $this->wheres[] = compact('type', 'query', 'boolean'); + + $this->addBinding($query->getBindings(), 'where'); + } + + return $this; + } + + /** + * Add a full sub-select to the query. + * + * @param string $column + * @param string $operator + * @param \Closure $callback + * @param string $boolean + * @return $this + */ + protected function whereSub($column, $operator, Closure $callback, $boolean) + { + $type = 'Sub'; + + $query = $this->newQuery(); + + // Once we have the query instance we can simply execute it so it can add all + // of the sub-select's conditions to itself, and then we can cache it off + // in the array of where clauses for the "main" parent query instance. + call_user_func($callback, $query); + + $this->wheres[] = compact('type', 'column', 'operator', 'query', 'boolean'); + + $this->addBinding($query->getBindings(), 'where'); + + return $this; + } + + /** + * Add an exists clause to the query. + * + * @param \Closure $callback + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereExists(Closure $callback, $boolean = 'and', $not = false) + { + $type = $not ? 'NotExists' : 'Exists'; + + $query = $this->newQuery(); + + // Similar to the sub-select clause, we will create a new query instance so + // the developer may cleanly specify the entire exists query and we will + // compile the whole thing in the grammar and insert it into the SQL. + call_user_func($callback, $query); + + $this->wheres[] = compact('type', 'operator', 'query', 'boolean'); + + $this->addBinding($query->getBindings(), 'where'); + + return $this; + } + + /** + * Add an or exists clause to the query. + * + * @param \Closure $callback + * @param bool $not + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereExists(Closure $callback, $not = false) + { + return $this->whereExists($callback, 'or', $not); + } + + /** + * Add a where not exists clause to the query. + * + * @param \Closure $callback + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNotExists(Closure $callback, $boolean = 'and') + { + return $this->whereExists($callback, $boolean, true); + } + + /** + * Add a where not exists clause to the query. + * + * @param \Closure $callback + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNotExists(Closure $callback) + { + return $this->orWhereExists($callback, true); + } + + /** + * Add a "where in" clause to the query. + * + * @param string $column + * @param mixed $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereIn($column, $values, $boolean = 'and', $not = false) + { + $type = $not ? 'NotIn' : 'In'; + + if ($values instanceof static) { + return $this->whereInExistingQuery( + $column, $values, $boolean, $not + ); + } + + // If the value of the where in clause is actually a Closure, we will assume that + // the developer is using a full sub-select for this "in" statement, and will + // execute those Closures, then we can re-construct the entire sub-selects. + if ($values instanceof Closure) { + return $this->whereInSub($column, $values, $boolean, $not); + } + + if ($values instanceof Arrayable) { + $values = $values->toArray(); + } + + $this->wheres[] = compact('type', 'column', 'values', 'boolean'); + + $this->addBinding($values, 'where'); + + return $this; + } + + /** + * Add an "or where in" clause to the query. + * + * @param string $column + * @param mixed $values + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereIn($column, $values) + { + return $this->whereIn($column, $values, 'or'); + } + + /** + * Add a "where not in" clause to the query. + * + * @param string $column + * @param mixed $values + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNotIn($column, $values, $boolean = 'and') + { + return $this->whereIn($column, $values, $boolean, true); + } + + /** + * Add an "or where not in" clause to the query. + * + * @param string $column + * @param mixed $values + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNotIn($column, $values) + { + return $this->whereNotIn($column, $values, 'or'); + } + + /** + * Add a where in with a sub-select to the query. + * + * @param string $column + * @param \Closure $callback + * @param string $boolean + * @param bool $not + * @return $this + */ + protected function whereInSub($column, Closure $callback, $boolean, $not) + { + $type = $not ? 'NotInSub' : 'InSub'; + + // To create the exists sub-select, we will actually create a query and call the + // provided callback with the query so the developer may set any of the query + // conditions they want for the in clause, then we'll put it in this array. + call_user_func($callback, $query = $this->newQuery()); + + $this->wheres[] = compact('type', 'column', 'query', 'boolean'); + + $this->addBinding($query->getBindings(), 'where'); + + return $this; + } + + /** + * Add a external sub-select to the query. + * + * @param string $column + * @param \Illuminate\Database\Query\Builder|static $query + * @param string $boolean + * @param bool $not + * @return $this + */ + protected function whereInExistingQuery($column, $query, $boolean, $not) + { + $type = $not ? 'NotInSub' : 'InSub'; + + $this->wheres[] = compact('type', 'column', 'query', 'boolean'); + + $this->addBinding($query->getBindings(), 'where'); + + return $this; + } + + /** + * Add a "where null" clause to the query. + * + * @param string $column + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereNull($column, $boolean = 'and', $not = false) + { + $type = $not ? 'NotNull' : 'Null'; + + $this->wheres[] = compact('type', 'column', 'boolean'); + + return $this; + } + + /** + * Add an "or where null" clause to the query. + * + * @param string $column + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNull($column) + { + return $this->whereNull($column, 'or'); + } + + /** + * Add a "where not null" clause to the query. + * + * @param string $column + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNotNull($column, $boolean = 'and') + { + return $this->whereNull($column, $boolean, true); + } + + /** + * Add an "or where not null" clause to the query. + * + * @param string $column + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNotNull($column) + { + return $this->whereNotNull($column, 'or'); + } + + /** + * Add a "where date" statement to the query. + * + * @param string $column + * @param string $operator + * @param int $value + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereDate($column, $operator, $value, $boolean = 'and') + { + return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean); + } + + /** + * Add a "where day" statement to the query. + * + * @param string $column + * @param string $operator + * @param int $value + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereDay($column, $operator, $value, $boolean = 'and') + { + return $this->addDateBasedWhere('Day', $column, $operator, $value, $boolean); + } + + /** + * Add a "where month" statement to the query. + * + * @param string $column + * @param string $operator + * @param int $value + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereMonth($column, $operator, $value, $boolean = 'and') + { + return $this->addDateBasedWhere('Month', $column, $operator, $value, $boolean); + } + + /** + * Add a "where year" statement to the query. + * + * @param string $column + * @param string $operator + * @param int $value + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereYear($column, $operator, $value, $boolean = 'and') + { + return $this->addDateBasedWhere('Year', $column, $operator, $value, $boolean); + } + + /** + * Add a date based (year, month, day) statement to the query. + * + * @param string $type + * @param string $column + * @param string $operator + * @param int $value + * @param string $boolean + * @return $this + */ + protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and') + { + $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value'); + + $this->addBinding($value, 'where'); + + return $this; + } + + /** + * Handles dynamic "where" clauses to the query. + * + * @param string $method + * @param string $parameters + * @return $this + */ + public function dynamicWhere($method, $parameters) + { + $finder = substr($method, 5); + + $segments = preg_split('/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE); + + // The connector variable will determine which connector will be used for the + // query condition. We will change it as we come across new boolean values + // in the dynamic method strings, which could contain a number of these. + $connector = 'and'; + + $index = 0; + + foreach ($segments as $segment) { + // If the segment is not a boolean connector, we can assume it is a column's name + // and we will add it to the query as a new constraint as a where clause, then + // we can keep iterating through the dynamic method string's segments again. + if ($segment != 'And' && $segment != 'Or') { + $this->addDynamic($segment, $connector, $parameters, $index); + + $index++; + } + + // Otherwise, we will store the connector so we know how the next where clause we + // find in the query should be connected to the previous ones, meaning we will + // have the proper boolean connector to connect the next where clause found. + else { + $connector = $segment; + } + } + + return $this; + } + + /** + * Add a single dynamic where clause statement to the query. + * + * @param string $segment + * @param string $connector + * @param array $parameters + * @param int $index + * @return void + */ + protected function addDynamic($segment, $connector, $parameters, $index) + { + // Once we have parsed out the columns and formatted the boolean operators we + // are ready to add it to this query as a where clause just like any other + // clause on the query. Then we'll increment the parameter index values. + $bool = strtolower($connector); + + $this->where(Str::snake($segment), '=', $parameters[$index], $bool); + } + + /** + * Add a "group by" clause to the query. + * + * @param array|string $column,... + * @return $this + */ + public function groupBy() + { + foreach (func_get_args() as $arg) { + $this->groups = array_merge((array) $this->groups, is_array($arg) ? $arg : [$arg]); + } + + return $this; + } + + /** + * Add a "having" clause to the query. + * + * @param string $column + * @param string $operator + * @param string $value + * @param string $boolean + * @return $this + */ + public function having($column, $operator = null, $value = null, $boolean = 'and') + { + $type = 'basic'; + + $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean'); + + if (! $value instanceof Expression) { + $this->addBinding($value, 'having'); + } + + return $this; + } + + /** + * Add a "or having" clause to the query. + * + * @param string $column + * @param string $operator + * @param string $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function orHaving($column, $operator = null, $value = null) + { + return $this->having($column, $operator, $value, 'or'); + } + + /** + * Add a raw having clause to the query. + * + * @param string $sql + * @param array $bindings + * @param string $boolean + * @return $this + */ + public function havingRaw($sql, array $bindings = [], $boolean = 'and') + { + $type = 'raw'; + + $this->havings[] = compact('type', 'sql', 'boolean'); + + $this->addBinding($bindings, 'having'); + + return $this; + } + + /** + * Add a raw or having clause to the query. + * + * @param string $sql + * @param array $bindings + * @return \Illuminate\Database\Query\Builder|static + */ + public function orHavingRaw($sql, array $bindings = []) + { + return $this->havingRaw($sql, $bindings, 'or'); + } + + /** + * Add an "order by" clause to the query. + * + * @param string $column + * @param string $direction + * @return $this + */ + public function orderBy($column, $direction = 'asc') + { + $property = $this->unions ? 'unionOrders' : 'orders'; + $direction = strtolower($direction) == 'asc' ? 'asc' : 'desc'; + + $this->{$property}[] = compact('column', 'direction'); + + return $this; + } + + /** + * Add an "order by" clause for a timestamp to the query. + * + * @param string $column + * @return \Illuminate\Database\Query\Builder|static + */ + public function latest($column = 'created_at') + { + return $this->orderBy($column, 'desc'); + } + + /** + * Add an "order by" clause for a timestamp to the query. + * + * @param string $column + * @return \Illuminate\Database\Query\Builder|static + */ + public function oldest($column = 'created_at') + { + return $this->orderBy($column, 'asc'); + } + + /** + * Add a raw "order by" clause to the query. + * + * @param string $sql + * @param array $bindings + * @return $this + */ + public function orderByRaw($sql, $bindings = []) + { + $property = $this->unions ? 'unionOrders' : 'orders'; + + $type = 'raw'; + + $this->{$property}[] = compact('type', 'sql'); + + $this->addBinding($bindings, 'order'); + + return $this; + } + + /** + * Set the "offset" value of the query. + * + * @param int $value + * @return $this + */ + public function offset($value) + { + $property = $this->unions ? 'unionOffset' : 'offset'; + + $this->$property = max(0, $value); + + return $this; + } + + /** + * Alias to set the "offset" value of the query. + * + * @param int $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function skip($value) + { + return $this->offset($value); + } + + /** + * Set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function limit($value) + { + $property = $this->unions ? 'unionLimit' : 'limit'; + + if ($value >= 0) { + $this->$property = $value; + } + + return $this; + } + + /** + * Alias to set the "limit" value of the query. + * + * @param int $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function take($value) + { + return $this->limit($value); + } + + /** + * Set the limit and offset for a given page. + * + * @param int $page + * @param int $perPage + * @return \Illuminate\Database\Query\Builder|static + */ + public function forPage($page, $perPage = 15) + { + return $this->skip(($page - 1) * $perPage)->take($perPage); + } + + /** + * Add a union statement to the query. + * + * @param \Illuminate\Database\Query\Builder|\Closure $query + * @param bool $all + * @return \Illuminate\Database\Query\Builder|static + */ + public function union($query, $all = false) + { + if ($query instanceof Closure) { + call_user_func($query, $query = $this->newQuery()); + } + + $this->unions[] = compact('query', 'all'); + + $this->addBinding($query->getBindings(), 'union'); + + return $this; + } + + /** + * Add a union all statement to the query. + * + * @param \Illuminate\Database\Query\Builder|\Closure $query + * @return \Illuminate\Database\Query\Builder|static + */ + public function unionAll($query) + { + return $this->union($query, true); + } + + /** + * Lock the selected rows in the table. + * + * @param bool $value + * @return $this + */ + public function lock($value = true) + { + $this->lock = $value; + + if ($this->lock) { + $this->useWritePdo(); + } + + return $this; + } + + /** + * Lock the selected rows in the table for updating. + * + * @return \Illuminate\Database\Query\Builder + */ + public function lockForUpdate() + { + return $this->lock(true); + } + + /** + * Share lock the selected rows in the table. + * + * @return \Illuminate\Database\Query\Builder + */ + public function sharedLock() + { + return $this->lock(false); + } + + /** + * Get the SQL representation of the query. + * + * @return string + */ + public function toSql() + { + return $this->grammar->compileSelect($this); + } + + /** + * Execute a query for a single record by ID. + * + * @param int $id + * @param array $columns + * @return mixed|static + */ + public function find($id, $columns = ['*']) + { + return $this->where('id', '=', $id)->first($columns); + } + + /** + * Get a single column's value from the first result of a query. + * + * @param string $column + * @return mixed + */ + public function value($column) + { + $result = (array) $this->first([$column]); + + return count($result) > 0 ? reset($result) : null; + } + + /** + * Get a single column's value from the first result of a query. + * + * This is an alias for the "value" method. + * + * @param string $column + * @return mixed + * + * @deprecated since version 5.1. + */ + public function pluck($column) + { + return $this->value($column); + } + + /** + * Execute the query and get the first result. + * + * @param array $columns + * @return mixed|static + */ + public function first($columns = ['*']) + { + $results = $this->take(1)->get($columns); + + return count($results) > 0 ? reset($results) : null; + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return array|static[] + */ + public function get($columns = ['*']) + { + if (is_null($this->columns)) { + $this->columns = $columns; + } + + return $this->processor->processSelect($this, $this->runSelect()); + } + + /** + * Execute the query as a fresh "select" statement. + * + * @param array $columns + * @return array|static[] + * + * @deprecated since version 5.1. Use get instead. + */ + public function getFresh($columns = ['*']) + { + return $this->get($columns); + } + + /** + * Run the query as a "select" statement against the connection. + * + * @return array + */ + protected function runSelect() + { + return $this->connection->select($this->toSql(), $this->getBindings(), ! $this->useWritePdo); + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null) + { + $page = $page ?: Paginator::resolveCurrentPage($pageName); + + $total = $this->getCountForPagination($columns); + + $results = $this->forPage($page, $perPage)->get($columns); + + return new LengthAwarePaginator($results, $total, $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } + + /** + * Get a paginator only supporting simple next and previous links. + * + * This is more efficient on larger data-sets, etc. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = 15, $columns = ['*'], $pageName = 'page') + { + $page = Paginator::resolveCurrentPage($pageName); + + $this->skip(($page - 1) * $perPage)->take($perPage + 1); + + return new Paginator($this->get($columns), $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } + + /** + * Get the count of the total records for the paginator. + * + * @param array $columns + * @return int + */ + public function getCountForPagination($columns = ['*']) + { + $this->backupFieldsForCount(); + + $this->aggregate = ['function' => 'count', 'columns' => $this->clearSelectAliases($columns)]; + + $results = $this->get(); + + $this->aggregate = null; + + $this->restoreFieldsForCount(); + + if (isset($this->groups)) { + return count($results); + } + + return isset($results[0]) ? (int) array_change_key_case((array) $results[0])['aggregate'] : 0; + } + + /** + * Backup some fields for the pagination count. + * + * @return void + */ + protected function backupFieldsForCount() + { + foreach (['orders', 'limit', 'offset', 'columns'] as $field) { + $this->backups[$field] = $this->{$field}; + + $this->{$field} = null; + } + + foreach (['order', 'select'] as $key) { + $this->bindingBackups[$key] = $this->bindings[$key]; + + $this->bindings[$key] = []; + } + } + + /** + * Remove the column aliases since they will break count queries. + * + * @param array $columns + * @return array + */ + protected function clearSelectAliases(array $columns) + { + return array_map(function ($column) { + return is_string($column) && ($aliasPosition = strpos(strtolower($column), ' as ')) !== false + ? substr($column, 0, $aliasPosition) : $column; + }, $columns); + } + + /** + * Restore some fields after the pagination count. + * + * @return void + */ + protected function restoreFieldsForCount() + { + foreach (['orders', 'limit', 'offset', 'columns'] as $field) { + $this->{$field} = $this->backups[$field]; + } + + foreach (['order', 'select'] as $key) { + $this->bindings[$key] = $this->bindingBackups[$key]; + } + + $this->backups = []; + $this->bindingBackups = []; + } + + /** + * Chunk the results of the query. + * + * @param int $count + * @param callable $callback + * @return bool + */ + public function chunk($count, callable $callback) + { + $results = $this->forPage($page = 1, $count)->get(); + + while (count($results) > 0) { + // On each chunk result set, we will pass them to the callback and then let the + // developer take care of everything within the callback, which allows us to + // keep the memory low for spinning through large result sets for working. + if (call_user_func($callback, $results) === false) { + return false; + } + + $page++; + + $results = $this->forPage($page, $count)->get(); + } + + return true; + } + + /** + * Get an array with the values of a given column. + * + * @param string $column + * @param string|null $key + * @return array + */ + public function lists($column, $key = null) + { + $results = $this->get(is_null($key) ? [$column] : [$column, $key]); + + // If the columns are qualified with a table or have an alias, we cannot use + // those directly in the "pluck" operations since the results from the DB + // are only keyed by the column itself. We'll strip the table out here. + return Arr::pluck( + $results, + $this->stripTableForPluck($column), + $this->stripTableForPluck($key) + ); + } + + /** + * Strip off the table name or alias from a column identifier. + * + * @param string $column + * @return string|null + */ + protected function stripTableForPluck($column) + { + return is_null($column) ? $column : last(preg_split('~\.| ~', $column)); + } + + /** + * Concatenate values of a given column as a string. + * + * @param string $column + * @param string $glue + * @return string + */ + public function implode($column, $glue = '') + { + return implode($glue, $this->lists($column)); + } + + /** + * Determine if any rows exist for the current query. + * + * @return bool + */ + public function exists() + { + $sql = $this->grammar->compileExists($this); + + $results = $this->connection->select($sql, $this->getBindings(), ! $this->useWritePdo); + + if (isset($results[0])) { + $results = (array) $results[0]; + + return (bool) $results['exists']; + } + + return false; + } + + /** + * Retrieve the "count" result of the query. + * + * @param string $columns + * @return int + */ + public function count($columns = '*') + { + if (! is_array($columns)) { + $columns = [$columns]; + } + + return (int) $this->aggregate(__FUNCTION__, $columns); + } + + /** + * Retrieve the minimum value of a given column. + * + * @param string $column + * @return mixed + */ + public function min($column) + { + return $this->aggregate(__FUNCTION__, [$column]); + } + + /** + * Retrieve the maximum value of a given column. + * + * @param string $column + * @return mixed + */ + public function max($column) + { + return $this->aggregate(__FUNCTION__, [$column]); + } + + /** + * Retrieve the sum of the values of a given column. + * + * @param string $column + * @return mixed + */ + public function sum($column) + { + return $this->aggregate(__FUNCTION__, [$column]); + } + + /** + * Retrieve the average of the values of a given column. + * + * @param string $column + * @return mixed + */ + public function avg($column) + { + return $this->aggregate(__FUNCTION__, [$column]); + } + + /** + * Alias for the "avg" method. + * + * @param string $column + * @return mixed + */ + public function average($column) + { + return $this->avg($column); + } + + /** + * Execute an aggregate function on the database. + * + * @param string $function + * @param array $columns + * @return mixed + */ + public function aggregate($function, $columns = ['*']) + { + $this->aggregate = compact('function', 'columns'); + + $previousColumns = $this->columns; + + // We will also back up the select bindings since the select clause will be + // removed when performing the aggregate function. Once the query is run + // we will add the bindings back onto this query so they can get used. + $previousSelectBindings = $this->bindings['select']; + + $this->bindings['select'] = []; + + $results = $this->get($columns); + + // Once we have executed the query, we will reset the aggregate property so + // that more select queries can be executed against the database without + // the aggregate value getting in the way when the grammar builds it. + $this->aggregate = null; + + $this->columns = $previousColumns; + + $this->bindings['select'] = $previousSelectBindings; + + if (isset($results[0])) { + return array_change_key_case((array) $results[0])['aggregate']; + } + } + + /** + * Execute a numeric aggregate function on the database. + * + * @param string $function + * @param array $columns + * @return float|int + */ + public function numericAggregate($function, $columns = ['*']) + { + $result = $this->aggregate($function, $columns); + + if (! $result) { + return 0; + } + + if (is_int($result) || is_float($result)) { + return $result; + } + + if (strpos((string) $result, '.') === false) { + return (int) $result; + } + + return (float) $result; + } + + /** + * Insert a new record into the database. + * + * @param array $values + * @return bool + */ + public function insert(array $values) + { + if (empty($values)) { + return true; + } + + // Since every insert gets treated like a batch insert, we will make sure the + // bindings are structured in a way that is convenient for building these + // inserts statements by verifying the elements are actually an array. + if (! is_array(reset($values))) { + $values = [$values]; + } + + // Since every insert gets treated like a batch insert, we will make sure the + // bindings are structured in a way that is convenient for building these + // inserts statements by verifying the elements are actually an array. + else { + foreach ($values as $key => $value) { + ksort($value); + $values[$key] = $value; + } + } + + // We'll treat every insert like a batch insert so we can easily insert each + // of the records into the database consistently. This will make it much + // easier on the grammars to just handle one type of record insertion. + $bindings = []; + + foreach ($values as $record) { + foreach ($record as $value) { + $bindings[] = $value; + } + } + + $sql = $this->grammar->compileInsert($this, $values); + + // Once we have compiled the insert statement's SQL we can execute it on the + // connection and return a result as a boolean success indicator as that + // is the same type of result returned by the raw connection instance. + $bindings = $this->cleanBindings($bindings); + + return $this->connection->insert($sql, $bindings); + } + + /** + * Insert a new record and get the value of the primary key. + * + * @param array $values + * @param string $sequence + * @return int + */ + public function insertGetId(array $values, $sequence = null) + { + $sql = $this->grammar->compileInsertGetId($this, $values, $sequence); + + $values = $this->cleanBindings($values); + + return $this->processor->processInsertGetId($this, $sql, $values, $sequence); + } + + /** + * Update a record in the database. + * + * @param array $values + * @return int + */ + public function update(array $values) + { + $bindings = array_values(array_merge($values, $this->getBindings())); + + $sql = $this->grammar->compileUpdate($this, $values); + + return $this->connection->update($sql, $this->cleanBindings($bindings)); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + public function increment($column, $amount = 1, array $extra = []) + { + if (! is_numeric($amount)) { + throw new InvalidArgumentException('Non-numeric value passed to increment method.'); + } + + $wrapped = $this->grammar->wrap($column); + + $columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra); + + return $this->update($columns); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + public function decrement($column, $amount = 1, array $extra = []) + { + if (! is_numeric($amount)) { + throw new InvalidArgumentException('Non-numeric value passed to decrement method.'); + } + + $wrapped = $this->grammar->wrap($column); + + $columns = array_merge([$column => $this->raw("$wrapped - $amount")], $extra); + + return $this->update($columns); + } + + /** + * Delete a record from the database. + * + * @param mixed $id + * @return int + */ + public function delete($id = null) + { + // If an ID is passed to the method, we will set the where clause to check + // the ID to allow developers to simply and quickly remove a single row + // from their database without manually specifying the where clauses. + if (! is_null($id)) { + $this->where('id', '=', $id); + } + + $sql = $this->grammar->compileDelete($this); + + return $this->connection->delete($sql, $this->getBindings()); + } + + /** + * Run a truncate statement on the table. + * + * @return void + */ + public function truncate() + { + foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) { + $this->connection->statement($sql, $bindings); + } + } + + /** + * Get a new instance of the query builder. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newQuery() + { + return new static($this->connection, $this->grammar, $this->processor); + } + + /** + * Merge an array of where clauses and bindings. + * + * @param array $wheres + * @param array $bindings + * @return void + */ + public function mergeWheres($wheres, $bindings) + { + $this->wheres = array_merge((array) $this->wheres, (array) $wheres); + + $this->bindings['where'] = array_values(array_merge($this->bindings['where'], (array) $bindings)); + } + + /** + * Remove all of the expressions from a list of bindings. + * + * @param array $bindings + * @return array + */ + protected function cleanBindings(array $bindings) + { + return array_values(array_filter($bindings, function ($binding) { + return ! $binding instanceof Expression; + })); + } + + /** + * Create a raw database expression. + * + * @param mixed $value + * @return \Illuminate\Database\Query\Expression + */ + public function raw($value) + { + return $this->connection->raw($value); + } + + /** + * Get the current query value bindings in a flattened array. + * + * @return array + */ + public function getBindings() + { + return Arr::flatten($this->bindings); + } + + /** + * Get the raw array of bindings. + * + * @return array + */ + public function getRawBindings() + { + return $this->bindings; + } + + /** + * Set the bindings on the query builder. + * + * @param array $bindings + * @param string $type + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setBindings(array $bindings, $type = 'where') + { + if (! array_key_exists($type, $this->bindings)) { + throw new InvalidArgumentException("Invalid binding type: {$type}."); + } + + $this->bindings[$type] = $bindings; + + return $this; + } + + /** + * Add a binding to the query. + * + * @param mixed $value + * @param string $type + * @return $this + * + * @throws \InvalidArgumentException + */ + public function addBinding($value, $type = 'where') + { + if (! array_key_exists($type, $this->bindings)) { + throw new InvalidArgumentException("Invalid binding type: {$type}."); + } + + if (is_array($value)) { + $this->bindings[$type] = array_values(array_merge($this->bindings[$type], $value)); + } else { + $this->bindings[$type][] = $value; + } + + return $this; + } + + /** + * Merge an array of bindings into our bindings. + * + * @param \Illuminate\Database\Query\Builder $query + * @return $this + */ + public function mergeBindings(Builder $query) + { + $this->bindings = array_merge_recursive($this->bindings, $query->bindings); + + return $this; + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Get the database query processor instance. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + public function getProcessor() + { + return $this->processor; + } + + /** + * Get the query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + public function getGrammar() + { + return $this->grammar; + } + + /** + * Use the write pdo for query. + * + * @return $this + */ + public function useWritePdo() + { + $this->useWritePdo = true; + + return $this; + } + + /** + * Handle dynamic method calls into the method. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + if (Str::startsWith($method, 'where')) { + return $this->dynamicWhere($method, $parameters); + } + + $className = get_class($this); + + throw new BadMethodCallException("Call to undefined method {$className}::{$method}()"); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Expression.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Expression.php new file mode 100644 index 0000000..de69029 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Expression.php @@ -0,0 +1,44 @@ +value = $value; + } + + /** + * Get the value of the expression. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Get the value of the expression. + * + * @return string + */ + public function __toString() + { + return (string) $this->getValue(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php new file mode 100644 index 0000000..5a871ad --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -0,0 +1,825 @@ +columns)) { + $query->columns = ['*']; + } + + return trim($this->concatenate($this->compileComponents($query))); + } + + /** + * Compile the components necessary for a select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + protected function compileComponents(Builder $query) + { + $sql = []; + + foreach ($this->selectComponents as $component) { + // To compile the query, we'll spin through each component of the query and + // see if that component exists. If it does we'll just call the compiler + // function for the component which is responsible for making the SQL. + if (! is_null($query->$component)) { + $method = 'compile'.ucfirst($component); + + $sql[$component] = $this->$method($query, $query->$component); + } + } + + return $sql; + } + + /** + * Compile an aggregated select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $aggregate + * @return string + */ + protected function compileAggregate(Builder $query, $aggregate) + { + $column = $this->columnize($aggregate['columns']); + + // If the query has a "distinct" constraint and we're not asking for all columns + // we need to prepend "distinct" onto the column name so that the query takes + // it into account when it performs the aggregating operations on the data. + if ($query->distinct && $column !== '*') { + $column = 'distinct '.$column; + } + + return 'select '.$aggregate['function'].'('.$column.') as aggregate'; + } + + /** + * Compile the "select *" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @return string|null + */ + protected function compileColumns(Builder $query, $columns) + { + // If the query is actually performing an aggregating select, we will let that + // compiler handle the building of the select clauses, as it will need some + // more syntax that is best handled by that function to keep things neat. + if (! is_null($query->aggregate)) { + return; + } + + $select = $query->distinct ? 'select distinct ' : 'select '; + + return $select.$this->columnize($columns); + } + + /** + * Compile the "from" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @return string + */ + protected function compileFrom(Builder $query, $table) + { + return 'from '.$this->wrapTable($table); + } + + /** + * Compile the "join" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $joins + * @return string + */ + protected function compileJoins(Builder $query, $joins) + { + $sql = []; + + foreach ($joins as $join) { + $table = $this->wrapTable($join->table); + + // First we need to build all of the "on" clauses for the join. There may be many + // of these clauses so we will need to iterate through each one and build them + // separately, then we'll join them up into a single string when we're done. + $clauses = []; + + foreach ($join->clauses as $clause) { + $clauses[] = $this->compileJoinConstraint($clause); + } + + // Once we have constructed the clauses, we'll need to take the boolean connector + // off of the first clause as it obviously will not be required on that clause + // because it leads the rest of the clauses, thus not requiring any boolean. + $clauses[0] = $this->removeLeadingBoolean($clauses[0]); + + $clauses = implode(' ', $clauses); + + $type = $join->type; + + // Once we have everything ready to go, we will just concatenate all the parts to + // build the final join statement SQL for the query and we can then return the + // final clause back to the callers as a single, stringified join statement. + $sql[] = "$type join $table on $clauses"; + } + + return implode(' ', $sql); + } + + /** + * Create a join clause constraint segment. + * + * @param array $clause + * @return string + */ + protected function compileJoinConstraint(array $clause) + { + if ($clause['nested']) { + return $this->compileNestedJoinConstraint($clause); + } + + $first = $this->wrap($clause['first']); + + if ($clause['where']) { + if ($clause['operator'] === 'in' || $clause['operator'] === 'not in') { + $second = '('.implode(', ', array_fill(0, $clause['second'], '?')).')'; + } else { + $second = '?'; + } + } else { + $second = $this->wrap($clause['second']); + } + + return "{$clause['boolean']} $first {$clause['operator']} $second"; + } + + /** + * Create a nested join clause constraint segment. + * + * @param array $clause + * @return string + */ + protected function compileNestedJoinConstraint(array $clause) + { + $clauses = []; + + foreach ($clause['join']->clauses as $nestedClause) { + $clauses[] = $this->compileJoinConstraint($nestedClause); + } + + $clauses[0] = $this->removeLeadingBoolean($clauses[0]); + + $clauses = implode(' ', $clauses); + + return "{$clause['boolean']} ({$clauses})"; + } + + /** + * Compile the "where" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileWheres(Builder $query) + { + $sql = []; + + if (is_null($query->wheres)) { + return ''; + } + + // Each type of where clauses has its own compiler function which is responsible + // for actually creating the where clauses SQL. This helps keep the code nice + // and maintainable since each clause has a very small method that it uses. + foreach ($query->wheres as $where) { + $method = "where{$where['type']}"; + + $sql[] = $where['boolean'].' '.$this->$method($query, $where); + } + + // If we actually have some where clauses, we will strip off the first boolean + // operator, which is added by the query builders for convenience so we can + // avoid checking for the first clauses in each of the compilers methods. + if (count($sql) > 0) { + $sql = implode(' ', $sql); + + return 'where '.$this->removeLeadingBoolean($sql); + } + + return ''; + } + + /** + * Compile a nested where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNested(Builder $query, $where) + { + $nested = $where['query']; + + return '('.substr($this->compileWheres($nested), 6).')'; + } + + /** + * Compile a where condition with a sub-select. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereSub(Builder $query, $where) + { + $select = $this->compileSelect($where['query']); + + return $this->wrap($where['column']).' '.$where['operator']." ($select)"; + } + + /** + * Compile a basic where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBasic(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return $this->wrap($where['column']).' '.$where['operator'].' '.$value; + } + + /** + * Compile a "between" where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBetween(Builder $query, $where) + { + $between = $where['not'] ? 'not between' : 'between'; + + return $this->wrap($where['column']).' '.$between.' ? and ?'; + } + + /** + * Compile a where exists clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereExists(Builder $query, $where) + { + return 'exists ('.$this->compileSelect($where['query']).')'; + } + + /** + * Compile a where exists clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotExists(Builder $query, $where) + { + return 'not exists ('.$this->compileSelect($where['query']).')'; + } + + /** + * Compile a "where in" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereIn(Builder $query, $where) + { + if (empty($where['values'])) { + return '0 = 1'; + } + + $values = $this->parameterize($where['values']); + + return $this->wrap($where['column']).' in ('.$values.')'; + } + + /** + * Compile a "where not in" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotIn(Builder $query, $where) + { + if (empty($where['values'])) { + return '1 = 1'; + } + + $values = $this->parameterize($where['values']); + + return $this->wrap($where['column']).' not in ('.$values.')'; + } + + /** + * Compile a where in sub-select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereInSub(Builder $query, $where) + { + $select = $this->compileSelect($where['query']); + + return $this->wrap($where['column']).' in ('.$select.')'; + } + + /** + * Compile a where not in sub-select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotInSub(Builder $query, $where) + { + $select = $this->compileSelect($where['query']); + + return $this->wrap($where['column']).' not in ('.$select.')'; + } + + /** + * Compile a "where null" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNull(Builder $query, $where) + { + return $this->wrap($where['column']).' is null'; + } + + /** + * Compile a "where not null" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotNull(Builder $query, $where) + { + return $this->wrap($where['column']).' is not null'; + } + + /** + * Compile a "where date" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDate(Builder $query, $where) + { + return $this->dateBasedWhere('date', $query, $where); + } + + /** + * Compile a "where day" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDay(Builder $query, $where) + { + return $this->dateBasedWhere('day', $query, $where); + } + + /** + * Compile a "where month" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereMonth(Builder $query, $where) + { + return $this->dateBasedWhere('month', $query, $where); + } + + /** + * Compile a "where year" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereYear(Builder $query, $where) + { + return $this->dateBasedWhere('year', $query, $where); + } + + /** + * Compile a date based where clause. + * + * @param string $type + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function dateBasedWhere($type, Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return $type.'('.$this->wrap($where['column']).') '.$where['operator'].' '.$value; + } + + /** + * Compile a raw where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereRaw(Builder $query, $where) + { + return $where['sql']; + } + + /** + * Compile the "group by" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $groups + * @return string + */ + protected function compileGroups(Builder $query, $groups) + { + return 'group by '.$this->columnize($groups); + } + + /** + * Compile the "having" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $havings + * @return string + */ + protected function compileHavings(Builder $query, $havings) + { + $sql = implode(' ', array_map([$this, 'compileHaving'], $havings)); + + return 'having '.$this->removeLeadingBoolean($sql); + } + + /** + * Compile a single having clause. + * + * @param array $having + * @return string + */ + protected function compileHaving(array $having) + { + // If the having clause is "raw", we can just return the clause straight away + // without doing any more processing on it. Otherwise, we will compile the + // clause into SQL based on the components that make it up from builder. + if ($having['type'] === 'raw') { + return $having['boolean'].' '.$having['sql']; + } + + return $this->compileBasicHaving($having); + } + + /** + * Compile a basic having clause. + * + * @param array $having + * @return string + */ + protected function compileBasicHaving($having) + { + $column = $this->wrap($having['column']); + + $parameter = $this->parameter($having['value']); + + return $having['boolean'].' '.$column.' '.$having['operator'].' '.$parameter; + } + + /** + * Compile the "order by" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $orders + * @return string + */ + protected function compileOrders(Builder $query, $orders) + { + return 'order by '.implode(', ', array_map(function ($order) { + if (isset($order['sql'])) { + return $order['sql']; + } + + return $this->wrap($order['column']).' '.$order['direction']; + }, $orders)); + } + + /** + * Compile the "limit" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $limit + * @return string + */ + protected function compileLimit(Builder $query, $limit) + { + return 'limit '.(int) $limit; + } + + /** + * Compile the "offset" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $offset + * @return string + */ + protected function compileOffset(Builder $query, $offset) + { + return 'offset '.(int) $offset; + } + + /** + * Compile the "union" queries attached to the main query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUnions(Builder $query) + { + $sql = ''; + + foreach ($query->unions as $union) { + $sql .= $this->compileUnion($union); + } + + if (isset($query->unionOrders)) { + $sql .= ' '.$this->compileOrders($query, $query->unionOrders); + } + + if (isset($query->unionLimit)) { + $sql .= ' '.$this->compileLimit($query, $query->unionLimit); + } + + if (isset($query->unionOffset)) { + $sql .= ' '.$this->compileOffset($query, $query->unionOffset); + } + + return ltrim($sql); + } + + /** + * Compile a single union statement. + * + * @param array $union + * @return string + */ + protected function compileUnion(array $union) + { + $joiner = $union['all'] ? ' union all ' : ' union '; + + return $joiner.$union['query']->toSql(); + } + + /** + * Compile an exists statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileExists(Builder $query) + { + $select = $this->compileSelect($query); + + return "select exists($select) as {$this->wrap('exists')}"; + } + + /** + * Compile an insert statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsert(Builder $query, array $values) + { + // Essentially we will force every insert to be treated as a batch insert which + // simply makes creating the SQL easier for us since we can utilize the same + // basic routine regardless of an amount of records given to us to insert. + $table = $this->wrapTable($query->from); + + if (! is_array(reset($values))) { + $values = [$values]; + } + + $columns = $this->columnize(array_keys(reset($values))); + + // We need to build a list of parameter place-holders of values that are bound + // to the query. Each insert should have the exact same amount of parameter + // bindings so we will loop through the record and parameterize them all. + $parameters = []; + + foreach ($values as $record) { + $parameters[] = '('.$this->parameterize($record).')'; + } + + $parameters = implode(', ', $parameters); + + return "insert into $table ($columns) values $parameters"; + } + + /** + * Compile an insert and get ID statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param string $sequence + * @return string + */ + public function compileInsertGetId(Builder $query, $values, $sequence) + { + return $this->compileInsert($query, $values); + } + + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, $values) + { + $table = $this->wrapTable($query->from); + + // Each one of the columns in the update statements needs to be wrapped in the + // keyword identifiers, also a place-holder needs to be created for each of + // the values in the list of bindings so we can make the sets statements. + $columns = []; + + foreach ($values as $key => $value) { + $columns[] = $this->wrap($key).' = '.$this->parameter($value); + } + + $columns = implode(', ', $columns); + + // If the query has any "join" clauses, we will setup the joins on the builder + // and compile them so we can attach them to this update, as update queries + // can get join statements to attach to other tables when they're needed. + if (isset($query->joins)) { + $joins = ' '.$this->compileJoins($query, $query->joins); + } else { + $joins = ''; + } + + // Of course, update queries may also be constrained by where clauses so we'll + // need to compile the where clauses and attach it to the query so only the + // intended records are updated by the SQL statements we generate to run. + $where = $this->compileWheres($query); + + return trim("update {$table}{$joins} set $columns $where"); + } + + /** + * Compile a delete statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileDelete(Builder $query) + { + $table = $this->wrapTable($query->from); + + $where = is_array($query->wheres) ? $this->compileWheres($query) : ''; + + return trim("delete from $table ".$where); + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return ['truncate '.$this->wrapTable($query->from) => []]; + } + + /** + * Compile the lock into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param bool|string $value + * @return string + */ + protected function compileLock(Builder $query, $value) + { + return is_string($value) ? $value : ''; + } + + /** + * Determine if the grammar supports savepoints. + * + * @return bool + */ + public function supportsSavepoints() + { + return true; + } + + /** + * Compile the SQL statement to define a savepoint. + * + * @param string $name + * @return string + */ + public function compileSavepoint($name) + { + return 'SAVEPOINT '.$name; + } + + /** + * Compile the SQL statement to execute a savepoint rollback. + * + * @param string $name + * @return string + */ + public function compileSavepointRollBack($name) + { + return 'ROLLBACK TO SAVEPOINT '.$name; + } + + /** + * Concatenate an array of segments, removing empties. + * + * @param array $segments + * @return string + */ + protected function concatenate($segments) + { + return implode(' ', array_filter($segments, function ($value) { + return (string) $value !== ''; + })); + } + + /** + * Remove the leading boolean from a statement. + * + * @param string $value + * @return string + */ + protected function removeLeadingBoolean($value) + { + return preg_replace('/and |or /i', '', $value, 1); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php new file mode 100644 index 0000000..e48221c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php @@ -0,0 +1,141 @@ +unions) { + $sql = '('.$sql.') '.$this->compileUnions($query); + } + + return $sql; + } + + /** + * Compile a single union statement. + * + * @param array $union + * @return string + */ + protected function compileUnion(array $union) + { + $joiner = $union['all'] ? ' union all ' : ' union '; + + return $joiner.'('.$union['query']->toSql().')'; + } + + /** + * Compile the lock into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param bool|string $value + * @return string + */ + protected function compileLock(Builder $query, $value) + { + if (is_string($value)) { + return $value; + } + + return $value ? 'for update' : 'lock in share mode'; + } + + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, $values) + { + $sql = parent::compileUpdate($query, $values); + + if (isset($query->orders)) { + $sql .= ' '.$this->compileOrders($query, $query->orders); + } + + if (isset($query->limit)) { + $sql .= ' '.$this->compileLimit($query, $query->limit); + } + + return rtrim($sql); + } + + /** + * Compile a delete statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileDelete(Builder $query) + { + $table = $this->wrapTable($query->from); + + $where = is_array($query->wheres) ? $this->compileWheres($query) : ''; + + if (isset($query->joins)) { + $joins = ' '.$this->compileJoins($query, $query->joins); + + $sql = trim("delete $table from {$table}{$joins} $where"); + } else { + $sql = trim("delete from $table $where"); + + if (isset($query->orders)) { + $sql .= ' '.$this->compileOrders($query, $query->orders); + } + + if (isset($query->limit)) { + $sql .= ' '.$this->compileLimit($query, $query->limit); + } + } + + return $sql; + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + if ($value === '*') { + return $value; + } + + return '`'.str_replace('`', '``', $value).'`'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php new file mode 100644 index 0000000..5582649 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php @@ -0,0 +1,245 @@ +', '<=', '>=', '<>', '!=', + 'like', 'not like', 'between', 'ilike', + '&', '|', '#', '<<', '>>', + ]; + + /** + * Compile the lock into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param bool|string $value + * @return string + */ + protected function compileLock(Builder $query, $value) + { + if (is_string($value)) { + return $value; + } + + return $value ? 'for update' : 'for share'; + } + + /** + * Compile a "where date" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDate(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return $this->wrap($where['column']).' '.$where['operator'].' '.$value.'::date'; + } + + /** + * Compile a "where day" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDay(Builder $query, $where) + { + return $this->dateBasedWhere('day', $query, $where); + } + + /** + * Compile a "where month" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereMonth(Builder $query, $where) + { + return $this->dateBasedWhere('month', $query, $where); + } + + /** + * Compile a "where year" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereYear(Builder $query, $where) + { + return $this->dateBasedWhere('year', $query, $where); + } + + /** + * Compile a date based where clause. + * + * @param string $type + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function dateBasedWhere($type, Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return 'extract('.$type.' from '.$this->wrap($where['column']).') '.$where['operator'].' '.$value; + } + + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, $values) + { + $table = $this->wrapTable($query->from); + + // Each one of the columns in the update statements needs to be wrapped in the + // keyword identifiers, also a place-holder needs to be created for each of + // the values in the list of bindings so we can make the sets statements. + $columns = $this->compileUpdateColumns($values); + + $from = $this->compileUpdateFrom($query); + + $where = $this->compileUpdateWheres($query); + + return trim("update {$table} set {$columns}{$from} $where"); + } + + /** + * Compile the columns for the update statement. + * + * @param array $values + * @return string + */ + protected function compileUpdateColumns($values) + { + $columns = []; + + // When gathering the columns for an update statement, we'll wrap each of the + // columns and convert it to a parameter value. Then we will concatenate a + // list of the columns that can be added into this update query clauses. + foreach ($values as $key => $value) { + $columns[] = $this->wrap($key).' = '.$this->parameter($value); + } + + return implode(', ', $columns); + } + + /** + * Compile the "from" clause for an update with a join. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string|null + */ + protected function compileUpdateFrom(Builder $query) + { + if (! isset($query->joins)) { + return ''; + } + + $froms = []; + + // When using Postgres, updates with joins list the joined tables in the from + // clause, which is different than other systems like MySQL. Here, we will + // compile out the tables that are joined and add them to a from clause. + foreach ($query->joins as $join) { + $froms[] = $this->wrapTable($join->table); + } + + if (count($froms) > 0) { + return ' from '.implode(', ', $froms); + } + } + + /** + * Compile the additional where clauses for updates with joins. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUpdateWheres(Builder $query) + { + $baseWhere = $this->compileWheres($query); + + if (! isset($query->joins)) { + return $baseWhere; + } + + // Once we compile the join constraints, we will either use them as the where + // clause or append them to the existing base where clauses. If we need to + // strip the leading boolean we will do so when using as the only where. + $joinWhere = $this->compileUpdateJoinWheres($query); + + if (trim($baseWhere) == '') { + return 'where '.$this->removeLeadingBoolean($joinWhere); + } + + return $baseWhere.' '.$joinWhere; + } + + /** + * Compile the "join" clauses for an update. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUpdateJoinWheres(Builder $query) + { + $joinWheres = []; + + // Here we will just loop through all of the join constraints and compile them + // all out then implode them. This should give us "where" like syntax after + // everything has been built and then we will join it to the real wheres. + foreach ($query->joins as $join) { + foreach ($join->clauses as $clause) { + $joinWheres[] = $this->compileJoinConstraint($clause); + } + } + + return implode(' ', $joinWheres); + } + + /** + * Compile an insert and get ID statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param string $sequence + * @return string + */ + public function compileInsertGetId(Builder $query, $values, $sequence) + { + if (is_null($sequence)) { + $sequence = 'id'; + } + + return $this->compileInsert($query, $values).' returning '.$this->wrap($sequence); + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return ['truncate '.$this->wrapTable($query->from).' restart identity' => []]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php new file mode 100644 index 0000000..dd1f9c0 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -0,0 +1,140 @@ +', '<=', '>=', '<>', '!=', + 'like', 'not like', 'between', 'ilike', + '&', '|', '<<', '>>', + ]; + + /** + * Compile an insert statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsert(Builder $query, array $values) + { + // Essentially we will force every insert to be treated as a batch insert which + // simply makes creating the SQL easier for us since we can utilize the same + // basic routine regardless of an amount of records given to us to insert. + $table = $this->wrapTable($query->from); + + if (! is_array(reset($values))) { + $values = [$values]; + } + + // If there is only one record being inserted, we will just use the usual query + // grammar insert builder because no special syntax is needed for the single + // row inserts in SQLite. However, if there are multiples, we'll continue. + if (count($values) == 1) { + return parent::compileInsert($query, reset($values)); + } + + $names = $this->columnize(array_keys(reset($values))); + + $columns = []; + + // SQLite requires us to build the multi-row insert as a listing of select with + // unions joining them together. So we'll build out this list of columns and + // then join them all together with select unions to complete the queries. + foreach (array_keys(reset($values)) as $column) { + $columns[] = '? as '.$this->wrap($column); + } + + $columns = array_fill(0, count($values), implode(', ', $columns)); + + return "insert into $table ($names) select ".implode(' union all select ', $columns); + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + $sql = ['delete from sqlite_sequence where name = ?' => [$query->from]]; + + $sql['delete from '.$this->wrapTable($query->from)] = []; + + return $sql; + } + + /** + * Compile a "where date" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDate(Builder $query, $where) + { + return $this->dateBasedWhere('%Y-%m-%d', $query, $where); + } + + /** + * Compile a "where day" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDay(Builder $query, $where) + { + return $this->dateBasedWhere('%d', $query, $where); + } + + /** + * Compile a "where month" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereMonth(Builder $query, $where) + { + return $this->dateBasedWhere('%m', $query, $where); + } + + /** + * Compile a "where year" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereYear(Builder $query, $where) + { + return $this->dateBasedWhere('%Y', $query, $where); + } + + /** + * Compile a date based where clause. + * + * @param string $type + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function dateBasedWhere($type, Builder $query, $where) + { + $value = str_pad($where['value'], 2, '0', STR_PAD_LEFT); + + $value = $this->parameter($value); + + return 'strftime(\''.$type.'\', '.$this->wrap($where['column']).') '.$where['operator'].' '.$value; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php new file mode 100644 index 0000000..b36e94c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php @@ -0,0 +1,344 @@ +', '<=', '>=', '!<', '!>', '<>', '!=', + 'like', 'not like', 'between', 'ilike', + '&', '&=', '|', '|=', '^', '^=', + ]; + + /** + * Compile a select query into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileSelect(Builder $query) + { + if (is_null($query->columns)) { + $query->columns = ['*']; + } + + $components = $this->compileComponents($query); + + // If an offset is present on the query, we will need to wrap the query in + // a big "ANSI" offset syntax block. This is very nasty compared to the + // other database systems but is necessary for implementing features. + if ($query->offset > 0) { + return $this->compileAnsiOffset($query, $components); + } + + return $this->concatenate($components); + } + + /** + * Compile the "select *" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @return string|null + */ + protected function compileColumns(Builder $query, $columns) + { + if (! is_null($query->aggregate)) { + return; + } + + $select = $query->distinct ? 'select distinct ' : 'select '; + + // If there is a limit on the query, but not an offset, we will add the top + // clause to the query, which serves as a "limit" type clause within the + // SQL Server system similar to the limit keywords available in MySQL. + if ($query->limit > 0 && $query->offset <= 0) { + $select .= 'top '.$query->limit.' '; + } + + return $select.$this->columnize($columns); + } + + /** + * Compile the "from" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @return string + */ + protected function compileFrom(Builder $query, $table) + { + $from = parent::compileFrom($query, $table); + + if (is_string($query->lock)) { + return $from.' '.$query->lock; + } + + if (! is_null($query->lock)) { + return $from.' with(rowlock,'.($query->lock ? 'updlock,' : '').'holdlock)'; + } + + return $from; + } + + /** + * Create a full ANSI offset clause for the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $components + * @return string + */ + protected function compileAnsiOffset(Builder $query, $components) + { + // An ORDER BY clause is required to make this offset query work, so if one does + // not exist we'll just create a dummy clause to trick the database and so it + // does not complain about the queries for not having an "order by" clause. + if (! isset($components['orders'])) { + $components['orders'] = 'order by (select 0)'; + } + + // We need to add the row number to the query so we can compare it to the offset + // and limit values given for the statements. So we will add an expression to + // the "select" that will give back the row numbers on each of the records. + $orderings = $components['orders']; + + $components['columns'] .= $this->compileOver($orderings); + + unset($components['orders']); + + // Next we need to calculate the constraints that should be placed on the query + // to get the right offset and limit from our query but if there is no limit + // set we will just handle the offset only since that is all that matters. + $constraint = $this->compileRowConstraint($query); + + $sql = $this->concatenate($components); + + // We are now ready to build the final SQL query so we'll create a common table + // expression from the query and get the records with row numbers within our + // given limit and offset value that we just put on as a query constraint. + return $this->compileTableExpression($sql, $constraint); + } + + /** + * Compile the over statement for a table expression. + * + * @param string $orderings + * @return string + */ + protected function compileOver($orderings) + { + return ", row_number() over ({$orderings}) as row_num"; + } + + /** + * Compile the limit / offset row constraint for a query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileRowConstraint($query) + { + $start = $query->offset + 1; + + if ($query->limit > 0) { + $finish = $query->offset + $query->limit; + + return "between {$start} and {$finish}"; + } + + return ">= {$start}"; + } + + /** + * Compile a common table expression for a query. + * + * @param string $sql + * @param string $constraint + * @return string + */ + protected function compileTableExpression($sql, $constraint) + { + return "select * from ({$sql}) as temp_table where row_num {$constraint}"; + } + + /** + * Compile the "limit" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $limit + * @return string + */ + protected function compileLimit(Builder $query, $limit) + { + return ''; + } + + /** + * Compile the "offset" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $offset + * @return string + */ + protected function compileOffset(Builder $query, $offset) + { + return ''; + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return ['truncate table '.$this->wrapTable($query->from) => []]; + } + + /** + * Compile an exists statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileExists(Builder $query) + { + $existsQuery = clone $query; + + $existsQuery->columns = []; + + return $this->compileSelect($existsQuery->selectRaw('1 [exists]')->limit(1)); + } + + /** + * Compile a "where date" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDate(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return 'cast('.$this->wrap($where['column']).' as date) '.$where['operator'].' '.$value; + } + + /** + * Determine if the grammar supports savepoints. + * + * @return bool + */ + public function supportsSavepoints() + { + return false; + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + public function getDateFormat() + { + return 'Y-m-d H:i:s.000'; + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + if ($value === '*') { + return $value; + } + + return '['.str_replace(']', ']]', $value).']'; + } + + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, $values) + { + $table = $alias = $this->wrapTable($query->from); + + if (strpos(strtolower($table), '] as [') !== false) { + $segments = explode('] as [', $table); + + $alias = '['.$segments[1]; + } + + // Each one of the columns in the update statements needs to be wrapped in the + // keyword identifiers, also a place-holder needs to be created for each of + // the values in the list of bindings so we can make the sets statements. + $columns = []; + + foreach ($values as $key => $value) { + $columns[] = $this->wrap($key).' = '.$this->parameter($value); + } + + $columns = implode(', ', $columns); + + // If the query has any "join" clauses, we will setup the joins on the builder + // and compile them so we can attach them to this update, as update queries + // can get join statements to attach to other tables when they're needed. + if (isset($query->joins)) { + $joins = ' '.$this->compileJoins($query, $query->joins); + } else { + $joins = ''; + } + + // Of course, update queries may also be constrained by where clauses so we'll + // need to compile the where clauses and attach it to the query so only the + // intended records are updated by the SQL statements we generate to run. + $where = $this->compileWheres($query); + + if (! empty($joins)) { + return trim("update {$alias} set {$columns} from {$table}{$joins} {$where}"); + } + + return trim("update {$table}{$joins} set $columns $where"); + } + + /** + * Wrap a table in keyword identifiers. + * + * @param \Illuminate\Database\Query\Expression|string $table + * @return string + */ + public function wrapTable($table) + { + return $this->wrapTableValuedFunction(parent::wrapTable($table)); + } + + /** + * Wrap a table in keyword identifiers. + * + * @param string $table + * @return string + */ + protected function wrapTableValuedFunction($table) + { + if (preg_match('/^(.+?)(\(.*?\))]$/', $table, $matches) === 1) { + $table = $matches[1].']'.$matches[2]; + } + + return $table; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/JoinClause.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/JoinClause.php new file mode 100644 index 0000000..29ce036 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/JoinClause.php @@ -0,0 +1,253 @@ +type = $type; + $this->table = $table; + } + + /** + * Add an "on" clause to the join. + * + * On clauses can be chained, e.g. + * + * $join->on('contacts.user_id', '=', 'users.id') + * ->on('contacts.info_id', '=', 'info.id') + * + * will produce the following SQL: + * + * on `contacts`.`user_id` = `users`.`id` and `contacts`.`info_id` = `info`.`id` + * + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @param string $boolean + * @param bool $where + * @return $this + * + * @throws \InvalidArgumentException + */ + public function on($first, $operator = null, $second = null, $boolean = 'and', $where = false) + { + if ($first instanceof Closure) { + return $this->nest($first, $boolean); + } + + if (func_num_args() < 3) { + throw new InvalidArgumentException('Not enough arguments for the on clause.'); + } + + if ($where) { + $this->bindings[] = $second; + } + + if ($where && ($operator === 'in' || $operator === 'not in') && is_array($second)) { + $second = count($second); + } + + $nested = false; + + $this->clauses[] = compact('first', 'operator', 'second', 'boolean', 'where', 'nested'); + + return $this; + } + + /** + * Add an "or on" clause to the join. + * + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @return \Illuminate\Database\Query\JoinClause + */ + public function orOn($first, $operator = null, $second = null) + { + return $this->on($first, $operator, $second, 'or'); + } + + /** + * Add an "on where" clause to the join. + * + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @param string $boolean + * @return \Illuminate\Database\Query\JoinClause + */ + public function where($first, $operator = null, $second = null, $boolean = 'and') + { + return $this->on($first, $operator, $second, $boolean, true); + } + + /** + * Add an "or on where" clause to the join. + * + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @return \Illuminate\Database\Query\JoinClause + */ + public function orWhere($first, $operator = null, $second = null) + { + return $this->on($first, $operator, $second, 'or', true); + } + + /** + * Add an "on where is null" clause to the join. + * + * @param string $column + * @param string $boolean + * @return \Illuminate\Database\Query\JoinClause + */ + public function whereNull($column, $boolean = 'and') + { + return $this->on($column, 'is', new Expression('null'), $boolean, false); + } + + /** + * Add an "or on where is null" clause to the join. + * + * @param string $column + * @return \Illuminate\Database\Query\JoinClause + */ + public function orWhereNull($column) + { + return $this->whereNull($column, 'or'); + } + + /** + * Add an "on where is not null" clause to the join. + * + * @param string $column + * @param string $boolean + * @return \Illuminate\Database\Query\JoinClause + */ + public function whereNotNull($column, $boolean = 'and') + { + return $this->on($column, 'is', new Expression('not null'), $boolean, false); + } + + /** + * Add an "or on where is not null" clause to the join. + * + * @param string $column + * @return \Illuminate\Database\Query\JoinClause + */ + public function orWhereNotNull($column) + { + return $this->whereNotNull($column, 'or'); + } + + /** + * Add an "on where in (...)" clause to the join. + * + * @param string $column + * @param array $values + * @return \Illuminate\Database\Query\JoinClause + */ + public function whereIn($column, array $values) + { + return $this->on($column, 'in', $values, 'and', true); + } + + /** + * Add an "on where not in (...)" clause to the join. + * + * @param string $column + * @param array $values + * @return \Illuminate\Database\Query\JoinClause + */ + public function whereNotIn($column, array $values) + { + return $this->on($column, 'not in', $values, 'and', true); + } + + /** + * Add an "or on where in (...)" clause to the join. + * + * @param string $column + * @param array $values + * @return \Illuminate\Database\Query\JoinClause + */ + public function orWhereIn($column, array $values) + { + return $this->on($column, 'in', $values, 'or', true); + } + + /** + * Add an "or on where not in (...)" clause to the join. + * + * @param string $column + * @param array $values + * @return \Illuminate\Database\Query\JoinClause + */ + public function orWhereNotIn($column, array $values) + { + return $this->on($column, 'not in', $values, 'or', true); + } + + /** + * Add a nested where statement to the query. + * + * @param \Closure $callback + * @param string $boolean + * @return \Illuminate\Database\Query\JoinClause + */ + public function nest(Closure $callback, $boolean = 'and') + { + $join = new static($this->type, $this->table); + + $callback($join); + + if (count($join->clauses)) { + $nested = true; + + $this->clauses[] = compact('nested', 'join', 'boolean'); + $this->bindings = array_merge($this->bindings, $join->bindings); + } + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/MySqlProcessor.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/MySqlProcessor.php new file mode 100644 index 0000000..a8a9a6c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/MySqlProcessor.php @@ -0,0 +1,23 @@ +column_name; + }; + + return array_map($mapping, $results); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/PostgresProcessor.php new file mode 100644 index 0000000..ab350cb --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -0,0 +1,47 @@ +getConnection()->selectFromWriteConnection($sql, $values); + + $sequence = $sequence ?: 'id'; + + $result = (array) $results[0]; + + $id = $result[$sequence]; + + return is_numeric($id) ? (int) $id : $id; + } + + /** + * Process the results of a column listing query. + * + * @param array $results + * @return array + */ + public function processColumnListing($results) + { + $mapping = function ($r) { + $r = (object) $r; + + return $r->column_name; + }; + + return array_map($mapping, $results); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php new file mode 100644 index 0000000..f78429f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php @@ -0,0 +1,49 @@ +getConnection()->insert($sql, $values); + + $id = $query->getConnection()->getPdo()->lastInsertId($sequence); + + return is_numeric($id) ? (int) $id : $id; + } + + /** + * Process the results of a column listing query. + * + * @param array $results + * @return array + */ + public function processColumnListing($results) + { + return $results; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php new file mode 100644 index 0000000..e4a8950 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php @@ -0,0 +1,23 @@ +name; + }; + + return array_map($mapping, $results); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php new file mode 100644 index 0000000..447a9c6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php @@ -0,0 +1,43 @@ +getConnection()->insert($sql, $values); + + $id = $query->getConnection()->getPdo()->lastInsertId(); + + return is_numeric($id) ? (int) $id : $id; + } + + /** + * Process the results of a column listing query. + * + * @param array $results + * @return array + */ + public function processColumnListing($results) + { + $mapping = function ($r) { + $r = (object) $r; + + return $r->name; + }; + + return array_map($mapping, $results); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/QueryException.php b/core/vendor/laravel/framework/src/Illuminate/Database/QueryException.php new file mode 100644 index 0000000..922570b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/QueryException.php @@ -0,0 +1,78 @@ +sql = $sql; + $this->bindings = $bindings; + $this->previous = $previous; + $this->code = $previous->getCode(); + $this->message = $this->formatMessage($sql, $bindings, $previous); + + if ($previous instanceof PDOException) { + $this->errorInfo = $previous->errorInfo; + } + } + + /** + * Format the SQL error message. + * + * @param string $sql + * @param array $bindings + * @param \Exception $previous + * @return string + */ + protected function formatMessage($sql, $bindings, $previous) + { + return $previous->getMessage().' (SQL: '.str_replace_array('\?', $bindings, $sql).')'; + } + + /** + * Get the SQL for the query. + * + * @return string + */ + public function getSql() + { + return $this->sql; + } + + /** + * Get the bindings for the query. + * + * @return array + */ + public function getBindings() + { + return $this->bindings; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/README.md b/core/vendor/laravel/framework/src/Illuminate/Database/README.md new file mode 100644 index 0000000..1675a93 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/README.md @@ -0,0 +1,70 @@ +## Illuminate Database + +The Illuminate Database component is a full database toolkit for PHP, providing an expressive query builder, ActiveRecord style ORM, and schema builder. It currently supports MySQL, Postgres, SQL Server, and SQLite. It also serves as the database layer of the Laravel PHP framework. + +### Usage Instructions + +First, create a new "Capsule" manager instance. Capsule aims to make configuring the library for usage outside of the Laravel framework as easy as possible. + +```PHP +use Illuminate\Database\Capsule\Manager as Capsule; + +$capsule = new Capsule; + +$capsule->addConnection([ + 'driver' => 'mysql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => 'password', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', +]); + +// Set the event dispatcher used by Eloquent models... (optional) +use Illuminate\Events\Dispatcher; +use Illuminate\Container\Container; +$capsule->setEventDispatcher(new Dispatcher(new Container)); + +// Make this Capsule instance available globally via static methods... (optional) +$capsule->setAsGlobal(); + +// Setup the Eloquent ORM... (optional; unless you've used setEventDispatcher()) +$capsule->bootEloquent(); +``` + +> `composer require "illuminate/events"` required when you need to use observers with Eloquent. + +Once the Capsule instance has been registered. You may use it like so: + +**Using The Query Builder** + +```PHP +$users = Capsule::table('users')->where('votes', '>', 100)->get(); +``` +Other core methods may be accessed directly from the Capsule in the same manner as from the DB facade: +```PHP +$results = Capsule::select('select * from users where id = ?', array(1)); +``` + +**Using The Schema Builder** + +```PHP +Capsule::schema()->create('users', function($table) +{ + $table->increments('id'); + $table->string('email')->unique(); + $table->timestamps(); +}); +``` + +**Using The Eloquent ORM** + +```PHP +class User extends Illuminate\Database\Eloquent\Model {} + +$users = User::where('votes', '>', 1)->get(); +``` + +For further documentation on using the various database facilities this library provides, consult the [Laravel framework documentation](http://laravel.com/docs). diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/SQLiteConnection.php b/core/vendor/laravel/framework/src/Illuminate/Database/SQLiteConnection.php new file mode 100644 index 0000000..b32786c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/SQLiteConnection.php @@ -0,0 +1,51 @@ +withTablePrefix(new QueryGrammar); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\SQLiteGrammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\SQLiteProcessor + */ + protected function getDefaultPostProcessor() + { + return new SQLiteProcessor; + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Doctrine\DBAL\Driver\PDOSqlite\Driver + */ + protected function getDoctrineDriver() + { + return new DoctrineDriver; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php new file mode 100644 index 0000000..9786fcb --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php @@ -0,0 +1,1040 @@ +table = $table; + + if (! is_null($callback)) { + $callback($this); + } + } + + /** + * Execute the blueprint against the database. + * + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return void + */ + public function build(Connection $connection, Grammar $grammar) + { + foreach ($this->toSql($connection, $grammar) as $statement) { + $connection->statement($statement); + } + } + + /** + * Get the raw SQL statements for the blueprint. + * + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return array + */ + public function toSql(Connection $connection, Grammar $grammar) + { + $this->addImpliedCommands(); + + $statements = []; + + // Each type of command has a corresponding compiler function on the schema + // grammar which is used to build the necessary SQL statements to build + // the blueprint element, so we'll just call that compilers function. + foreach ($this->commands as $command) { + $method = 'compile'.ucfirst($command->name); + + if (method_exists($grammar, $method)) { + if (! is_null($sql = $grammar->$method($this, $command, $connection))) { + $statements = array_merge($statements, (array) $sql); + } + } + } + + return $statements; + } + + /** + * Add the commands that are implied by the blueprint. + * + * @return void + */ + protected function addImpliedCommands() + { + if (count($this->getAddedColumns()) > 0 && ! $this->creating()) { + array_unshift($this->commands, $this->createCommand('add')); + } + + if (count($this->getChangedColumns()) > 0 && ! $this->creating()) { + array_unshift($this->commands, $this->createCommand('change')); + } + + $this->addFluentIndexes(); + } + + /** + * Add the index commands fluently specified on columns. + * + * @return void + */ + protected function addFluentIndexes() + { + foreach ($this->columns as $column) { + foreach (['primary', 'unique', 'index'] as $index) { + // If the index has been specified on the given column, but is simply + // equal to "true" (boolean), no name has been specified for this + // index, so we will simply call the index methods without one. + if ($column->$index === true) { + $this->$index($column->name); + + continue 2; + } + + // If the index has been specified on the column and it is something + // other than boolean true, we will assume a name was provided on + // the index specification, and pass in the name to the method. + elseif (isset($column->$index)) { + $this->$index($column->name, $column->$index); + + continue 2; + } + } + } + } + + /** + * Determine if the blueprint has a create command. + * + * @return bool + */ + protected function creating() + { + foreach ($this->commands as $command) { + if ($command->name == 'create') { + return true; + } + } + + return false; + } + + /** + * Indicate that the table needs to be created. + * + * @return \Illuminate\Support\Fluent + */ + public function create() + { + return $this->addCommand('create'); + } + + /** + * Indicate that the table needs to be temporary. + * + * @return void + */ + public function temporary() + { + $this->temporary = true; + } + + /** + * Indicate that the table should be dropped. + * + * @return \Illuminate\Support\Fluent + */ + public function drop() + { + return $this->addCommand('drop'); + } + + /** + * Indicate that the table should be dropped if it exists. + * + * @return \Illuminate\Support\Fluent + */ + public function dropIfExists() + { + return $this->addCommand('dropIfExists'); + } + + /** + * Indicate that the given columns should be dropped. + * + * @param array|mixed $columns + * @return \Illuminate\Support\Fluent + */ + public function dropColumn($columns) + { + $columns = is_array($columns) ? $columns : (array) func_get_args(); + + return $this->addCommand('dropColumn', compact('columns')); + } + + /** + * Indicate that the given columns should be renamed. + * + * @param string $from + * @param string $to + * @return \Illuminate\Support\Fluent + */ + public function renameColumn($from, $to) + { + return $this->addCommand('renameColumn', compact('from', 'to')); + } + + /** + * Indicate that the given primary key should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropPrimary($index = null) + { + return $this->dropIndexCommand('dropPrimary', 'primary', $index); + } + + /** + * Indicate that the given unique key should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropUnique($index) + { + return $this->dropIndexCommand('dropUnique', 'unique', $index); + } + + /** + * Indicate that the given index should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropIndex($index) + { + return $this->dropIndexCommand('dropIndex', 'index', $index); + } + + /** + * Indicate that the given foreign key should be dropped. + * + * @param string $index + * @return \Illuminate\Support\Fluent + */ + public function dropForeign($index) + { + return $this->dropIndexCommand('dropForeign', 'foreign', $index); + } + + /** + * Indicate that the timestamp columns should be dropped. + * + * @return void + */ + public function dropTimestamps() + { + $this->dropColumn('created_at', 'updated_at'); + } + + /** + * Indicate that the timestamp columns should be dropped. + * + * @return void + */ + public function dropTimestampsTz() + { + $this->dropTimestamps(); + } + + /** + * Indicate that the soft delete column should be dropped. + * + * @return void + */ + public function dropSoftDeletes() + { + $this->dropColumn('deleted_at'); + } + + /** + * Indicate that the remember token column should be dropped. + * + * @return void + */ + public function dropRememberToken() + { + $this->dropColumn('remember_token'); + } + + /** + * Rename the table to a given name. + * + * @param string $to + * @return \Illuminate\Support\Fluent + */ + public function rename($to) + { + return $this->addCommand('rename', compact('to')); + } + + /** + * Specify the primary key(s) for the table. + * + * @param string|array $columns + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function primary($columns, $name = null) + { + return $this->indexCommand('primary', $columns, $name); + } + + /** + * Specify a unique index for the table. + * + * @param string|array $columns + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function unique($columns, $name = null) + { + return $this->indexCommand('unique', $columns, $name); + } + + /** + * Specify an index for the table. + * + * @param string|array $columns + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function index($columns, $name = null) + { + return $this->indexCommand('index', $columns, $name); + } + + /** + * Specify a foreign key for the table. + * + * @param string|array $columns + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function foreign($columns, $name = null) + { + return $this->indexCommand('foreign', $columns, $name); + } + + /** + * Create a new auto-incrementing integer (4-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function increments($column) + { + return $this->unsignedInteger($column, true); + } + + /** + * Create a new auto-incrementing small integer (2-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function smallIncrements($column) + { + return $this->unsignedSmallInteger($column, true); + } + + /** + * Create a new auto-incrementing medium integer (3-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function mediumIncrements($column) + { + return $this->unsignedMediumInteger($column, true); + } + + /** + * Create a new auto-incrementing big integer (8-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function bigIncrements($column) + { + return $this->unsignedBigInteger($column, true); + } + + /** + * Create a new char column on the table. + * + * @param string $column + * @param int $length + * @return \Illuminate\Support\Fluent + */ + public function char($column, $length = 255) + { + return $this->addColumn('char', $column, compact('length')); + } + + /** + * Create a new string column on the table. + * + * @param string $column + * @param int $length + * @return \Illuminate\Support\Fluent + */ + public function string($column, $length = 255) + { + return $this->addColumn('string', $column, compact('length')); + } + + /** + * Create a new text column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function text($column) + { + return $this->addColumn('text', $column); + } + + /** + * Create a new medium text column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function mediumText($column) + { + return $this->addColumn('mediumText', $column); + } + + /** + * Create a new long text column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function longText($column) + { + return $this->addColumn('longText', $column); + } + + /** + * Create a new integer (4-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function integer($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('integer', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new tiny integer (1-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function tinyInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('tinyInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new small integer (2-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function smallInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('smallInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new medium integer (3-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function mediumInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('mediumInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new big integer (8-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function bigInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('bigInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new unsigned tiny integer (1-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Support\Fluent + */ + public function unsignedTinyInteger($column, $autoIncrement = false) + { + return $this->tinyInteger($column, $autoIncrement, true); + } + + /** + * Create a new unsigned small integer (2-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Support\Fluent + */ + public function unsignedSmallInteger($column, $autoIncrement = false) + { + return $this->smallInteger($column, $autoIncrement, true); + } + + /** + * Create a new unsigned medium integer (3-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Support\Fluent + */ + public function unsignedMediumInteger($column, $autoIncrement = false) + { + return $this->mediumInteger($column, $autoIncrement, true); + } + + /** + * Create a new unsigned integer (4-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Support\Fluent + */ + public function unsignedInteger($column, $autoIncrement = false) + { + return $this->integer($column, $autoIncrement, true); + } + + /** + * Create a new unsigned big integer (8-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Support\Fluent + */ + public function unsignedBigInteger($column, $autoIncrement = false) + { + return $this->bigInteger($column, $autoIncrement, true); + } + + /** + * Create a new float column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @return \Illuminate\Support\Fluent + */ + public function float($column, $total = 8, $places = 2) + { + return $this->addColumn('float', $column, compact('total', 'places')); + } + + /** + * Create a new double column on the table. + * + * @param string $column + * @param int|null $total + * @param int|null $places + * @return \Illuminate\Support\Fluent + */ + public function double($column, $total = null, $places = null) + { + return $this->addColumn('double', $column, compact('total', 'places')); + } + + /** + * Create a new decimal column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @return \Illuminate\Support\Fluent + */ + public function decimal($column, $total = 8, $places = 2) + { + return $this->addColumn('decimal', $column, compact('total', 'places')); + } + + /** + * Create a new boolean column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function boolean($column) + { + return $this->addColumn('boolean', $column); + } + + /** + * Create a new enum column on the table. + * + * @param string $column + * @param array $allowed + * @return \Illuminate\Support\Fluent + */ + public function enum($column, array $allowed) + { + return $this->addColumn('enum', $column, compact('allowed')); + } + + /** + * Create a new json column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function json($column) + { + return $this->addColumn('json', $column); + } + + /** + * Create a new jsonb column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function jsonb($column) + { + return $this->addColumn('jsonb', $column); + } + + /** + * Create a new date column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function date($column) + { + return $this->addColumn('date', $column); + } + + /** + * Create a new date-time column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function dateTime($column) + { + return $this->addColumn('dateTime', $column); + } + + /** + * Create a new date-time column (with time zone) on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function dateTimeTz($column) + { + return $this->addColumn('dateTimeTz', $column); + } + + /** + * Create a new time column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function time($column) + { + return $this->addColumn('time', $column); + } + + /** + * Create a new time column (with time zone) on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function timeTz($column) + { + return $this->addColumn('timeTz', $column); + } + + /** + * Create a new timestamp column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function timestamp($column) + { + return $this->addColumn('timestamp', $column); + } + + /** + * Create a new timestamp (with time zone) column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function timestampTz($column) + { + return $this->addColumn('timestampTz', $column); + } + + /** + * Add nullable creation and update timestamps to the table. + * + * @return void + */ + public function nullableTimestamps() + { + $this->timestamp('created_at')->nullable(); + + $this->timestamp('updated_at')->nullable(); + } + + /** + * Add creation and update timestamps to the table. + * + * @return void + */ + public function timestamps() + { + $this->timestamp('created_at'); + + $this->timestamp('updated_at'); + } + + /** + * Add creation and update timestampTz columns to the table. + * + * @return void + */ + public function timestampsTz() + { + $this->timestampTz('created_at'); + + $this->timestampTz('updated_at'); + } + + /** + * Add a "deleted at" timestamp for the table. + * + * @return \Illuminate\Support\Fluent + */ + public function softDeletes() + { + return $this->timestamp('deleted_at')->nullable(); + } + + /** + * Create a new binary column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function binary($column) + { + return $this->addColumn('binary', $column); + } + + /** + * Create a new uuid column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function uuid($column) + { + return $this->addColumn('uuid', $column); + } + + /** + * Add the proper columns for a polymorphic table. + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function morphs($name, $indexName = null) + { + $this->unsignedInteger("{$name}_id"); + + $this->string("{$name}_type"); + + $this->index(["{$name}_id", "{$name}_type"], $indexName); + } + + /** + * Adds the `remember_token` column to the table. + * + * @return \Illuminate\Support\Fluent + */ + public function rememberToken() + { + return $this->string('remember_token', 100)->nullable(); + } + + /** + * Create a new drop index command on the blueprint. + * + * @param string $command + * @param string $type + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + protected function dropIndexCommand($command, $type, $index) + { + $columns = []; + + // If the given "index" is actually an array of columns, the developer means + // to drop an index merely by specifying the columns involved without the + // conventional name, so we will build the index name from the columns. + if (is_array($index)) { + $columns = $index; + + $index = $this->createIndexName($type, $columns); + } + + return $this->indexCommand($command, $columns, $index); + } + + /** + * Add a new index command to the blueprint. + * + * @param string $type + * @param string|array $columns + * @param string $index + * @return \Illuminate\Support\Fluent + */ + protected function indexCommand($type, $columns, $index) + { + $columns = (array) $columns; + + // If no name was specified for this index, we will create one using a basic + // convention of the table name, followed by the columns, followed by an + // index type, such as primary or index, which makes the index unique. + if (is_null($index)) { + $index = $this->createIndexName($type, $columns); + } + + return $this->addCommand($type, compact('index', 'columns')); + } + + /** + * Create a default index name for the table. + * + * @param string $type + * @param array $columns + * @return string + */ + protected function createIndexName($type, array $columns) + { + $index = strtolower($this->table.'_'.implode('_', $columns).'_'.$type); + + return str_replace(['-', '.'], '_', $index); + } + + /** + * Add a new column to the blueprint. + * + * @param string $type + * @param string $name + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + public function addColumn($type, $name, array $parameters = []) + { + $attributes = array_merge(compact('type', 'name'), $parameters); + + $this->columns[] = $column = new Fluent($attributes); + + return $column; + } + + /** + * Remove a column from the schema blueprint. + * + * @param string $name + * @return $this + */ + public function removeColumn($name) + { + $this->columns = array_values(array_filter($this->columns, function ($c) use ($name) { + return $c['attributes']['name'] != $name; + })); + + return $this; + } + + /** + * Add a new command to the blueprint. + * + * @param string $name + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + protected function addCommand($name, array $parameters = []) + { + $this->commands[] = $command = $this->createCommand($name, $parameters); + + return $command; + } + + /** + * Create a new Fluent command. + * + * @param string $name + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + protected function createCommand($name, array $parameters = []) + { + return new Fluent(array_merge(compact('name'), $parameters)); + } + + /** + * Get the table the blueprint describes. + * + * @return string + */ + public function getTable() + { + return $this->table; + } + + /** + * Get the columns on the blueprint. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Get the commands on the blueprint. + * + * @return array + */ + public function getCommands() + { + return $this->commands; + } + + /** + * Get the columns on the blueprint that should be added. + * + * @return array + */ + public function getAddedColumns() + { + return array_filter($this->columns, function ($column) { + return ! $column->change; + }); + } + + /** + * Get the columns on the blueprint that should be changed. + * + * @return array + */ + public function getChangedColumns() + { + return array_filter($this->columns, function ($column) { + return (bool) $column->change; + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php new file mode 100644 index 0000000..5e10b42 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php @@ -0,0 +1,243 @@ +connection = $connection; + $this->grammar = $connection->getSchemaGrammar(); + } + + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + $sql = $this->grammar->compileTableExists(); + + $table = $this->connection->getTablePrefix().$table; + + return count($this->connection->select($sql, [$table])) > 0; + } + + /** + * Determine if the given table has a given column. + * + * @param string $table + * @param string $column + * @return bool + */ + public function hasColumn($table, $column) + { + $column = strtolower($column); + + return in_array($column, array_map('strtolower', $this->getColumnListing($table))); + } + + /** + * Determine if the given table has given columns. + * + * @param string $table + * @param array $columns + * @return bool + */ + public function hasColumns($table, array $columns) + { + $tableColumns = array_map('strtolower', $this->getColumnListing($table)); + + foreach ($columns as $column) { + if (! in_array(strtolower($column), $tableColumns)) { + return false; + } + } + + return true; + } + + /** + * Get the column listing for a given table. + * + * @param string $table + * @return array + */ + public function getColumnListing($table) + { + $table = $this->connection->getTablePrefix().$table; + + $results = $this->connection->select($this->grammar->compileColumnExists($table)); + + return $this->connection->getPostProcessor()->processColumnListing($results); + } + + /** + * Modify a table on the schema. + * + * @param string $table + * @param \Closure $callback + * @return \Illuminate\Database\Schema\Blueprint + */ + public function table($table, Closure $callback) + { + $this->build($this->createBlueprint($table, $callback)); + } + + /** + * Create a new table on the schema. + * + * @param string $table + * @param \Closure $callback + * @return \Illuminate\Database\Schema\Blueprint + */ + public function create($table, Closure $callback) + { + $blueprint = $this->createBlueprint($table); + + $blueprint->create(); + + $callback($blueprint); + + $this->build($blueprint); + } + + /** + * Drop a table from the schema. + * + * @param string $table + * @return \Illuminate\Database\Schema\Blueprint + */ + public function drop($table) + { + $blueprint = $this->createBlueprint($table); + + $blueprint->drop(); + + $this->build($blueprint); + } + + /** + * Drop a table from the schema if it exists. + * + * @param string $table + * @return \Illuminate\Database\Schema\Blueprint + */ + public function dropIfExists($table) + { + $blueprint = $this->createBlueprint($table); + + $blueprint->dropIfExists(); + + $this->build($blueprint); + } + + /** + * Rename a table on the schema. + * + * @param string $from + * @param string $to + * @return \Illuminate\Database\Schema\Blueprint + */ + public function rename($from, $to) + { + $blueprint = $this->createBlueprint($from); + + $blueprint->rename($to); + + $this->build($blueprint); + } + + /** + * Execute the blueprint to build / modify the table. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return void + */ + protected function build(Blueprint $blueprint) + { + $blueprint->build($this->connection, $this->grammar); + } + + /** + * Create a new command set with a Closure. + * + * @param string $table + * @param \Closure|null $callback + * @return \Illuminate\Database\Schema\Blueprint + */ + protected function createBlueprint($table, Closure $callback = null) + { + if (isset($this->resolver)) { + return call_user_func($this->resolver, $table, $callback); + } + + return new Blueprint($table, $callback); + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Set the database connection instance. + * + * @param \Illuminate\Database\Connection $connection + * @return $this + */ + public function setConnection(Connection $connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Set the Schema Blueprint resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public function blueprintResolver(Closure $resolver) + { + $this->resolver = $resolver; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php new file mode 100644 index 0000000..9dbc170 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -0,0 +1,452 @@ +getDoctrineSchemaManager(); + + $table = $this->getTablePrefix().$blueprint->getTable(); + + $column = $connection->getDoctrineColumn($table, $command->from); + + $tableDiff = $this->getRenamedDiff($blueprint, $command, $column, $schema); + + return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff); + } + + /** + * Get a new column instance with the new column name. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected function getRenamedDiff(Blueprint $blueprint, Fluent $command, Column $column, SchemaManager $schema) + { + $tableDiff = $this->getDoctrineTableDiff($blueprint, $schema); + + return $this->setRenamedColumns($tableDiff, $command, $column); + } + + /** + * Set the renamed columns on the table diff. + * + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Illuminate\Support\Fluent $command + * @param \Doctrine\DBAL\Schema\Column $column + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected function setRenamedColumns(TableDiff $tableDiff, Fluent $command, Column $column) + { + $newColumn = new Column($command->to, $column->getType(), $column->toArray()); + + $tableDiff->renamedColumns = [$command->from => $newColumn]; + + return $tableDiff; + } + + /** + * Compile a foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileForeign(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $on = $this->wrapTable($command->on); + + // We need to prepare several of the elements of the foreign key definition + // before we can create the SQL, such as wrapping the tables and convert + // an array of columns to comma-delimited strings for the SQL queries. + $columns = $this->columnize($command->columns); + + $onColumns = $this->columnize((array) $command->references); + + $sql = "alter table {$table} add constraint {$command->index} "; + + $sql .= "foreign key ({$columns}) references {$on} ({$onColumns})"; + + // Once we have the basic foreign key creation statement constructed we can + // build out the syntax for what should happen on an update or delete of + // the affected columns, which will get something like "cascade", etc. + if (! is_null($command->onDelete)) { + $sql .= " on delete {$command->onDelete}"; + } + + if (! is_null($command->onUpdate)) { + $sql .= " on update {$command->onUpdate}"; + } + + return $sql; + } + + /** + * Compile the blueprint's column definitions. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return array + */ + protected function getColumns(Blueprint $blueprint) + { + $columns = []; + + foreach ($blueprint->getAddedColumns() as $column) { + // Each of the column types have their own compiler functions which are tasked + // with turning the column definition into its SQL format for this platform + // used by the connection. The column's modifiers are compiled and added. + $sql = $this->wrap($column).' '.$this->getType($column); + + $columns[] = $this->addModifiers($sql, $blueprint, $column); + } + + return $columns; + } + + /** + * Add the column modifiers to the definition. + * + * @param string $sql + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function addModifiers($sql, Blueprint $blueprint, Fluent $column) + { + foreach ($this->modifiers as $modifier) { + if (method_exists($this, $method = "modify{$modifier}")) { + $sql .= $this->{$method}($blueprint, $column); + } + } + + return $sql; + } + + /** + * Get the primary key command if it exists on the blueprint. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param string $name + * @return \Illuminate\Support\Fluent|null + */ + protected function getCommandByName(Blueprint $blueprint, $name) + { + $commands = $this->getCommandsByName($blueprint, $name); + + if (count($commands) > 0) { + return reset($commands); + } + } + + /** + * Get all of the commands with a given name. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param string $name + * @return array + */ + protected function getCommandsByName(Blueprint $blueprint, $name) + { + return array_filter($blueprint->getCommands(), function ($value) use ($name) { + return $value->name == $name; + }); + } + + /** + * Get the SQL for the column data type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function getType(Fluent $column) + { + return $this->{'type'.ucfirst($column->type)}($column); + } + + /** + * Add a prefix to an array of values. + * + * @param string $prefix + * @param array $values + * @return array + */ + public function prefixArray($prefix, array $values) + { + return array_map(function ($value) use ($prefix) { + return $prefix.' '.$value; + }, $values); + } + + /** + * Wrap a table in keyword identifiers. + * + * @param mixed $table + * @return string + */ + public function wrapTable($table) + { + if ($table instanceof Blueprint) { + $table = $table->getTable(); + } + + return parent::wrapTable($table); + } + + /** + * {@inheritdoc} + */ + public function wrap($value, $prefixAlias = false) + { + if ($value instanceof Fluent) { + $value = $value->name; + } + + return parent::wrap($value, $prefixAlias); + } + + /** + * Format a value so that it can be used in "default" clauses. + * + * @param mixed $value + * @return string + */ + protected function getDefaultValue($value) + { + if ($value instanceof Expression) { + return $value; + } + + if (is_bool($value)) { + return "'".(int) $value."'"; + } + + return "'".strval($value)."'"; + } + + /** + * Create an empty Doctrine DBAL TableDiff from the Blueprint. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected function getDoctrineTableDiff(Blueprint $blueprint, SchemaManager $schema) + { + $table = $this->getTablePrefix().$blueprint->getTable(); + + $tableDiff = new TableDiff($table); + + $tableDiff->fromTable = $schema->listTableDetails($table); + + return $tableDiff; + } + + /** + * Compile a change column command into a series of SQL statements. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Illuminate\Database\Connection $connection + * @return array + */ + public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + { + if (! $connection->isDoctrineAvailable()) { + throw new RuntimeException(sprintf( + 'Changing columns for table "%s" requires Doctrine DBAL; install "doctrine/dbal".', + $blueprint->getTable() + )); + } + + $schema = $connection->getDoctrineSchemaManager(); + + $tableDiff = $this->getChangedDiff($blueprint, $schema); + + if ($tableDiff !== false) { + return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff); + } + + return []; + } + + /** + * Get the Doctrine table difference for the given changes. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @return \Doctrine\DBAL\Schema\TableDiff|bool + */ + protected function getChangedDiff(Blueprint $blueprint, SchemaManager $schema) + { + $table = $schema->listTableDetails($this->getTablePrefix().$blueprint->getTable()); + + return (new Comparator)->diffTable($table, $this->getTableWithColumnChanges($blueprint, $table)); + } + + /** + * Get a copy of the given Doctrine table after making the column changes. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Doctrine\DBAL\Schema\Table $table + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected function getTableWithColumnChanges(Blueprint $blueprint, Table $table) + { + $table = clone $table; + + foreach ($blueprint->getChangedColumns() as $fluent) { + $column = $this->getDoctrineColumnForChange($table, $fluent); + + // Here we will spin through each fluent column definition and map it to the proper + // Doctrine column definitions - which is necessary because Laravel and Doctrine + // use some different terminology for various column attributes on the tables. + foreach ($fluent->getAttributes() as $key => $value) { + if (! is_null($option = $this->mapFluentOptionToDoctrine($key))) { + if (method_exists($column, $method = 'set'.ucfirst($option))) { + $column->{$method}($this->mapFluentValueToDoctrine($option, $value)); + } + } + } + } + + return $table; + } + + /** + * Get the Doctrine column instance for a column change. + * + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Illuminate\Support\Fluent $fluent + * @return \Doctrine\DBAL\Schema\Column + */ + protected function getDoctrineColumnForChange(Table $table, Fluent $fluent) + { + return $table->changeColumn( + $fluent['name'], $this->getDoctrineColumnChangeOptions($fluent) + )->getColumn($fluent['name']); + } + + /** + * Get the Doctrine column change options. + * + * @param \Illuminate\Support\Fluent $fluent + * @return array + */ + protected function getDoctrineColumnChangeOptions(Fluent $fluent) + { + $options = ['type' => $this->getDoctrineColumnType($fluent['type'])]; + + if (in_array($fluent['type'], ['text', 'mediumText', 'longText'])) { + $options['length'] = $this->calculateDoctrineTextLength($fluent['type']); + } + + return $options; + } + + /** + * Get the doctrine column type. + * + * @param string $type + * @return \Doctrine\DBAL\Types\Type + */ + protected function getDoctrineColumnType($type) + { + $type = strtolower($type); + + switch ($type) { + case 'biginteger': + $type = 'bigint'; + break; + case 'smallinteger': + $type = 'smallint'; + break; + case 'mediumtext': + case 'longtext': + $type = 'text'; + break; + } + + return Type::getType($type); + } + + /** + * Calculate the proper column length to force the Doctrine text type. + * + * @param string $type + * @return int + */ + protected function calculateDoctrineTextLength($type) + { + switch ($type) { + case 'mediumText': + return 65535 + 1; + case 'longText': + return 16777215 + 1; + default: + return 255 + 1; + } + } + + /** + * Get the matching Doctrine option for a given Fluent attribute name. + * + * @param string $attribute + * @return string|null + */ + protected function mapFluentOptionToDoctrine($attribute) + { + switch ($attribute) { + case 'type': + case 'name': + return; + case 'nullable': + return 'notnull'; + case 'total': + return 'precision'; + case 'places': + return 'scale'; + default: + return $attribute; + } + } + + /** + * Get the matching Doctrine value for a given Fluent attribute. + * + * @param string $option + * @param mixed $value + * @return mixed + */ + protected function mapFluentValueToDoctrine($option, $value) + { + return $option == 'notnull' ? ! $value : $value; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php new file mode 100644 index 0000000..3687882 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -0,0 +1,721 @@ +getColumns($blueprint)); + + $sql = $blueprint->temporary ? 'create temporary' : 'create'; + + $sql .= ' table '.$this->wrapTable($blueprint)." ($columns)"; + + // Once we have the primary SQL, we can add the encoding option to the SQL for + // the table. Then, we can check if a storage engine has been supplied for + // the table. If so, we will add the engine declaration to the SQL query. + $sql = $this->compileCreateEncoding($sql, $connection, $blueprint); + + if (isset($blueprint->engine)) { + $sql .= ' engine = '.$blueprint->engine; + } + + return $sql; + } + + /** + * Append the character set specifications to a command. + * + * @param string $sql + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string + */ + protected function compileCreateEncoding($sql, Connection $connection, Blueprint $blueprint) + { + if (isset($blueprint->charset)) { + $sql .= ' default character set '.$blueprint->charset; + } elseif (! is_null($charset = $connection->getConfig('charset'))) { + $sql .= ' default character set '.$charset; + } + + if (isset($blueprint->collation)) { + $sql .= ' collate '.$blueprint->collation; + } elseif (! is_null($collation = $connection->getConfig('collation'))) { + $sql .= ' collate '.$collation; + } + + return $sql; + } + + /** + * Compile an add column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->prefixArray('add', $this->getColumns($blueprint)); + + return 'alter table '.$table.' '.implode(', ', $columns); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + $command->name(null); + + return $this->compileKey($blueprint, $command, 'primary key'); + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + return $this->compileKey($blueprint, $command, 'unique'); + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + return $this->compileKey($blueprint, $command, 'index'); + } + + /** + * Compile an index creation command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param string $type + * @return string + */ + protected function compileKey(Blueprint $blueprint, Fluent $command, $type) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "alter table {$table} add {$type} `{$command->index}`($columns)"; + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->prefixArray('drop', $this->wrapArray($command->columns)); + + $table = $this->wrapTable($blueprint); + + return 'alter table '.$table.' '.implode(', ', $columns); + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + return 'alter table '.$this->wrapTable($blueprint).' drop primary key'; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop index `{$command->index}`"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop index `{$command->index}`"; + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop foreign key `{$command->index}`"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "rename table {$from} to ".$this->wrapTable($command->to); + } + + /** + * Create the column definition for a char type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeChar(Fluent $column) + { + return "char({$column->length})"; + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "varchar({$column->length})"; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'mediumtext'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'longtext'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'bigint'; + } + + /** + * Create the column definition for a integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'mediumint'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'tinyint'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'smallint'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return $this->typeDouble($column); + } + + /** + * Create the column definition for a double type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDouble(Fluent $column) + { + if ($column->total && $column->places) { + return "double({$column->total}, {$column->places})"; + } + + return 'double'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'tinyint(1)'; + } + + /** + * Create the column definition for an enum type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return "enum('".implode("', '", $column->allowed)."')"; + } + + /** + * Create the column definition for a json type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJson(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a jsonb type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJsonb(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTimeTz(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimeTz(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + if ($column->useCurrent) { + return 'timestamp default CURRENT_TIMESTAMP'; + } + + if (! $column->nullable && $column->default === null) { + return 'timestamp default 0'; + } + + return 'timestamp'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestampTz(Fluent $column) + { + if ($column->useCurrent) { + return 'timestamp default CURRENT_TIMESTAMP'; + } + + if (! $column->nullable && $column->default === null) { + return 'timestamp default 0'; + } + + return 'timestamp'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'blob'; + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'char(36)'; + } + + /** + * Get the SQL for an unsigned column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyUnsigned(Blueprint $blueprint, Fluent $column) + { + if ($column->unsigned) { + return ' unsigned'; + } + } + + /** + * Get the SQL for a character set column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyCharset(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->charset)) { + return ' character set '.$column->charset; + } + } + + /** + * Get the SQL for a collation column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyCollate(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->collation)) { + return ' collate '.$column->collation; + } + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return $column->nullable ? ' null' : ' not null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->default)) { + return ' default '.$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) && $column->autoIncrement) { + return ' auto_increment primary key'; + } + } + + /** + * Get the SQL for a "first" column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyFirst(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->first)) { + return ' first'; + } + } + + /** + * Get the SQL for an "after" column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyAfter(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->after)) { + return ' after '.$this->wrap($column->after); + } + } + + /** + * Get the SQL for a "comment" column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyComment(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->comment)) { + return ' comment "'.$column->comment.'"'; + } + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + if ($value === '*') { + return $value; + } + + return '`'.str_replace('`', '``', $value).'`'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php new file mode 100644 index 0000000..aad33f1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -0,0 +1,568 @@ +getColumns($blueprint)); + + $sql = $blueprint->temporary ? 'create temporary' : 'create'; + + $sql .= ' table '.$this->wrapTable($blueprint)." ($columns)"; + + return $sql; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); + + return 'alter table '.$table.' '.implode(', ', $columns); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + return 'alter table '.$this->wrapTable($blueprint)." add primary key ({$columns})"; + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->columnize($command->columns); + + return "alter table $table add constraint {$command->index} unique ($columns)"; + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + return "create index {$command->index} on ".$this->wrapTable($blueprint)." ({$columns})"; + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); + + $table = $this->wrapTable($blueprint); + + return 'alter table '.$table.' '.implode(', ', $columns); + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + $table = $blueprint->getTable(); + + return 'alter table '.$this->wrapTable($blueprint)." drop constraint {$table}_pkey"; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop constraint {$command->index}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + return "drop index {$command->index}"; + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop constraint {$command->index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "alter table {$from} rename to ".$this->wrapTable($command->to); + } + + /** + * Create the column definition for a char type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeChar(Fluent $column) + { + return "char({$column->length})"; + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "varchar({$column->length})"; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return $column->autoIncrement ? 'serial' : 'integer'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return $column->autoIncrement ? 'bigserial' : 'bigint'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return $column->autoIncrement ? 'serial' : 'integer'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return $column->autoIncrement ? 'smallserial' : 'smallint'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return $column->autoIncrement ? 'smallserial' : 'smallint'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return $this->typeDouble($column); + } + + /** + * Create the column definition for a double type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDouble(Fluent $column) + { + return 'double precision'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'boolean'; + } + + /** + * Create the column definition for an enum type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + $allowed = array_map(function ($a) { + return "'".$a."'"; + }, $column->allowed); + + return "varchar(255) check (\"{$column->name}\" in (".implode(', ', $allowed).'))'; + } + + /** + * Create the column definition for a json type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJson(Fluent $column) + { + return 'json'; + } + + /** + * Create the column definition for a jsonb type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJsonb(Fluent $column) + { + return 'jsonb'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return 'timestamp(0) without time zone'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTimeTz(Fluent $column) + { + return 'timestamp(0) with time zone'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time(0) without time zone'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimeTz(Fluent $column) + { + return 'time(0) with time zone'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + if ($column->useCurrent) { + return 'timestamp(0) without time zone default CURRENT_TIMESTAMP(0)'; + } + + return 'timestamp(0) without time zone'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestampTz(Fluent $column) + { + if ($column->useCurrent) { + return 'timestamp(0) with time zone default CURRENT_TIMESTAMP(0)'; + } + + return 'timestamp(0) with time zone'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'bytea'; + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'uuid'; + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return $column->nullable ? ' null' : ' not null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->default)) { + return ' default '.$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) && $column->autoIncrement) { + return ' primary key'; + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php new file mode 100644 index 0000000..a257de7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -0,0 +1,625 @@ +getColumns($blueprint)); + + $sql = $blueprint->temporary ? 'create temporary' : 'create'; + + $sql .= ' table '.$this->wrapTable($blueprint)." ($columns"; + + // SQLite forces primary keys to be added when the table is initially created + // so we will need to check for a primary key commands and add the columns + // to the table's declaration here so they can be created on the tables. + $sql .= (string) $this->addForeignKeys($blueprint); + + $sql .= (string) $this->addPrimaryKeys($blueprint); + + return $sql.')'; + } + + /** + * Get the foreign key syntax for a table creation statement. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string|null + */ + protected function addForeignKeys(Blueprint $blueprint) + { + $sql = ''; + + $foreigns = $this->getCommandsByName($blueprint, 'foreign'); + + // Once we have all the foreign key commands for the table creation statement + // we'll loop through each of them and add them to the create table SQL we + // are building, since SQLite needs foreign keys on the tables creation. + foreach ($foreigns as $foreign) { + $sql .= $this->getForeignKey($foreign); + + if (! is_null($foreign->onDelete)) { + $sql .= " on delete {$foreign->onDelete}"; + } + + if (! is_null($foreign->onUpdate)) { + $sql .= " on update {$foreign->onUpdate}"; + } + } + + return $sql; + } + + /** + * Get the SQL for the foreign key. + * + * @param \Illuminate\Support\Fluent $foreign + * @return string + */ + protected function getForeignKey($foreign) + { + $on = $this->wrapTable($foreign->on); + + // We need to columnize the columns that the foreign key is being defined for + // so that it is a properly formatted list. Once we have done this, we can + // return the foreign key SQL declaration to the calling method for use. + $columns = $this->columnize($foreign->columns); + + $onColumns = $this->columnize((array) $foreign->references); + + return ", foreign key($columns) references $on($onColumns)"; + } + + /** + * Get the primary key syntax for a table creation statement. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string|null + */ + protected function addPrimaryKeys(Blueprint $blueprint) + { + $primary = $this->getCommandByName($blueprint, 'primary'); + + if (! is_null($primary)) { + $columns = $this->columnize($primary->columns); + + return ", primary key ({$columns})"; + } + } + + /** + * Compile alter table commands for adding columns. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return array + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); + + $statements = []; + + foreach ($columns as $column) { + $statements[] = 'alter table '.$table.' '.$column; + } + + return $statements; + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "create unique index {$command->index} on {$table} ({$columns})"; + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "create index {$command->index} on {$table} ({$columns})"; + } + + /** + * Compile a foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileForeign(Blueprint $blueprint, Fluent $command) + { + // Handled on table creation... + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Illuminate\Database\Connection $connection + * @return array + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + { + $schema = $connection->getDoctrineSchemaManager(); + + $tableDiff = $this->getDoctrineTableDiff($blueprint, $schema); + + foreach ($command->columns as $name) { + $column = $connection->getDoctrineColumn($blueprint->getTable(), $name); + + $tableDiff->removedColumns[$name] = $column; + } + + return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff); + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + return "drop index {$command->index}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + return "drop index {$command->index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "alter table {$from} rename to ".$this->wrapTable($command->to); + } + + /** + * Create the column definition for a char type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeChar(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a double type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDouble(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return 'numeric'; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'tinyint(1)'; + } + + /** + * Create the column definition for an enum type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a json type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJson(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a jsonb type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJsonb(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a date-time type. + * + * Note: "SQLite does not have a storage class set aside for storing dates and/or times." + * @link https://www.sqlite.org/datatype3.html + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTimeTz(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimeTz(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + if ($column->useCurrent) { + return 'datetime default CURRENT_TIMESTAMP'; + } + + return 'datetime'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestampTz(Fluent $column) + { + if ($column->useCurrent) { + return 'datetime default CURRENT_TIMESTAMP'; + } + + return 'datetime'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'blob'; + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'varchar'; + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return $column->nullable ? ' null' : ' not null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->default)) { + return ' default '.$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) && $column->autoIncrement) { + return ' primary key autoincrement'; + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php new file mode 100644 index 0000000..ba5d245 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -0,0 +1,570 @@ +getColumns($blueprint)); + + return 'create table '.$this->wrapTable($blueprint)." ($columns)"; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->getColumns($blueprint); + + return 'alter table '.$table.' add '.implode(', ', $columns); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "alter table {$table} add constraint {$command->index} primary key ({$columns})"; + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "create unique index {$command->index} on {$table} ({$columns})"; + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "create index {$command->index} on {$table} ({$columns})"; + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'if exists (select * from INFORMATION_SCHEMA.TABLES where TABLE_NAME = \''.$blueprint->getTable().'\') drop table '.$blueprint->getTable(); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->wrapArray($command->columns); + + $table = $this->wrapTable($blueprint); + + return 'alter table '.$table.' drop column '.implode(', ', $columns); + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop constraint {$command->index}"; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "drop index {$command->index} on {$table}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "drop index {$command->index} on {$table}"; + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop constraint {$command->index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "sp_rename {$from}, ".$this->wrapTable($command->to); + } + + /** + * Create the column definition for a char type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeChar(Fluent $column) + { + return "nchar({$column->length})"; + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "nvarchar({$column->length})"; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'bigint'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'tinyint'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'smallint'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a double type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDouble(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'bit'; + } + + /** + * Create the column definition for an enum type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return 'nvarchar(255)'; + } + + /** + * Create the column definition for a json type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJson(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a jsonb type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJsonb(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTimeTz(Fluent $column) + { + return 'datetimeoffset(0)'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimeTz(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + if ($column->useCurrent) { + return 'datetime default CURRENT_TIMESTAMP'; + } + + return 'datetime'; + } + + /** + * Create the column definition for a timestamp type. + * + * @link https://msdn.microsoft.com/en-us/library/bb630289(v=sql.120).aspx + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestampTz(Fluent $column) + { + if ($column->useCurrent) { + return 'datetimeoffset(0) default CURRENT_TIMESTAMP'; + } + + return 'datetimeoffset(0)'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'varbinary(max)'; + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'uniqueidentifier'; + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return $column->nullable ? ' null' : ' not null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->default)) { + return ' default '.$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) && $column->autoIncrement) { + return ' identity primary key'; + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/MySqlBuilder.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/MySqlBuilder.php new file mode 100644 index 0000000..e0b0ade --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/MySqlBuilder.php @@ -0,0 +1,42 @@ +grammar->compileTableExists(); + + $database = $this->connection->getDatabaseName(); + + $table = $this->connection->getTablePrefix().$table; + + return count($this->connection->select($sql, [$database, $table])) > 0; + } + + /** + * Get the column listing for a given table. + * + * @param string $table + * @return array + */ + public function getColumnListing($table) + { + $sql = $this->grammar->compileColumnExists(); + + $database = $this->connection->getDatabaseName(); + + $table = $this->connection->getTablePrefix().$table; + + $results = $this->connection->select($sql, [$database, $table]); + + return $this->connection->getPostProcessor()->processColumnListing($results); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Schema/PostgresBuilder.php b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/PostgresBuilder.php new file mode 100644 index 0000000..da3fd60 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -0,0 +1,25 @@ +grammar->compileTableExists(); + + $schema = $this->connection->getConfig('schema'); + + $schema = $schema ? $schema : 'public'; + + $table = $this->connection->getTablePrefix().$table; + + return count($this->connection->select($sql, [$schema, $table])) > 0; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/SeedServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Database/SeedServiceProvider.php new file mode 100644 index 0000000..3a97cde --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/SeedServiceProvider.php @@ -0,0 +1,69 @@ +registerSeedCommand(); + + $this->registerMakeCommand(); + + $this->app->singleton('seeder', function () { + return new Seeder; + }); + + $this->commands('command.seed', 'command.seeder.make'); + } + + /** + * Register the seed console command. + * + * @return void + */ + protected function registerSeedCommand() + { + $this->app->singleton('command.seed', function ($app) { + return new SeedCommand($app['db']); + }); + } + + /** + * Register the seeder generator command. + * + * @return void + */ + protected function registerMakeCommand() + { + $this->app->singleton('command.seeder.make', function ($app) { + return new SeederMakeCommand($app['files'], $app['composer']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['seeder', 'command.seed', 'command.seeder.make']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/Seeder.php b/core/vendor/laravel/framework/src/Illuminate/Database/Seeder.php new file mode 100644 index 0000000..4e29c16 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/Seeder.php @@ -0,0 +1,97 @@ +resolve($class)->run(); + + if (isset($this->command)) { + $this->command->getOutput()->writeln("Seeded: $class"); + } + } + + /** + * Resolve an instance of the given seeder class. + * + * @param string $class + * @return \Illuminate\Database\Seeder + */ + protected function resolve($class) + { + if (isset($this->container)) { + $instance = $this->container->make($class); + + $instance->setContainer($this->container); + } else { + $instance = new $class; + } + + if (isset($this->command)) { + $instance->setCommand($this->command); + } + + return $instance; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return $this + */ + public function setContainer(Container $container) + { + $this->container = $container; + + return $this; + } + + /** + * Set the console command instance. + * + * @param \Illuminate\Console\Command $command + * @return $this + */ + public function setCommand(Command $command) + { + $this->command = $command; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/SqlServerConnection.php b/core/vendor/laravel/framework/src/Illuminate/Database/SqlServerConnection.php new file mode 100644 index 0000000..fa097e2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/SqlServerConnection.php @@ -0,0 +1,95 @@ +getDriverName() == 'sqlsrv') { + return parent::transaction($callback); + } + + $this->pdo->exec('BEGIN TRAN'); + + // We'll simply execute the given callback within a try / catch block + // and if we catch any exception we can rollback the transaction + // so that none of the changes are persisted to the database. + try { + $result = $callback($this); + + $this->pdo->exec('COMMIT TRAN'); + } + + // If we catch an exception, we will roll back so nothing gets messed + // up in the database. Then we'll re-throw the exception so it can + // be handled how the developer sees fit for their applications. + catch (Exception $e) { + $this->pdo->exec('ROLLBACK TRAN'); + + throw $e; + } catch (Throwable $e) { + $this->pdo->exec('ROLLBACK TRAN'); + + throw $e; + } + + return $result; + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\SqlServerGrammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new QueryGrammar); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\SqlServerGrammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\SqlServerProcessor + */ + protected function getDefaultPostProcessor() + { + return new SqlServerProcessor; + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Doctrine\DBAL\Driver\PDOSqlsrv\Driver + */ + protected function getDoctrineDriver() + { + return new DoctrineDriver; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Database/composer.json b/core/vendor/laravel/framework/src/Illuminate/Database/composer.json new file mode 100644 index 0000000..26c462a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Database/composer.json @@ -0,0 +1,43 @@ +{ + "name": "illuminate/database", + "description": "The Illuminate Database package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "keywords": ["laravel", "database", "sql", "orm"], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/container": "5.1.*", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "nesbot/carbon": "~1.19" + }, + "autoload": { + "psr-4": { + "Illuminate\\Database\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).", + "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", + "illuminate/console": "Required to use the database commands (5.1.*).", + "illuminate/events": "Required to use the observers with Eloquent (5.1.*).", + "illuminate/filesystem": "Required to use the migrations (5.1.*).", + "illuminate/pagination": "Required to paginate the result set (5.1.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Encryption/BaseEncrypter.php b/core/vendor/laravel/framework/src/Illuminate/Encryption/BaseEncrypter.php new file mode 100644 index 0000000..1d427af --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Encryption/BaseEncrypter.php @@ -0,0 +1,82 @@ +key); + } + + /** + * Get the JSON array from the given payload. + * + * @param string $payload + * @return array + * + * @throws \Illuminate\Contracts\Encryption\DecryptException + */ + protected function getJsonPayload($payload) + { + $payload = json_decode(base64_decode($payload), true); + + // If the payload is not valid JSON or does not have the proper keys set we will + // assume it is invalid and bail out of the routine since we will not be able + // to decrypt the given value. We'll also check the MAC for this encryption. + if (! $payload || $this->invalidPayload($payload)) { + throw new DecryptException('The payload is invalid.'); + } + + if (! $this->validMac($payload)) { + throw new DecryptException('The MAC is invalid.'); + } + + return $payload; + } + + /** + * Verify that the encryption payload is valid. + * + * @param array|mixed $data + * @return bool + */ + protected function invalidPayload($data) + { + return ! is_array($data) || ! isset($data['iv']) || ! isset($data['value']) || ! isset($data['mac']); + } + + /** + * Determine if the MAC for the given payload is valid. + * + * @param array $payload + * @return bool + * + * @throws \RuntimeException + */ + protected function validMac(array $payload) + { + $bytes = Str::randomBytes(16); + + $calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true); + + return Str::equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php b/core/vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php new file mode 100644 index 0000000..71a0ddc --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php @@ -0,0 +1,119 @@ +key = $key; + $this->cipher = $cipher; + } else { + throw new RuntimeException('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.'); + } + } + + /** + * Determine if the given key and cipher combination is valid. + * + * @param string $key + * @param string $cipher + * @return bool + */ + public static function supported($key, $cipher) + { + $length = mb_strlen($key, '8bit'); + + return ($cipher === 'AES-128-CBC' && $length === 16) || ($cipher === 'AES-256-CBC' && $length === 32); + } + + /** + * Encrypt the given value. + * + * @param string $value + * @return string + * + * @throws \Illuminate\Contracts\Encryption\EncryptException + */ + public function encrypt($value) + { + $iv = Str::randomBytes($this->getIvSize()); + + $value = \openssl_encrypt(serialize($value), $this->cipher, $this->key, 0, $iv); + + if ($value === false) { + throw new EncryptException('Could not encrypt the data.'); + } + + // Once we have the encrypted value we will go ahead base64_encode the input + // vector and create the MAC for the encrypted value so we can verify its + // authenticity. Then, we'll JSON encode the data in a "payload" array. + $mac = $this->hash($iv = base64_encode($iv), $value); + + $json = json_encode(compact('iv', 'value', 'mac')); + + if (! is_string($json)) { + throw new EncryptException('Could not encrypt the data.'); + } + + return base64_encode($json); + } + + /** + * Decrypt the given value. + * + * @param string $payload + * @return string + * + * @throws \Illuminate\Contracts\Encryption\DecryptException + */ + public function decrypt($payload) + { + $payload = $this->getJsonPayload($payload); + + $iv = base64_decode($payload['iv']); + + $decrypted = \openssl_decrypt($payload['value'], $this->cipher, $this->key, 0, $iv); + + if ($decrypted === false) { + throw new DecryptException('Could not decrypt the data.'); + } + + return unserialize($decrypted); + } + + /** + * Get the IV size for the cipher. + * + * @return int + */ + protected function getIvSize() + { + return 16; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php new file mode 100644 index 0000000..a0b9491 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php @@ -0,0 +1,33 @@ +app->singleton('encrypter', function ($app) { + $config = $app->make('config')->get('app'); + + $key = $config['key']; + + $cipher = $config['cipher']; + + if (Encrypter::supported($key, $cipher)) { + return new Encrypter($key, $cipher); + } elseif (McryptEncrypter::supported($key, $cipher)) { + return new McryptEncrypter($key, $cipher); + } else { + throw new RuntimeException('No supported encrypter found. The cipher and / or key length are invalid.'); + } + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Encryption/McryptEncrypter.php b/core/vendor/laravel/framework/src/Illuminate/Encryption/McryptEncrypter.php new file mode 100644 index 0000000..87958e6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Encryption/McryptEncrypter.php @@ -0,0 +1,214 @@ +key = $key; + $this->cipher = $cipher; + $this->block = mcrypt_get_iv_size($this->cipher, MCRYPT_MODE_CBC); + } else { + throw new RuntimeException('The only supported ciphers are MCRYPT_RIJNDAEL_128 and MCRYPT_RIJNDAEL_256.'); + } + } + + /** + * Determine if the given key and cipher combination is valid. + * + * @param string $key + * @param string $cipher + * @return bool + */ + public static function supported($key, $cipher) + { + return defined('MCRYPT_RIJNDAEL_128') && + ($cipher === MCRYPT_RIJNDAEL_128 || $cipher === MCRYPT_RIJNDAEL_256); + } + + /** + * Encrypt the given value. + * + * @param string $value + * @return string + * + * @throws \Illuminate\Contracts\Encryption\EncryptException + */ + public function encrypt($value) + { + $iv = mcrypt_create_iv($this->getIvSize(), $this->getRandomizer()); + + $value = base64_encode($this->padAndMcrypt($value, $iv)); + + // Once we have the encrypted value we will go ahead base64_encode the input + // vector and create the MAC for the encrypted value so we can verify its + // authenticity. Then, we'll JSON encode the data in a "payload" array. + $mac = $this->hash($iv = base64_encode($iv), $value); + + $json = json_encode(compact('iv', 'value', 'mac')); + + if (! is_string($json)) { + throw new EncryptException('Could not encrypt the data.'); + } + + return base64_encode($json); + } + + /** + * Pad and use mcrypt on the given value and input vector. + * + * @param string $value + * @param string $iv + * @return string + */ + protected function padAndMcrypt($value, $iv) + { + $value = $this->addPadding(serialize($value)); + + return mcrypt_encrypt($this->cipher, $this->key, $value, MCRYPT_MODE_CBC, $iv); + } + + /** + * Decrypt the given value. + * + * @param string $payload + * @return string + */ + public function decrypt($payload) + { + $payload = $this->getJsonPayload($payload); + + // We'll go ahead and remove the PKCS7 padding from the encrypted value before + // we decrypt it. Once we have the de-padded value, we will grab the vector + // and decrypt the data, passing back the unserialized from of the value. + $value = base64_decode($payload['value']); + + $iv = base64_decode($payload['iv']); + + return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv))); + } + + /** + * Run the mcrypt decryption routine for the value. + * + * @param string $value + * @param string $iv + * @return string + * + * @throws \Illuminate\Contracts\Encryption\DecryptException + */ + protected function mcryptDecrypt($value, $iv) + { + try { + return mcrypt_decrypt($this->cipher, $this->key, $value, MCRYPT_MODE_CBC, $iv); + } catch (Exception $e) { + throw new DecryptException($e->getMessage()); + } + } + + /** + * Add PKCS7 padding to a given value. + * + * @param string $value + * @return string + */ + protected function addPadding($value) + { + $pad = $this->block - (strlen($value) % $this->block); + + return $value.str_repeat(chr($pad), $pad); + } + + /** + * Remove the padding from the given value. + * + * @param string $value + * @return string + */ + protected function stripPadding($value) + { + $pad = ord($value[($len = strlen($value)) - 1]); + + return $this->paddingIsValid($pad, $value) ? substr($value, 0, $len - $pad) : $value; + } + + /** + * Determine if the given padding for a value is valid. + * + * @param string $pad + * @param string $value + * @return bool + */ + protected function paddingIsValid($pad, $value) + { + $beforePad = strlen($value) - $pad; + + return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad); + } + + /** + * Get the IV size for the cipher. + * + * @return int + */ + protected function getIvSize() + { + return mcrypt_get_iv_size($this->cipher, MCRYPT_MODE_CBC); + } + + /** + * Get the random data source available for the OS. + * + * @return int + */ + protected function getRandomizer() + { + if (defined('MCRYPT_DEV_URANDOM')) { + return MCRYPT_DEV_URANDOM; + } + + if (defined('MCRYPT_DEV_RANDOM')) { + return MCRYPT_DEV_RANDOM; + } + + mt_srand(); + + return MCRYPT_RAND; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Encryption/composer.json b/core/vendor/laravel/framework/src/Illuminate/Encryption/composer.json new file mode 100644 index 0000000..0b52c2a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Encryption/composer.json @@ -0,0 +1,35 @@ +{ + "name": "illuminate/encryption", + "description": "The Illuminate Encryption package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "ext-mbstring": "*", + "ext-openssl": "*", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "paragonie/random_compat": "~1.4" + }, + "autoload": { + "psr-4": { + "Illuminate\\Encryption\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Events/CallQueuedHandler.php b/core/vendor/laravel/framework/src/Illuminate/Events/CallQueuedHandler.php new file mode 100644 index 0000000..b182cd8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Events/CallQueuedHandler.php @@ -0,0 +1,80 @@ +container = $container; + } + + /** + * Handle the queued job. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param array $data + * @return void + */ + public function call(Job $job, array $data) + { + $handler = $this->setJobInstanceIfNecessary( + $job, $this->container->make($data['class']) + ); + + call_user_func_array( + [$handler, $data['method']], unserialize($data['data']) + ); + + if (! $job->isDeletedOrReleased()) { + $job->delete(); + } + } + + /** + * Set the job instance of the given class if necessary. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param mixed $instance + * @return mixed + */ + protected function setJobInstanceIfNecessary(Job $job, $instance) + { + if (in_array('Illuminate\Queue\InteractsWithQueue', class_uses_recursive(get_class($instance)))) { + $instance->setJob($job); + } + + return $instance; + } + + /** + * Call the failed method on the job instance. + * + * @param array $data + * @return void + */ + public function failed(array $data) + { + $handler = $this->container->make($data['class']); + + if (method_exists($handler, 'failed')) { + call_user_func_array([$handler, 'failed'], unserialize($data['data'])); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php b/core/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php new file mode 100644 index 0000000..63dc899 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php @@ -0,0 +1,503 @@ +container = $container ?: new Container; + } + + /** + * Register an event listener with the dispatcher. + * + * @param string|array $events + * @param mixed $listener + * @param int $priority + * @return void + */ + public function listen($events, $listener, $priority = 0) + { + foreach ((array) $events as $event) { + if (Str::contains($event, '*')) { + $this->setupWildcardListen($event, $listener); + } else { + $this->listeners[$event][$priority][] = $this->makeListener($listener); + + unset($this->sorted[$event]); + } + } + } + + /** + * Setup a wildcard listener callback. + * + * @param string $event + * @param mixed $listener + * @return void + */ + protected function setupWildcardListen($event, $listener) + { + $this->wildcards[$event][] = $this->makeListener($listener); + } + + /** + * Determine if a given event has listeners. + * + * @param string $eventName + * @return bool + */ + public function hasListeners($eventName) + { + return isset($this->listeners[$eventName]) || isset($this->wildcards[$eventName]); + } + + /** + * Register an event and payload to be fired later. + * + * @param string $event + * @param array $payload + * @return void + */ + public function push($event, $payload = []) + { + $this->listen($event.'_pushed', function () use ($event, $payload) { + $this->fire($event, $payload); + }); + } + + /** + * Register an event subscriber with the dispatcher. + * + * @param object|string $subscriber + * @return void + */ + public function subscribe($subscriber) + { + $subscriber = $this->resolveSubscriber($subscriber); + + $subscriber->subscribe($this); + } + + /** + * Resolve the subscriber instance. + * + * @param object|string $subscriber + * @return mixed + */ + protected function resolveSubscriber($subscriber) + { + if (is_string($subscriber)) { + return $this->container->make($subscriber); + } + + return $subscriber; + } + + /** + * Fire an event until the first non-null response is returned. + * + * @param string|object $event + * @param array $payload + * @return mixed + */ + public function until($event, $payload = []) + { + return $this->fire($event, $payload, true); + } + + /** + * Flush a set of pushed events. + * + * @param string $event + * @return void + */ + public function flush($event) + { + $this->fire($event.'_pushed'); + } + + /** + * Get the event that is currently firing. + * + * @return string + */ + public function firing() + { + return last($this->firing); + } + + /** + * Fire an event and call the listeners. + * + * @param string|object $event + * @param mixed $payload + * @param bool $halt + * @return array|null + */ + public function fire($event, $payload = [], $halt = false) + { + // When the given "event" is actually an object we will assume it is an event + // object and use the class as the event name and this event itself as the + // payload to the handler, which makes object based events quite simple. + if (is_object($event)) { + list($payload, $event) = [[$event], get_class($event)]; + } + + $responses = []; + + // If an array is not given to us as the payload, we will turn it into one so + // we can easily use call_user_func_array on the listeners, passing in the + // payload to each of them so that they receive each of these arguments. + if (! is_array($payload)) { + $payload = [$payload]; + } + + $this->firing[] = $event; + + if (isset($payload[0]) && $payload[0] instanceof ShouldBroadcast) { + $this->broadcastEvent($payload[0]); + } + + foreach ($this->getListeners($event) as $listener) { + $response = call_user_func_array($listener, $payload); + + // If a response is returned from the listener and event halting is enabled + // we will just return this response, and not call the rest of the event + // listeners. Otherwise we will add the response on the response list. + if (! is_null($response) && $halt) { + array_pop($this->firing); + + return $response; + } + + // If a boolean false is returned from a listener, we will stop propagating + // the event to any further listeners down in the chain, else we keep on + // looping through the listeners and firing every one in our sequence. + if ($response === false) { + break; + } + + $responses[] = $response; + } + + array_pop($this->firing); + + return $halt ? null : $responses; + } + + /** + * Broadcast the given event class. + * + * @param \Illuminate\Contracts\Broadcasting\ShouldBroadcast $event + * @return void + */ + protected function broadcastEvent($event) + { + if ($this->queueResolver) { + $connection = $event instanceof ShouldBroadcastNow ? 'sync' : null; + + $queue = method_exists($event, 'onQueue') ? $event->onQueue() : null; + + $this->resolveQueue()->connection($connection)->pushOn($queue, 'Illuminate\Broadcasting\BroadcastEvent', [ + 'event' => serialize(clone $event), + ]); + } + } + + /** + * Get all of the listeners for a given event name. + * + * @param string $eventName + * @return array + */ + public function getListeners($eventName) + { + $wildcards = $this->getWildcardListeners($eventName); + + if (! isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return array_merge($this->sorted[$eventName], $wildcards); + } + + /** + * Get the wildcard listeners for the event. + * + * @param string $eventName + * @return array + */ + protected function getWildcardListeners($eventName) + { + $wildcards = []; + + foreach ($this->wildcards as $key => $listeners) { + if (Str::is($key, $eventName)) { + $wildcards = array_merge($wildcards, $listeners); + } + } + + return $wildcards; + } + + /** + * Sort the listeners for a given event by priority. + * + * @param string $eventName + * @return array + */ + protected function sortListeners($eventName) + { + $this->sorted[$eventName] = []; + + // If listeners exist for the given event, we will sort them by the priority + // so that we can call them in the correct order. We will cache off these + // sorted event listeners so we do not have to re-sort on every events. + if (isset($this->listeners[$eventName])) { + krsort($this->listeners[$eventName]); + + $this->sorted[$eventName] = call_user_func_array( + 'array_merge', $this->listeners[$eventName] + ); + } + } + + /** + * Register an event listener with the dispatcher. + * + * @param mixed $listener + * @return mixed + */ + public function makeListener($listener) + { + return is_string($listener) ? $this->createClassListener($listener) : $listener; + } + + /** + * Create a class based listener using the IoC container. + * + * @param mixed $listener + * @return \Closure + */ + public function createClassListener($listener) + { + $container = $this->container; + + return function () use ($listener, $container) { + return call_user_func_array( + $this->createClassCallable($listener, $container), func_get_args() + ); + }; + } + + /** + * Create the class based event callable. + * + * @param string $listener + * @param \Illuminate\Container\Container $container + * @return callable + */ + protected function createClassCallable($listener, $container) + { + list($class, $method) = $this->parseClassCallable($listener); + + if ($this->handlerShouldBeQueued($class)) { + return $this->createQueuedHandlerCallable($class, $method); + } else { + return [$container->make($class), $method]; + } + } + + /** + * Parse the class listener into class and method. + * + * @param string $listener + * @return array + */ + protected function parseClassCallable($listener) + { + $segments = explode('@', $listener); + + return [$segments[0], count($segments) == 2 ? $segments[1] : 'handle']; + } + + /** + * Determine if the event handler class should be queued. + * + * @param string $class + * @return bool + */ + protected function handlerShouldBeQueued($class) + { + try { + return (new ReflectionClass($class))->implementsInterface( + 'Illuminate\Contracts\Queue\ShouldQueue' + ); + } catch (Exception $e) { + return false; + } + } + + /** + * Create a callable for putting an event handler on the queue. + * + * @param string $class + * @param string $method + * @return \Closure + */ + protected function createQueuedHandlerCallable($class, $method) + { + return function () use ($class, $method) { + $arguments = $this->cloneArgumentsForQueueing(func_get_args()); + + if (method_exists($class, 'queue')) { + $this->callQueueMethodOnHandler($class, $method, $arguments); + } else { + $this->resolveQueue()->push('Illuminate\Events\CallQueuedHandler@call', [ + 'class' => $class, 'method' => $method, 'data' => serialize($arguments), + ]); + } + }; + } + + /** + * Clone the given arguments for queueing. + * + * @param array $arguments + * @return array + */ + protected function cloneArgumentsForQueueing(array $arguments) + { + return array_map(function ($a) { + return is_object($a) ? clone $a : $a; + }, $arguments); + } + + /** + * Call the queue method on the handler class. + * + * @param string $class + * @param string $method + * @param array $arguments + * @return void + */ + protected function callQueueMethodOnHandler($class, $method, $arguments) + { + $handler = (new ReflectionClass($class))->newInstanceWithoutConstructor(); + + $handler->queue($this->resolveQueue(), 'Illuminate\Events\CallQueuedHandler@call', [ + 'class' => $class, 'method' => $method, 'data' => serialize($arguments), + ]); + } + + /** + * Remove a set of listeners from the dispatcher. + * + * @param string $event + * @return void + */ + public function forget($event) + { + if (Str::contains($event, '*')) { + unset($this->wildcards[$event]); + } else { + unset($this->listeners[$event], $this->sorted[$event]); + } + } + + /** + * Forget all of the pushed listeners. + * + * @return void + */ + public function forgetPushed() + { + foreach ($this->listeners as $key => $value) { + if (Str::endsWith($key, '_pushed')) { + $this->forget($key); + } + } + } + + /** + * Get the queue implementation from the resolver. + * + * @return \Illuminate\Contracts\Queue\Queue + */ + protected function resolveQueue() + { + return call_user_func($this->queueResolver); + } + + /** + * Set the queue resolver implementation. + * + * @param callable $resolver + * @return $this + */ + public function setQueueResolver(callable $resolver) + { + $this->queueResolver = $resolver; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php new file mode 100644 index 0000000..54c24cd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php @@ -0,0 +1,22 @@ +app->singleton('events', function ($app) { + return (new Dispatcher($app))->setQueueResolver(function () use ($app) { + return $app->make('Illuminate\Contracts\Queue\Factory'); + }); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Events/composer.json b/core/vendor/laravel/framework/src/Illuminate/Events/composer.json new file mode 100644 index 0000000..4cbe705 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Events/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/events", + "description": "The Illuminate Events package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/container": "5.1.*", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Filesystem/ClassFinder.php b/core/vendor/laravel/framework/src/Illuminate/Filesystem/ClassFinder.php new file mode 100644 index 0000000..84f2c24 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Filesystem/ClassFinder.php @@ -0,0 +1,145 @@ +in($directory)->name('*.php') as $file) { + $classes[] = $this->findClass($file->getRealPath()); + } + + return array_filter($classes); + } + + /** + * Extract the class name from the file at the given path. + * + * @param string $path + * @return string|null + */ + public function findClass($path) + { + $namespace = null; + + $tokens = token_get_all(file_get_contents($path)); + + foreach ($tokens as $key => $token) { + if ($this->tokenIsNamespace($token)) { + $namespace = $this->getNamespace($key + 2, $tokens); + } elseif ($this->tokenIsClassOrInterface($token)) { + return ltrim($namespace.'\\'.$this->getClass($key + 2, $tokens), '\\'); + } + } + } + + /** + * Find the namespace in the tokens starting at a given key. + * + * @param int $key + * @param array $tokens + * @return string|null + */ + protected function getNamespace($key, array $tokens) + { + $namespace = null; + + $tokenCount = count($tokens); + + for ($i = $key; $i < $tokenCount; $i++) { + if ($this->isPartOfNamespace($tokens[$i])) { + $namespace .= $tokens[$i][1]; + } elseif ($tokens[$i] == ';') { + return $namespace; + } + } + } + + /** + * Find the class in the tokens starting at a given key. + * + * @param int $key + * @param array $tokens + * @return string|null + */ + protected function getClass($key, array $tokens) + { + $class = null; + + $tokenCount = count($tokens); + + for ($i = $key; $i < $tokenCount; $i++) { + if ($this->isPartOfClass($tokens[$i])) { + $class .= $tokens[$i][1]; + } elseif ($this->isWhitespace($tokens[$i])) { + return $class; + } + } + } + + /** + * Determine if the given token is a namespace keyword. + * + * @param array|string $token + * @return bool + */ + protected function tokenIsNamespace($token) + { + return is_array($token) && $token[0] == T_NAMESPACE; + } + + /** + * Determine if the given token is a class or interface keyword. + * + * @param array|string $token + * @return bool + */ + protected function tokenIsClassOrInterface($token) + { + return is_array($token) && ($token[0] == T_CLASS || $token[0] == T_INTERFACE); + } + + /** + * Determine if the given token is part of the namespace. + * + * @param array|string $token + * @return bool + */ + protected function isPartOfNamespace($token) + { + return is_array($token) && ($token[0] == T_STRING || $token[0] == T_NS_SEPARATOR); + } + + /** + * Determine if the given token is part of the class. + * + * @param array|string $token + * @return bool + */ + protected function isPartOfClass($token) + { + return is_array($token) && $token[0] == T_STRING; + } + + /** + * Determine if the given token is whitespace. + * + * @param array|string $token + * @return bool + */ + protected function isWhitespace($token) + { + return is_array($token) && $token[0] == T_WHITESPACE; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php b/core/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php new file mode 100644 index 0000000..11eb46c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php @@ -0,0 +1,442 @@ +isFile($path)) { + return file_get_contents($path); + } + + throw new FileNotFoundException("File does not exist at path {$path}"); + } + + /** + * Get the returned value of a file. + * + * @param string $path + * @return mixed + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function getRequire($path) + { + if ($this->isFile($path)) { + return require $path; + } + + throw new FileNotFoundException("File does not exist at path {$path}"); + } + + /** + * Require the given file once. + * + * @param string $file + * @return mixed + */ + public function requireOnce($file) + { + require_once $file; + } + + /** + * Write the contents of a file. + * + * @param string $path + * @param string $contents + * @param bool $lock + * @return int + */ + public function put($path, $contents, $lock = false) + { + return file_put_contents($path, $contents, $lock ? LOCK_EX : 0); + } + + /** + * Prepend to a file. + * + * @param string $path + * @param string $data + * @return int + */ + public function prepend($path, $data) + { + if ($this->exists($path)) { + return $this->put($path, $data.$this->get($path)); + } + + return $this->put($path, $data); + } + + /** + * Append to a file. + * + * @param string $path + * @param string $data + * @return int + */ + public function append($path, $data) + { + return file_put_contents($path, $data, FILE_APPEND); + } + + /** + * Delete the file at a given path. + * + * @param string|array $paths + * @return bool + */ + public function delete($paths) + { + $paths = is_array($paths) ? $paths : func_get_args(); + + $success = true; + + foreach ($paths as $path) { + try { + if (! @unlink($path)) { + $success = false; + } + } catch (ErrorException $e) { + $success = false; + } + } + + return $success; + } + + /** + * Move a file to a new location. + * + * @param string $path + * @param string $target + * @return bool + */ + public function move($path, $target) + { + return rename($path, $target); + } + + /** + * Copy a file to a new location. + * + * @param string $path + * @param string $target + * @return bool + */ + public function copy($path, $target) + { + return copy($path, $target); + } + + /** + * Extract the file name from a file path. + * + * @param string $path + * @return string + */ + public function name($path) + { + return pathinfo($path, PATHINFO_FILENAME); + } + + /** + * Extract the file extension from a file path. + * + * @param string $path + * @return string + */ + public function extension($path) + { + return pathinfo($path, PATHINFO_EXTENSION); + } + + /** + * Get the file type of a given file. + * + * @param string $path + * @return string + */ + public function type($path) + { + return filetype($path); + } + + /** + * Get the mime-type of a given file. + * + * @param string $path + * @return string|false + */ + public function mimeType($path) + { + return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path); + } + + /** + * Get the file size of a given file. + * + * @param string $path + * @return int + */ + public function size($path) + { + return filesize($path); + } + + /** + * Get the file's last modification time. + * + * @param string $path + * @return int + */ + public function lastModified($path) + { + return filemtime($path); + } + + /** + * Determine if the given path is a directory. + * + * @param string $directory + * @return bool + */ + public function isDirectory($directory) + { + return is_dir($directory); + } + + /** + * Determine if the given path is writable. + * + * @param string $path + * @return bool + */ + public function isWritable($path) + { + return is_writable($path); + } + + /** + * Determine if the given path is a file. + * + * @param string $file + * @return bool + */ + public function isFile($file) + { + return is_file($file); + } + + /** + * Find path names matching a given pattern. + * + * @param string $pattern + * @param int $flags + * @return array + */ + public function glob($pattern, $flags = 0) + { + return glob($pattern, $flags); + } + + /** + * Get an array of all files in a directory. + * + * @param string $directory + * @return array + */ + public function files($directory) + { + $glob = glob($directory.'/*'); + + if ($glob === false) { + return []; + } + + // To get the appropriate files, we'll simply glob the directory and filter + // out any "files" that are not truly files so we do not end up with any + // directories in our list, but only true files within the directory. + return array_filter($glob, function ($file) { + return filetype($file) == 'file'; + }); + } + + /** + * Get all of the files from the given directory (recursive). + * + * @param string $directory + * @return array + */ + public function allFiles($directory) + { + return iterator_to_array(Finder::create()->files()->in($directory), false); + } + + /** + * Get all of the directories within a given directory. + * + * @param string $directory + * @return array + */ + public function directories($directory) + { + $directories = []; + + foreach (Finder::create()->in($directory)->directories()->depth(0) as $dir) { + $directories[] = $dir->getPathname(); + } + + return $directories; + } + + /** + * Create a directory. + * + * @param string $path + * @param int $mode + * @param bool $recursive + * @param bool $force + * @return bool + */ + public function makeDirectory($path, $mode = 0755, $recursive = false, $force = false) + { + if ($force) { + return @mkdir($path, $mode, $recursive); + } + + return mkdir($path, $mode, $recursive); + } + + /** + * Copy a directory from one location to another. + * + * @param string $directory + * @param string $destination + * @param int $options + * @return bool + */ + public function copyDirectory($directory, $destination, $options = null) + { + if (! $this->isDirectory($directory)) { + return false; + } + + $options = $options ?: FilesystemIterator::SKIP_DOTS; + + // If the destination directory does not actually exist, we will go ahead and + // create it recursively, which just gets the destination prepared to copy + // the files over. Once we make the directory we'll proceed the copying. + if (! $this->isDirectory($destination)) { + $this->makeDirectory($destination, 0777, true); + } + + $items = new FilesystemIterator($directory, $options); + + foreach ($items as $item) { + // As we spin through items, we will check to see if the current file is actually + // a directory or a file. When it is actually a directory we will need to call + // back into this function recursively to keep copying these nested folders. + $target = $destination.'/'.$item->getBasename(); + + if ($item->isDir()) { + $path = $item->getPathname(); + + if (! $this->copyDirectory($path, $target, $options)) { + return false; + } + } + + // If the current items is just a regular file, we will just copy this to the new + // location and keep looping. If for some reason the copy fails we'll bail out + // and return false, so the developer is aware that the copy process failed. + else { + if (! $this->copy($item->getPathname(), $target)) { + return false; + } + } + } + + return true; + } + + /** + * Recursively delete a directory. + * + * The directory itself may be optionally preserved. + * + * @param string $directory + * @param bool $preserve + * @return bool + */ + public function deleteDirectory($directory, $preserve = false) + { + if (! $this->isDirectory($directory)) { + return false; + } + + $items = new FilesystemIterator($directory); + + foreach ($items as $item) { + // If the item is a directory, we can just recurse into the function and + // delete that sub-directory otherwise we'll just delete the file and + // keep iterating through each file until the directory is cleaned. + if ($item->isDir() && ! $item->isLink()) { + $this->deleteDirectory($item->getPathname()); + } + + // If the item is just a file, we can go ahead and delete it since we're + // just looping through and waxing all of the files in this directory + // and calling directories recursively, so we delete the real path. + else { + $this->delete($item->getPathname()); + } + } + + if (! $preserve) { + @rmdir($directory); + } + + return true; + } + + /** + * Empty the specified directory of all files and folders. + * + * @param string $directory + * @return bool + */ + public function cleanDirectory($directory) + { + return $this->deleteDirectory($directory, true); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php b/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php new file mode 100644 index 0000000..bb92370 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php @@ -0,0 +1,352 @@ +driver = $driver; + } + + /** + * Determine if a file exists. + * + * @param string $path + * @return bool + */ + public function exists($path) + { + return $this->driver->has($path); + } + + /** + * Get the contents of a file. + * + * @param string $path + * @return string + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function get($path) + { + try { + return $this->driver->read($path); + } catch (FileNotFoundException $e) { + throw new ContractFileNotFoundException($path, $e->getCode(), $e); + } + } + + /** + * Write the contents of a file. + * + * @param string $path + * @param string|resource $contents + * @param string $visibility + * @return bool + */ + public function put($path, $contents, $visibility = null) + { + if ($visibility = $this->parseVisibility($visibility)) { + $config = ['visibility' => $visibility]; + } else { + $config = []; + } + + if (is_resource($contents)) { + return $this->driver->putStream($path, $contents, $config); + } else { + return $this->driver->put($path, $contents, $config); + } + } + + /** + * Get the visibility for the given path. + * + * @param string $path + * @return string + */ + public function getVisibility($path) + { + if ($this->driver->getVisibility($path) == AdapterInterface::VISIBILITY_PUBLIC) { + return FilesystemContract::VISIBILITY_PUBLIC; + } + + return FilesystemContract::VISIBILITY_PRIVATE; + } + + /** + * Set the visibility for the given path. + * + * @param string $path + * @param string $visibility + * @return void + */ + public function setVisibility($path, $visibility) + { + return $this->driver->setVisibility($path, $this->parseVisibility($visibility)); + } + + /** + * Prepend to a file. + * + * @param string $path + * @param string $data + * @return int + */ + public function prepend($path, $data) + { + if ($this->exists($path)) { + return $this->put($path, $data.PHP_EOL.$this->get($path)); + } + + return $this->put($path, $data); + } + + /** + * Append to a file. + * + * @param string $path + * @param string $data + * @return int + */ + public function append($path, $data) + { + if ($this->exists($path)) { + return $this->put($path, $this->get($path).PHP_EOL.$data); + } + + return $this->put($path, $data); + } + + /** + * Delete the file at a given path. + * + * @param string|array $paths + * @return bool + */ + public function delete($paths) + { + $paths = is_array($paths) ? $paths : func_get_args(); + + foreach ($paths as $path) { + $this->driver->delete($path); + } + + return true; + } + + /** + * Copy a file to a new location. + * + * @param string $from + * @param string $to + * @return bool + */ + public function copy($from, $to) + { + return $this->driver->copy($from, $to); + } + + /** + * Move a file to a new location. + * + * @param string $from + * @param string $to + * @return bool + */ + public function move($from, $to) + { + return $this->driver->rename($from, $to); + } + + /** + * Get the file size of a given file. + * + * @param string $path + * @return int + */ + public function size($path) + { + return $this->driver->getSize($path); + } + + /** + * Get the mime-type of a given file. + * + * @param string $path + * @return string|false + */ + public function mimeType($path) + { + return $this->driver->getMimetype($path); + } + + /** + * Get the file's last modification time. + * + * @param string $path + * @return int + */ + public function lastModified($path) + { + return $this->driver->getTimestamp($path); + } + + /** + * Get an array of all files in a directory. + * + * @param string|null $directory + * @param bool $recursive + * @return array + */ + public function files($directory = null, $recursive = false) + { + $contents = $this->driver->listContents($directory, $recursive); + + return $this->filterContentsByType($contents, 'file'); + } + + /** + * Get all of the files from the given directory (recursive). + * + * @param string|null $directory + * @return array + */ + public function allFiles($directory = null) + { + return $this->files($directory, true); + } + + /** + * Get all of the directories within a given directory. + * + * @param string|null $directory + * @param bool $recursive + * @return array + */ + public function directories($directory = null, $recursive = false) + { + $contents = $this->driver->listContents($directory, $recursive); + + return $this->filterContentsByType($contents, 'dir'); + } + + /** + * Get all (recursive) of the directories within a given directory. + * + * @param string|null $directory + * @return array + */ + public function allDirectories($directory = null) + { + return $this->directories($directory, true); + } + + /** + * Create a directory. + * + * @param string $path + * @return bool + */ + public function makeDirectory($path) + { + return $this->driver->createDir($path); + } + + /** + * Recursively delete a directory. + * + * @param string $directory + * @return bool + */ + public function deleteDirectory($directory) + { + return $this->driver->deleteDir($directory); + } + + /** + * Get the Flysystem driver. + * + * @return \League\Flysystem\FilesystemInterface + */ + public function getDriver() + { + return $this->driver; + } + + /** + * Filter directory contents by type. + * + * @param array $contents + * @param string $type + * @return array + */ + protected function filterContentsByType($contents, $type) + { + return Collection::make($contents) + ->where('type', $type) + ->pluck('path') + ->values() + ->all(); + } + + /** + * Parse the given visibility value. + * + * @param string|null $visibility + * @return string|null + * @throws \InvalidArgumentException + */ + protected function parseVisibility($visibility) + { + if (is_null($visibility)) { + return; + } + + switch ($visibility) { + case FilesystemContract::VISIBILITY_PUBLIC: + return AdapterInterface::VISIBILITY_PUBLIC; + case FilesystemContract::VISIBILITY_PRIVATE: + return AdapterInterface::VISIBILITY_PRIVATE; + } + + throw new InvalidArgumentException('Unknown visibility: '.$visibility); + } + + /** + * Pass dynamic methods call onto Flysystem. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, array $parameters) + { + return call_user_func_array([$this->driver, $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemManager.php b/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemManager.php new file mode 100644 index 0000000..961c624 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemManager.php @@ -0,0 +1,304 @@ +app = $app; + } + + /** + * Get a filesystem instance. + * + * @param string $name + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + public function drive($name = null) + { + return $this->disk($name); + } + + /** + * Get a filesystem instance. + * + * @param string $name + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + public function disk($name = null) + { + $name = $name ?: $this->getDefaultDriver(); + + return $this->disks[$name] = $this->get($name); + } + + /** + * Attempt to get the disk from the local cache. + * + * @param string $name + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + protected function get($name) + { + return isset($this->disks[$name]) ? $this->disks[$name] : $this->resolve($name); + } + + /** + * Resolve the given disk. + * + * @param string $name + * @return \Illuminate\Contracts\Filesystem\Filesystem + * + * @throws \InvalidArgumentException + */ + protected function resolve($name) + { + $config = $this->getConfig($name); + + if (isset($this->customCreators[$config['driver']])) { + return $this->callCustomCreator($config); + } + + $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; + + if (method_exists($this, $driverMethod)) { + return $this->{$driverMethod}($config); + } else { + throw new InvalidArgumentException("Driver [{$config['driver']}] not supported."); + } + } + + /** + * Call a custom driver creator. + * + * @param array $config + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + protected function callCustomCreator(array $config) + { + $driver = $this->customCreators[$config['driver']]($this->app, $config); + + if ($driver instanceof FilesystemInterface) { + return $this->adapt($driver); + } + + return $driver; + } + + /** + * Create an instance of the local driver. + * + * @param array $config + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + public function createLocalDriver(array $config) + { + $permissions = isset($config['permissions']) ? $config['permissions'] : []; + + $links = Arr::get($config, 'links') === 'skip' + ? LocalAdapter::SKIP_LINKS + : LocalAdapter::DISALLOW_LINKS; + + return $this->adapt($this->createFlysystem(new LocalAdapter( + $config['root'], LOCK_EX, $links, $permissions + ), $config)); + } + + /** + * Create an instance of the ftp driver. + * + * @param array $config + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + public function createFtpDriver(array $config) + { + $ftpConfig = Arr::only($config, [ + 'host', 'username', 'password', 'port', 'root', 'passive', 'ssl', 'timeout', + ]); + + return $this->adapt($this->createFlysystem( + new FtpAdapter($ftpConfig), $config + )); + } + + /** + * Create an instance of the Amazon S3 driver. + * + * @param array $config + * @return \Illuminate\Contracts\Filesystem\Cloud + */ + public function createS3Driver(array $config) + { + $s3Config = $this->formatS3Config($config); + + $root = isset($s3Config['root']) ? $s3Config['root'] : null; + + return $this->adapt($this->createFlysystem( + new S3Adapter(new S3Client($s3Config), $s3Config['bucket'], $root), $config + )); + } + + /** + * Format the given S3 configuration with the default options. + * + * @param array $config + * @return array + */ + protected function formatS3Config(array $config) + { + $config += ['version' => 'latest']; + + if ($config['key'] && $config['secret']) { + $config['credentials'] = Arr::only($config, ['key', 'secret']); + } + + return $config; + } + + /** + * Create an instance of the Rackspace driver. + * + * @param array $config + * @return \Illuminate\Contracts\Filesystem\Cloud + */ + public function createRackspaceDriver(array $config) + { + $client = new Rackspace($config['endpoint'], [ + 'username' => $config['username'], 'apiKey' => $config['key'], + ]); + + return $this->adapt($this->createFlysystem( + new RackspaceAdapter($this->getRackspaceContainer($client, $config)), $config + )); + } + + /** + * Get the Rackspace Cloud Files container. + * + * @param \OpenCloud\Rackspace $client + * @param array $config + * @return \OpenCloud\ObjectStore\Resource\Container + */ + protected function getRackspaceContainer(Rackspace $client, array $config) + { + $urlType = Arr::get($config, 'url_type'); + + $store = $client->objectStoreService('cloudFiles', $config['region'], $urlType); + + return $store->getContainer($config['container']); + } + + /** + * Create a Flysystem instance with the given adapter. + * + * @param \League\Flysystem\AdapterInterface $adapter + * @param array $config + * @return \League\Flysystem\FlysystemInterface + */ + protected function createFlysystem(AdapterInterface $adapter, array $config) + { + $config = Arr::only($config, ['visibility', 'disable_asserts']); + + return new Flysystem($adapter, count($config) > 0 ? $config : null); + } + + /** + * Adapt the filesystem implementation. + * + * @param \League\Flysystem\FilesystemInterface $filesystem + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + protected function adapt(FilesystemInterface $filesystem) + { + return new FilesystemAdapter($filesystem); + } + + /** + * Get the filesystem connection configuration. + * + * @param string $name + * @return array + */ + protected function getConfig($name) + { + return $this->app['config']["filesystems.disks.{$name}"]; + } + + /** + * Get the default driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['filesystems.default']; + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * @return $this + */ + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback; + + return $this; + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->disk(), $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemServiceProvider.php new file mode 100644 index 0000000..6932270 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemServiceProvider.php @@ -0,0 +1,82 @@ +registerNativeFilesystem(); + + $this->registerFlysystem(); + } + + /** + * Register the native filesystem implementation. + * + * @return void + */ + protected function registerNativeFilesystem() + { + $this->app->singleton('files', function () { + return new Filesystem; + }); + } + + /** + * Register the driver based filesystem. + * + * @return void + */ + protected function registerFlysystem() + { + $this->registerManager(); + + $this->app->singleton('filesystem.disk', function () { + return $this->app['filesystem']->disk($this->getDefaultDriver()); + }); + + $this->app->singleton('filesystem.cloud', function () { + return $this->app['filesystem']->disk($this->getCloudDriver()); + }); + } + + /** + * Register the filesystem manager. + * + * @return void + */ + protected function registerManager() + { + $this->app->singleton('filesystem', function () { + return new FilesystemManager($this->app); + }); + } + + /** + * Get the default file driver. + * + * @return string + */ + protected function getDefaultDriver() + { + return $this->app['config']['filesystems.default']; + } + + /** + * Get the default cloud based file driver. + * + * @return string + */ + protected function getCloudDriver() + { + return $this->app['config']['filesystems.cloud']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Filesystem/composer.json b/core/vendor/laravel/framework/src/Illuminate/Filesystem/composer.json new file mode 100644 index 0000000..a00fa40 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Filesystem/composer.json @@ -0,0 +1,38 @@ +{ + "name": "illuminate/filesystem", + "description": "The Illuminate Filesystem package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/finder": "2.7.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Filesystem\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "league/flysystem": "Required to use the Flysystem local and FTP drivers (~1.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php new file mode 100644 index 0000000..c0fabc7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php @@ -0,0 +1,168 @@ +aliases = $aliases; + } + + /** + * Get or create the singleton alias loader instance. + * + * @param array $aliases + * @return \Illuminate\Foundation\AliasLoader + */ + public static function getInstance(array $aliases = []) + { + if (is_null(static::$instance)) { + return static::$instance = new static($aliases); + } + + $aliases = array_merge(static::$instance->getAliases(), $aliases); + + static::$instance->setAliases($aliases); + + return static::$instance; + } + + /** + * Load a class alias if it is registered. + * + * @param string $alias + * @return bool|null + */ + public function load($alias) + { + if (isset($this->aliases[$alias])) { + return class_alias($this->aliases[$alias], $alias); + } + } + + /** + * Add an alias to the loader. + * + * @param string $class + * @param string $alias + * @return void + */ + public function alias($class, $alias) + { + $this->aliases[$class] = $alias; + } + + /** + * Register the loader on the auto-loader stack. + * + * @return void + */ + public function register() + { + if (! $this->registered) { + $this->prependToLoaderStack(); + + $this->registered = true; + } + } + + /** + * Prepend the load method to the auto-loader stack. + * + * @return void + */ + protected function prependToLoaderStack() + { + spl_autoload_register([$this, 'load'], true, true); + } + + /** + * Get the registered aliases. + * + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Set the registered aliases. + * + * @param array $aliases + * @return void + */ + public function setAliases(array $aliases) + { + $this->aliases = $aliases; + } + + /** + * Indicates if the loader has been registered. + * + * @return bool + */ + public function isRegistered() + { + return $this->registered; + } + + /** + * Set the "registered" state of the loader. + * + * @param bool $value + * @return void + */ + public function setRegistered($value) + { + $this->registered = $value; + } + + /** + * Set the value of the singleton alias loader. + * + * @param \Illuminate\Foundation\AliasLoader $loader + * @return void + */ + public static function setInstance($loader) + { + static::$instance = $loader; + } + + /** + * Clone method. + * + * @return void + */ + private function __clone() + { + // + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Application.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Application.php new file mode 100644 index 0000000..b890434 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Application.php @@ -0,0 +1,1122 @@ +registerBaseBindings(); + + $this->registerBaseServiceProviders(); + + $this->registerCoreContainerAliases(); + + if ($basePath) { + $this->setBasePath($basePath); + } + } + + /** + * Get the version number of the application. + * + * @return string + */ + public function version() + { + return static::VERSION; + } + + /** + * Register the basic bindings into the container. + * + * @return void + */ + protected function registerBaseBindings() + { + static::setInstance($this); + + $this->instance('app', $this); + + $this->instance('Illuminate\Container\Container', $this); + } + + /** + * Register all of the base service providers. + * + * @return void + */ + protected function registerBaseServiceProviders() + { + $this->register(new EventServiceProvider($this)); + + $this->register(new RoutingServiceProvider($this)); + } + + /** + * Run the given array of bootstrap classes. + * + * @param array $bootstrappers + * @return void + */ + public function bootstrapWith(array $bootstrappers) + { + $this->hasBeenBootstrapped = true; + + foreach ($bootstrappers as $bootstrapper) { + $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]); + + $this->make($bootstrapper)->bootstrap($this); + + $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); + } + } + + /** + * Register a callback to run after loading the environment. + * + * @param \Closure $callback + * @return void + */ + public function afterLoadingEnvironment(Closure $callback) + { + return $this->afterBootstrapping( + 'Illuminate\Foundation\Bootstrap\DetectEnvironment', $callback + ); + } + + /** + * Register a callback to run before a bootstrapper. + * + * @param string $bootstrapper + * @param Closure $callback + * @return void + */ + public function beforeBootstrapping($bootstrapper, Closure $callback) + { + $this['events']->listen('bootstrapping: '.$bootstrapper, $callback); + } + + /** + * Register a callback to run after a bootstrapper. + * + * @param string $bootstrapper + * @param Closure $callback + * @return void + */ + public function afterBootstrapping($bootstrapper, Closure $callback) + { + $this['events']->listen('bootstrapped: '.$bootstrapper, $callback); + } + + /** + * Determine if the application has been bootstrapped before. + * + * @return bool + */ + public function hasBeenBootstrapped() + { + return $this->hasBeenBootstrapped; + } + + /** + * Set the base path for the application. + * + * @param string $basePath + * @return $this + */ + public function setBasePath($basePath) + { + $this->basePath = rtrim($basePath, '\/'); + + $this->bindPathsInContainer(); + + return $this; + } + + /** + * Bind all of the application paths in the container. + * + * @return void + */ + protected function bindPathsInContainer() + { + $this->instance('path', $this->path()); + + foreach (['base', 'config', 'database', 'lang', 'public', 'storage'] as $path) { + $this->instance('path.'.$path, $this->{$path.'Path'}()); + } + } + + /** + * Get the path to the application "app" directory. + * + * @return string + */ + public function path() + { + return $this->basePath.DIRECTORY_SEPARATOR.'app'; + } + + /** + * Get the base path of the Laravel installation. + * + * @return string + */ + public function basePath() + { + return $this->basePath; + } + + /** + * Get the path to the application configuration files. + * + * @return string + */ + public function configPath() + { + return $this->basePath.DIRECTORY_SEPARATOR.'config'; + } + + /** + * Get the path to the database directory. + * + * @return string + */ + public function databasePath() + { + return $this->databasePath ?: $this->basePath.DIRECTORY_SEPARATOR.'database'; + } + + /** + * Set the database directory. + * + * @param string $path + * @return $this + */ + public function useDatabasePath($path) + { + $this->databasePath = $path; + + $this->instance('path.database', $path); + + return $this; + } + + /** + * Get the path to the language files. + * + * @return string + */ + public function langPath() + { + return $this->basePath.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'lang'; + } + + /** + * Get the path to the public / web directory. + * + * @return string + */ + public function publicPath() + { + return $this->basePath.DIRECTORY_SEPARATOR.'public'; + } + + /** + * Get the path to the storage directory. + * + * @return string + */ + public function storagePath() + { + return $this->storagePath ?: $this->basePath.DIRECTORY_SEPARATOR.'storage'; + } + + /** + * Set the storage directory. + * + * @param string $path + * @return $this + */ + public function useStoragePath($path) + { + $this->storagePath = $path; + + $this->instance('path.storage', $path); + + return $this; + } + + /** + * Get the path to the environment file directory. + * + * @return string + */ + public function environmentPath() + { + return $this->environmentPath ?: $this->basePath; + } + + /** + * Set the directory for the environment file. + * + * @param string $path + * @return $this + */ + public function useEnvironmentPath($path) + { + $this->environmentPath = $path; + + return $this; + } + + /** + * Set the environment file to be loaded during bootstrapping. + * + * @param string $file + * @return $this + */ + public function loadEnvironmentFrom($file) + { + $this->environmentFile = $file; + + return $this; + } + + /** + * Get the environment file the application is using. + * + * @return string + */ + public function environmentFile() + { + return $this->environmentFile ?: '.env'; + } + + /** + * Get or check the current application environment. + * + * @param mixed + * @return string + */ + public function environment() + { + if (func_num_args() > 0) { + $patterns = is_array(func_get_arg(0)) ? func_get_arg(0) : func_get_args(); + + foreach ($patterns as $pattern) { + if (Str::is($pattern, $this['env'])) { + return true; + } + } + + return false; + } + + return $this['env']; + } + + /** + * Determine if application is in local environment. + * + * @return bool + */ + public function isLocal() + { + return $this['env'] == 'local'; + } + + /** + * Detect the application's current environment. + * + * @param \Closure $callback + * @return string + */ + public function detectEnvironment(Closure $callback) + { + $args = isset($_SERVER['argv']) ? $_SERVER['argv'] : null; + + return $this['env'] = (new EnvironmentDetector())->detect($callback, $args); + } + + /** + * Determine if we are running in the console. + * + * @return bool + */ + public function runningInConsole() + { + return php_sapi_name() == 'cli'; + } + + /** + * Determine if we are running unit tests. + * + * @return bool + */ + public function runningUnitTests() + { + return $this['env'] == 'testing'; + } + + /** + * Register all of the configured providers. + * + * @return void + */ + public function registerConfiguredProviders() + { + $manifestPath = $this->getCachedServicesPath(); + + (new ProviderRepository($this, new Filesystem, $manifestPath)) + ->load($this->config['app.providers']); + } + + /** + * Register a service provider with the application. + * + * @param \Illuminate\Support\ServiceProvider|string $provider + * @param array $options + * @param bool $force + * @return \Illuminate\Support\ServiceProvider + */ + public function register($provider, $options = [], $force = false) + { + if (($registered = $this->getProvider($provider)) && ! $force) { + return $registered; + } + + // If the given "provider" is a string, we will resolve it, passing in the + // application instance automatically for the developer. This is simply + // a more convenient way of specifying your service provider classes. + if (is_string($provider)) { + $provider = $this->resolveProviderClass($provider); + } + + $provider->register(); + + // Once we have registered the service we will iterate through the options + // and set each of them on the application so they will be available on + // the actual loading of the service objects and for developer usage. + foreach ($options as $key => $value) { + $this[$key] = $value; + } + + $this->markAsRegistered($provider); + + // If the application has already booted, we will call this boot method on + // the provider class so it has an opportunity to do its boot logic and + // will be ready for any usage by the developer's application logics. + if ($this->booted) { + $this->bootProvider($provider); + } + + return $provider; + } + + /** + * Get the registered service provider instance if it exists. + * + * @param \Illuminate\Support\ServiceProvider|string $provider + * @return \Illuminate\Support\ServiceProvider|null + */ + public function getProvider($provider) + { + $name = is_string($provider) ? $provider : get_class($provider); + + return Arr::first($this->serviceProviders, function ($key, $value) use ($name) { + return $value instanceof $name; + }); + } + + /** + * Resolve a service provider instance from the class name. + * + * @param string $provider + * @return \Illuminate\Support\ServiceProvider + */ + public function resolveProviderClass($provider) + { + return new $provider($this); + } + + /** + * Mark the given provider as registered. + * + * @param \Illuminate\Support\ServiceProvider $provider + * @return void + */ + protected function markAsRegistered($provider) + { + $this['events']->fire($class = get_class($provider), [$provider]); + + $this->serviceProviders[] = $provider; + + $this->loadedProviders[$class] = true; + } + + /** + * Load and boot all of the remaining deferred providers. + * + * @return void + */ + public function loadDeferredProviders() + { + // We will simply spin through each of the deferred providers and register each + // one and boot them if the application has booted. This should make each of + // the remaining services available to this application for immediate use. + foreach ($this->deferredServices as $service => $provider) { + $this->loadDeferredProvider($service); + } + + $this->deferredServices = []; + } + + /** + * Load the provider for a deferred service. + * + * @param string $service + * @return void + */ + public function loadDeferredProvider($service) + { + if (! isset($this->deferredServices[$service])) { + return; + } + + $provider = $this->deferredServices[$service]; + + // If the service provider has not already been loaded and registered we can + // register it with the application and remove the service from this list + // of deferred services, since it will already be loaded on subsequent. + if (! isset($this->loadedProviders[$provider])) { + $this->registerDeferredProvider($provider, $service); + } + } + + /** + * Register a deferred provider and service. + * + * @param string $provider + * @param string $service + * @return void + */ + public function registerDeferredProvider($provider, $service = null) + { + // Once the provider that provides the deferred service has been registered we + // will remove it from our local list of the deferred services with related + // providers so that this container does not try to resolve it out again. + if ($service) { + unset($this->deferredServices[$service]); + } + + $this->register($instance = new $provider($this)); + + if (! $this->booted) { + $this->booting(function () use ($instance) { + $this->bootProvider($instance); + }); + } + } + + /** + * Resolve the given type from the container. + * + * (Overriding Container::make) + * + * @param string $abstract + * @param array $parameters + * @return mixed + */ + public function make($abstract, array $parameters = []) + { + $abstract = $this->getAlias($abstract); + + if (isset($this->deferredServices[$abstract])) { + $this->loadDeferredProvider($abstract); + } + + return parent::make($abstract, $parameters); + } + + /** + * Determine if the given abstract type has been bound. + * + * (Overriding Container::bound) + * + * @param string $abstract + * @return bool + */ + public function bound($abstract) + { + return isset($this->deferredServices[$abstract]) || parent::bound($abstract); + } + + /** + * Determine if the application has booted. + * + * @return bool + */ + public function isBooted() + { + return $this->booted; + } + + /** + * Boot the application's service providers. + * + * @return void + */ + public function boot() + { + if ($this->booted) { + return; + } + + // Once the application has booted we will also fire some "booted" callbacks + // for any listeners that need to do work after this initial booting gets + // finished. This is useful when ordering the boot-up processes we run. + $this->fireAppCallbacks($this->bootingCallbacks); + + array_walk($this->serviceProviders, function ($p) { + $this->bootProvider($p); + }); + + $this->booted = true; + + $this->fireAppCallbacks($this->bootedCallbacks); + } + + /** + * Boot the given service provider. + * + * @param \Illuminate\Support\ServiceProvider $provider + * @return mixed + */ + protected function bootProvider(ServiceProvider $provider) + { + if (method_exists($provider, 'boot')) { + return $this->call([$provider, 'boot']); + } + } + + /** + * Register a new boot listener. + * + * @param mixed $callback + * @return void + */ + public function booting($callback) + { + $this->bootingCallbacks[] = $callback; + } + + /** + * Register a new "booted" listener. + * + * @param mixed $callback + * @return void + */ + public function booted($callback) + { + $this->bootedCallbacks[] = $callback; + + if ($this->isBooted()) { + $this->fireAppCallbacks([$callback]); + } + } + + /** + * Call the booting callbacks for the application. + * + * @param array $callbacks + * @return void + */ + protected function fireAppCallbacks(array $callbacks) + { + foreach ($callbacks as $callback) { + call_user_func($callback, $this); + } + } + + /** + * {@inheritdoc} + */ + public function handle(SymfonyRequest $request, $type = self::MASTER_REQUEST, $catch = true) + { + return $this['Illuminate\Contracts\Http\Kernel']->handle(Request::createFromBase($request)); + } + + /** + * Determine if middleware has been disabled for the application. + * + * @return bool + */ + public function shouldSkipMiddleware() + { + return $this->bound('middleware.disable') && + $this->make('middleware.disable') === true; + } + + /** + * Determine if the application configuration is cached. + * + * @return bool + */ + public function configurationIsCached() + { + return $this['files']->exists($this->getCachedConfigPath()); + } + + /** + * Get the path to the configuration cache file. + * + * @return string + */ + public function getCachedConfigPath() + { + return $this->basePath().'/bootstrap/cache/config.php'; + } + + /** + * Determine if the application routes are cached. + * + * @return bool + */ + public function routesAreCached() + { + return $this['files']->exists($this->getCachedRoutesPath()); + } + + /** + * Get the path to the routes cache file. + * + * @return string + */ + public function getCachedRoutesPath() + { + return $this->basePath().'/bootstrap/cache/routes.php'; + } + + /** + * Get the path to the cached "compiled.php" file. + * + * @return string + */ + public function getCachedCompilePath() + { + return $this->basePath().'/bootstrap/cache/compiled.php'; + } + + /** + * Get the path to the cached services.json file. + * + * @return string + */ + public function getCachedServicesPath() + { + return $this->basePath().'/bootstrap/cache/services.json'; + } + + /** + * Determine if the application is currently down for maintenance. + * + * @return bool + */ + public function isDownForMaintenance() + { + return file_exists($this->storagePath().'/framework/down'); + } + + /** + * Throw an HttpException with the given data. + * + * @param int $code + * @param string $message + * @param array $headers + * @return void + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + */ + public function abort($code, $message = '', array $headers = []) + { + if ($code == 404) { + throw new NotFoundHttpException($message); + } + + throw new HttpException($code, $message, null, $headers); + } + + /** + * Register a terminating callback with the application. + * + * @param \Closure $callback + * @return $this + */ + public function terminating(Closure $callback) + { + $this->terminatingCallbacks[] = $callback; + + return $this; + } + + /** + * Terminate the application. + * + * @return void + */ + public function terminate() + { + foreach ($this->terminatingCallbacks as $terminating) { + $this->call($terminating); + } + } + + /** + * Get the service providers that have been loaded. + * + * @return array + */ + public function getLoadedProviders() + { + return $this->loadedProviders; + } + + /** + * Get the application's deferred services. + * + * @return array + */ + public function getDeferredServices() + { + return $this->deferredServices; + } + + /** + * Set the application's deferred services. + * + * @param array $services + * @return void + */ + public function setDeferredServices(array $services) + { + $this->deferredServices = $services; + } + + /** + * Add an array of services to the application's deferred services. + * + * @param array $services + * @return void + */ + public function addDeferredServices(array $services) + { + $this->deferredServices = array_merge($this->deferredServices, $services); + } + + /** + * Determine if the given service is a deferred service. + * + * @param string $service + * @return bool + */ + public function isDeferredService($service) + { + return isset($this->deferredServices[$service]); + } + + /** + * Define a callback to be used to configure Monolog. + * + * @param callable $callback + * @return $this + */ + public function configureMonologUsing(callable $callback) + { + $this->monologConfigurator = $callback; + + return $this; + } + + /** + * Determine if the application has a custom Monolog configurator. + * + * @return bool + */ + public function hasMonologConfigurator() + { + return ! is_null($this->monologConfigurator); + } + + /** + * Get the custom Monolog configurator for the application. + * + * @return callable + */ + public function getMonologConfigurator() + { + return $this->monologConfigurator; + } + + /** + * Get the current application locale. + * + * @return string + */ + public function getLocale() + { + return $this['config']->get('app.locale'); + } + + /** + * Set the current application locale. + * + * @param string $locale + * @return void + */ + public function setLocale($locale) + { + $this['config']->set('app.locale', $locale); + + $this['translator']->setLocale($locale); + + $this['events']->fire('locale.changed', [$locale]); + } + + /** + * Register the core class aliases in the container. + * + * @return void + */ + public function registerCoreContainerAliases() + { + $aliases = [ + 'app' => ['Illuminate\Foundation\Application', 'Illuminate\Contracts\Container\Container', 'Illuminate\Contracts\Foundation\Application'], + 'auth' => 'Illuminate\Auth\AuthManager', + 'auth.driver' => ['Illuminate\Auth\Guard', 'Illuminate\Contracts\Auth\Guard'], + 'auth.password.tokens' => 'Illuminate\Auth\Passwords\TokenRepositoryInterface', + 'blade.compiler' => 'Illuminate\View\Compilers\BladeCompiler', + 'cache' => ['Illuminate\Cache\CacheManager', 'Illuminate\Contracts\Cache\Factory'], + 'cache.store' => ['Illuminate\Cache\Repository', 'Illuminate\Contracts\Cache\Repository'], + 'config' => ['Illuminate\Config\Repository', 'Illuminate\Contracts\Config\Repository'], + 'cookie' => ['Illuminate\Cookie\CookieJar', 'Illuminate\Contracts\Cookie\Factory', 'Illuminate\Contracts\Cookie\QueueingFactory'], + 'encrypter' => ['Illuminate\Encryption\Encrypter', 'Illuminate\Contracts\Encryption\Encrypter'], + 'db' => 'Illuminate\Database\DatabaseManager', + 'db.connection' => ['Illuminate\Database\Connection', 'Illuminate\Database\ConnectionInterface'], + 'events' => ['Illuminate\Events\Dispatcher', 'Illuminate\Contracts\Events\Dispatcher'], + 'files' => 'Illuminate\Filesystem\Filesystem', + 'filesystem' => ['Illuminate\Filesystem\FilesystemManager', 'Illuminate\Contracts\Filesystem\Factory'], + 'filesystem.disk' => 'Illuminate\Contracts\Filesystem\Filesystem', + 'filesystem.cloud' => 'Illuminate\Contracts\Filesystem\Cloud', + 'hash' => 'Illuminate\Contracts\Hashing\Hasher', + 'translator' => ['Illuminate\Translation\Translator', 'Symfony\Component\Translation\TranslatorInterface'], + 'log' => ['Illuminate\Log\Writer', 'Illuminate\Contracts\Logging\Log', 'Psr\Log\LoggerInterface'], + 'mailer' => ['Illuminate\Mail\Mailer', 'Illuminate\Contracts\Mail\Mailer', 'Illuminate\Contracts\Mail\MailQueue'], + 'auth.password' => ['Illuminate\Auth\Passwords\PasswordBroker', 'Illuminate\Contracts\Auth\PasswordBroker'], + 'queue' => ['Illuminate\Queue\QueueManager', 'Illuminate\Contracts\Queue\Factory', 'Illuminate\Contracts\Queue\Monitor'], + 'queue.connection' => 'Illuminate\Contracts\Queue\Queue', + 'redirect' => 'Illuminate\Routing\Redirector', + 'redis' => ['Illuminate\Redis\Database', 'Illuminate\Contracts\Redis\Database'], + 'request' => 'Illuminate\Http\Request', + 'router' => ['Illuminate\Routing\Router', 'Illuminate\Contracts\Routing\Registrar'], + 'session' => 'Illuminate\Session\SessionManager', + 'session.store' => ['Illuminate\Session\Store', 'Symfony\Component\HttpFoundation\Session\SessionInterface'], + 'url' => ['Illuminate\Routing\UrlGenerator', 'Illuminate\Contracts\Routing\UrlGenerator'], + 'validator' => ['Illuminate\Validation\Factory', 'Illuminate\Contracts\Validation\Factory'], + 'view' => ['Illuminate\View\Factory', 'Illuminate\Contracts\View\Factory'], + ]; + + foreach ($aliases as $key => $aliases) { + foreach ((array) $aliases as $alias) { + $this->alias($key, $alias); + } + } + } + + /** + * Flush the container of all bindings and resolved instances. + * + * @return void + */ + public function flush() + { + parent::flush(); + + $this->loadedProviders = []; + } + + /** + * Get the used kernel object. + * + * @return \Illuminate\Contracts\Console\Kernel|\Illuminate\Contracts\Http\Kernel + */ + protected function getKernel() + { + $kernelContract = $this->runningInConsole() + ? 'Illuminate\Contracts\Console\Kernel' + : 'Illuminate\Contracts\Http\Kernel'; + + return $this->make($kernelContract); + } + + /** + * Get the application namespace. + * + * @return string + * + * @throws \RuntimeException + */ + public function getNamespace() + { + if (! is_null($this->namespace)) { + return $this->namespace; + } + + $composer = json_decode(file_get_contents(base_path('composer.json')), true); + + foreach ((array) data_get($composer, 'autoload.psr-4') as $namespace => $path) { + foreach ((array) $path as $pathChoice) { + if (realpath(app_path()) == realpath(base_path().'/'.$pathChoice)) { + return $this->namespace = $namespace; + } + } + } + + throw new RuntimeException('Unable to detect application namespace.'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/Access/Authorizable.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/Access/Authorizable.php new file mode 100644 index 0000000..9e8e33b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/Access/Authorizable.php @@ -0,0 +1,44 @@ +forUser($this)->check($ability, $arguments); + } + + /** + * Determine if the entity does not have a given ability. + * + * @param string $ability + * @param array|mixed $arguments + * @return bool + */ + public function cant($ability, $arguments = []) + { + return ! $this->can($ability, $arguments); + } + + /** + * Determine if the entity does not have a given ability. + * + * @param string $ability + * @param array|mixed $arguments + * @return bool + */ + public function cannot($ability, $arguments = []) + { + return $this->cant($ability, $arguments); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php new file mode 100644 index 0000000..e3908f2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php @@ -0,0 +1,96 @@ +parseAbilityAndArguments($ability, $arguments); + + return $this->authorizeAtGate(app(Gate::class), $ability, $arguments); + } + + /** + * Authorize a given action for a user. + * + * @param \Illuminate\Contracts\Auth\Authenticatable|mixed $user + * @param mixed $ability + * @param mixed|array $arguments + * @return \Illuminate\Auth\Access\Response + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + */ + public function authorizeForUser($user, $ability, $arguments = []) + { + list($ability, $arguments) = $this->parseAbilityAndArguments($ability, $arguments); + + $gate = app(Gate::class)->forUser($user); + + return $this->authorizeAtGate($gate, $ability, $arguments); + } + + /** + * Authorize the request at the given gate. + * + * @param \Illuminate\Contracts\Auth\Access\Gate $gate + * @param mixed $ability + * @param mixed|array $arguments + * @return \Illuminate\Auth\Access\Response + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + */ + public function authorizeAtGate(Gate $gate, $ability, $arguments) + { + try { + return $gate->authorize($ability, $arguments); + } catch (UnauthorizedException $e) { + throw $this->createGateUnauthorizedException( + $ability, $arguments, $e->getMessage(), $e + ); + } + } + + /** + * Guesses the ability's name if it wasn't provided. + * + * @param mixed $ability + * @param mixed|array $arguments + * @return array + */ + protected function parseAbilityAndArguments($ability, $arguments) + { + if (is_string($ability)) { + return [$ability, $arguments]; + } + + return [debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function'], $ability]; + } + + /** + * Throw an unauthorized exception based on gate results. + * + * @param string $ability + * @param mixed|array $arguments + * @param string $message + * @param \Exception $previousException + * @return \Symfony\Component\HttpKernel\Exception\HttpException + */ + protected function createGateUnauthorizedException($ability, $arguments, $message = 'This action is unauthorized.', $previousException = null) + { + return new HttpException(403, $message, $previousException); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesAndRegistersUsers.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesAndRegistersUsers.php new file mode 100644 index 0000000..32ce2c7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesAndRegistersUsers.php @@ -0,0 +1,10 @@ +exists('auth.authenticate')) { + return view('auth.authenticate'); + } + + return view('auth.login'); + } + + /** + * Handle a login request to the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function postLogin(Request $request) + { + $this->validate($request, [ + $this->loginUsername() => 'required', 'password' => 'required', + ]); + + // If the class is using the ThrottlesLogins trait, we can automatically throttle + // the login attempts for this application. We'll key this by the username and + // the IP address of the client making these requests into this application. + $throttles = $this->isUsingThrottlesLoginsTrait(); + + if ($throttles && $this->hasTooManyLoginAttempts($request)) { + return $this->sendLockoutResponse($request); + } + + $credentials = $this->getCredentials($request); + + if (Auth::attempt($credentials, $request->has('remember'))) { + return $this->handleUserWasAuthenticated($request, $throttles); + } + + // If the login attempt was unsuccessful we will increment the number of attempts + // to login and redirect the user back to the login form. Of course, when this + // user surpasses their maximum number of attempts they will get locked out. + if ($throttles) { + $this->incrementLoginAttempts($request); + } + + return redirect($this->loginPath()) + ->withInput($request->only($this->loginUsername(), 'remember')) + ->withErrors([ + $this->loginUsername() => $this->getFailedLoginMessage(), + ]); + } + + /** + * Send the response after the user was authenticated. + * + * @param \Illuminate\Http\Request $request + * @param bool $throttles + * @return \Illuminate\Http\Response + */ + protected function handleUserWasAuthenticated(Request $request, $throttles) + { + if ($throttles) { + $this->clearLoginAttempts($request); + } + + if (method_exists($this, 'authenticated')) { + return $this->authenticated($request, Auth::user()); + } + + return redirect()->intended($this->redirectPath()); + } + + /** + * Get the needed authorization credentials from the request. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function getCredentials(Request $request) + { + return $request->only($this->loginUsername(), 'password'); + } + + /** + * Get the failed login message. + * + * @return string + */ + protected function getFailedLoginMessage() + { + return Lang::has('auth.failed') + ? Lang::get('auth.failed') + : 'These credentials do not match our records.'; + } + + /** + * Log the user out of the application. + * + * @return \Illuminate\Http\Response + */ + public function getLogout() + { + Auth::logout(); + + return redirect(property_exists($this, 'redirectAfterLogout') ? $this->redirectAfterLogout : '/'); + } + + /** + * Get the path to the login route. + * + * @return string + */ + public function loginPath() + { + return property_exists($this, 'loginPath') ? $this->loginPath : '/auth/login'; + } + + /** + * Get the login username to be used by the controller. + * + * @return string + */ + public function loginUsername() + { + return property_exists($this, 'username') ? $this->username : 'email'; + } + + /** + * Determine if the class is using the ThrottlesLogins trait. + * + * @return bool + */ + protected function isUsingThrottlesLoginsTrait() + { + return in_array( + ThrottlesLogins::class, class_uses_recursive(get_class($this)) + ); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RedirectsUsers.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RedirectsUsers.php new file mode 100644 index 0000000..99646a2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RedirectsUsers.php @@ -0,0 +1,20 @@ +redirectPath; + } + + return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php new file mode 100644 index 0000000..496abd6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php @@ -0,0 +1,42 @@ +validator($request->all()); + + if ($validator->fails()) { + $this->throwValidationException( + $request, $validator + ); + } + + Auth::login($this->create($request->all())); + + return redirect($this->redirectPath()); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php new file mode 100644 index 0000000..55d784d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php @@ -0,0 +1,119 @@ +validate($request, ['email' => 'required|email']); + + $response = Password::sendResetLink($request->only('email'), function (Message $message) { + $message->subject($this->getEmailSubject()); + }); + + switch ($response) { + case Password::RESET_LINK_SENT: + return redirect()->back()->with('status', trans($response)); + case Password::INVALID_USER: + return redirect()->back()->withErrors(['email' => trans($response)]); + } + } + + /** + * Get the e-mail subject line to be used for the reset link email. + * + * @return string + */ + protected function getEmailSubject() + { + return property_exists($this, 'subject') ? $this->subject : 'Your Password Reset Link'; + } + + /** + * Display the password reset view for the given token. + * + * @param string $token + * @return \Illuminate\Http\Response + */ + public function getReset($token = null) + { + if (is_null($token)) { + throw new NotFoundHttpException; + } + + return view('auth.reset')->with('token', $token); + } + + /** + * Reset the given user's password. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function postReset(Request $request) + { + $this->validate($request, [ + 'token' => 'required', + 'email' => 'required|email', + 'password' => 'required|confirmed|min:6', + ]); + + $credentials = $request->only( + 'email', 'password', 'password_confirmation', 'token' + ); + + $response = Password::reset($credentials, function ($user, $password) { + $this->resetPassword($user, $password); + }); + + switch ($response) { + case Password::PASSWORD_RESET: + return redirect($this->redirectPath())->with('status', trans($response)); + default: + return redirect()->back() + ->withInput($request->only('email')) + ->withErrors(['email' => trans($response)]); + } + } + + /** + * Reset the given user's password. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword $user + * @param string $password + * @return void + */ + protected function resetPassword($user, $password) + { + $user->password = bcrypt($password); + + $user->save(); + + Auth::login($user); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ThrottlesLogins.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ThrottlesLogins.php new file mode 100644 index 0000000..c382ce2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ThrottlesLogins.php @@ -0,0 +1,128 @@ +tooManyAttempts( + $this->getThrottleKey($request), + $this->maxLoginAttempts(), $this->lockoutTime() / 60 + ); + } + + /** + * Increment the login attempts for the user. + * + * @param \Illuminate\Http\Request $request + * @return int + */ + protected function incrementLoginAttempts(Request $request) + { + app(RateLimiter::class)->hit( + $this->getThrottleKey($request) + ); + } + + /** + * Determine how many retries are left for the user. + * + * @param \Illuminate\Http\Request $request + * @return int + */ + protected function retriesLeft(Request $request) + { + $attempts = app(RateLimiter::class)->attempts( + $this->getThrottleKey($request) + ); + + return $this->maxLoginAttempts() - $attempts + 1; + } + + /** + * Redirect the user after determining they are locked out. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + protected function sendLockoutResponse(Request $request) + { + $seconds = app(RateLimiter::class)->availableIn( + $this->getThrottleKey($request) + ); + + return redirect()->back() + ->withInput($request->only($this->loginUsername(), 'remember')) + ->withErrors([ + $this->loginUsername() => $this->getLockoutErrorMessage($seconds), + ]); + } + + /** + * Get the login lockout error message. + * + * @param int $seconds + * @return string + */ + protected function getLockoutErrorMessage($seconds) + { + return Lang::has('auth.throttle') + ? Lang::get('auth.throttle', ['seconds' => $seconds]) + : 'Too many login attempts. Please try again in '.$seconds.' seconds.'; + } + + /** + * Clear the login locks for the given user credentials. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function clearLoginAttempts(Request $request) + { + app(RateLimiter::class)->clear( + $this->getThrottleKey($request) + ); + } + + /** + * Get the throttle key for the given request. + * + * @param \Illuminate\Http\Request $request + * @return string + */ + protected function getThrottleKey(Request $request) + { + return mb_strtolower($request->input($this->loginUsername())).'|'.$request->ip(); + } + + /** + * Get the maximum number of login attempts for delaying further attempts. + * + * @return int + */ + protected function maxLoginAttempts() + { + return property_exists($this, 'maxLoginAttempts') ? $this->maxLoginAttempts : 5; + } + + /** + * The number of seconds to delay further login attempts. + * + * @return int + */ + protected function lockoutTime() + { + return property_exists($this, 'lockoutTime') ? $this->lockoutTime : 60; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/BootProviders.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/BootProviders.php new file mode 100644 index 0000000..2c0bcb0 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/BootProviders.php @@ -0,0 +1,19 @@ +boot(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/ConfigureLogging.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/ConfigureLogging.php new file mode 100644 index 0000000..8c75520 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/ConfigureLogging.php @@ -0,0 +1,112 @@ +registerLogger($app); + + // If a custom Monolog configurator has been registered for the application + // we will call that, passing Monolog along. Otherwise, we will grab the + // the configurations for the log system and use it for configuration. + if ($app->hasMonologConfigurator()) { + call_user_func( + $app->getMonologConfigurator(), $log->getMonolog() + ); + } else { + $this->configureHandlers($app, $log); + } + } + + /** + * Register the logger instance in the container. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return \Illuminate\Log\Writer + */ + protected function registerLogger(Application $app) + { + $app->instance('log', $log = new Writer( + new Monolog($app->environment()), $app['events']) + ); + + return $log; + } + + /** + * Configure the Monolog handlers for the application. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @param \Illuminate\Log\Writer $log + * @return void + */ + protected function configureHandlers(Application $app, Writer $log) + { + $method = 'configure'.ucfirst($app['config']['app.log']).'Handler'; + + $this->{$method}($app, $log); + } + + /** + * Configure the Monolog handlers for the application. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @param \Illuminate\Log\Writer $log + * @return void + */ + protected function configureSingleHandler(Application $app, Writer $log) + { + $log->useFiles($app->storagePath().'/logs/laravel.log'); + } + + /** + * Configure the Monolog handlers for the application. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @param \Illuminate\Log\Writer $log + * @return void + */ + protected function configureDailyHandler(Application $app, Writer $log) + { + $log->useDailyFiles( + $app->storagePath().'/logs/laravel.log', + $app->make('config')->get('app.log_max_files', 5) + ); + } + + /** + * Configure the Monolog handlers for the application. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @param \Illuminate\Log\Writer $log + * @return void + */ + protected function configureSyslogHandler(Application $app, Writer $log) + { + $log->useSyslog('laravel'); + } + + /** + * Configure the Monolog handlers for the application. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @param \Illuminate\Log\Writer $log + * @return void + */ + protected function configureErrorlogHandler(Application $app, Writer $log) + { + $log->useErrorLog(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/DetectEnvironment.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/DetectEnvironment.php new file mode 100644 index 0000000..4b44440 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/DetectEnvironment.php @@ -0,0 +1,29 @@ +environmentPath(), $app->environmentFile()); + } catch (InvalidArgumentException $e) { + // + } + + $app->detectEnvironment(function () { + return env('APP_ENV', 'production'); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php new file mode 100644 index 0000000..4974e5c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -0,0 +1,156 @@ +app = $app; + + error_reporting(-1); + + set_error_handler([$this, 'handleError']); + + set_exception_handler([$this, 'handleException']); + + register_shutdown_function([$this, 'handleShutdown']); + + if (! $app->environment('testing')) { + ini_set('display_errors', 'Off'); + } + } + + /** + * Convert a PHP error to an ErrorException. + * + * @param int $level + * @param string $message + * @param string $file + * @param int $line + * @param array $context + * @return void + * + * @throws \ErrorException + */ + public function handleError($level, $message, $file = '', $line = 0, $context = []) + { + if (error_reporting() & $level) { + throw new ErrorException($message, 0, $level, $file, $line); + } + } + + /** + * Handle an uncaught exception from the application. + * + * Note: Most exceptions can be handled via the try / catch block in + * the HTTP and Console kernels. But, fatal error exceptions must + * be handled differently since they are not normal exceptions. + * + * @param \Throwable $e + * @return void + */ + public function handleException($e) + { + if (! $e instanceof Exception) { + $e = new FatalThrowableError($e); + } + + $this->getExceptionHandler()->report($e); + + if ($this->app->runningInConsole()) { + $this->renderForConsole($e); + } else { + $this->renderHttpResponse($e); + } + } + + /** + * Render an exception to the console. + * + * @param \Exception $e + * @return void + */ + protected function renderForConsole($e) + { + $this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e); + } + + /** + * Render an exception as an HTTP response and send it. + * + * @param \Exception $e + * @return void + */ + protected function renderHttpResponse($e) + { + $this->getExceptionHandler()->render($this->app['request'], $e)->send(); + } + + /** + * Handle the PHP shutdown event. + * + * @return void + */ + public function handleShutdown() + { + if (! is_null($error = error_get_last()) && $this->isFatal($error['type'])) { + $this->handleException($this->fatalExceptionFromError($error, 0)); + } + } + + /** + * Create a new fatal exception instance from an error array. + * + * @param array $error + * @param int|null $traceOffset + * @return \Symfony\Component\Debug\Exception\FatalErrorException + */ + protected function fatalExceptionFromError(array $error, $traceOffset = null) + { + return new FatalErrorException( + $error['message'], $error['type'], 0, $error['file'], $error['line'], $traceOffset + ); + } + + /** + * Determine if the error type is fatal. + * + * @param int $type + * @return bool + */ + protected function isFatal($type) + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * Get an instance of the exception handler. + * + * @return \Illuminate\Contracts\Debug\ExceptionHandler + */ + protected function getExceptionHandler() + { + return $this->app->make('Illuminate\Contracts\Debug\ExceptionHandler'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php new file mode 100644 index 0000000..9f1c731 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php @@ -0,0 +1,98 @@ +getCachedConfigPath())) { + $items = require $cached; + + $loadedFromCache = true; + } + + $app->instance('config', $config = new Repository($items)); + + // Next we will spin through all of the configuration files in the configuration + // directory and load each one into the repository. This will make all of the + // options available to the developer for use in various parts of this app. + if (! isset($loadedFromCache)) { + $this->loadConfigurationFiles($app, $config); + } + + date_default_timezone_set($config['app.timezone']); + + mb_internal_encoding('UTF-8'); + } + + /** + * Load the configuration items from all of the files. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @param \Illuminate\Contracts\Config\Repository $repository + * @return void + */ + protected function loadConfigurationFiles(Application $app, RepositoryContract $repository) + { + foreach ($this->getConfigurationFiles($app) as $key => $path) { + $repository->set($key, require $path); + } + } + + /** + * Get all of the configuration files for the application. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return array + */ + protected function getConfigurationFiles(Application $app) + { + $files = []; + + $configPath = realpath($app->configPath()); + + foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) { + $nesting = $this->getConfigurationNesting($file, $configPath); + + $files[$nesting.basename($file->getRealPath(), '.php')] = $file->getRealPath(); + } + + return $files; + } + + /** + * Get the configuration file nesting path. + * + * @param \Symfony\Component\Finder\SplFileInfo $file + * @param string $configPath + * @return string + */ + protected function getConfigurationNesting(SplFileInfo $file, $configPath) + { + $directory = dirname($file->getRealPath()); + + if ($tree = trim(str_replace($configPath, '', $directory), DIRECTORY_SEPARATOR)) { + $tree = str_replace(DIRECTORY_SEPARATOR, '.', $tree).'.'; + } + + return $tree; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php new file mode 100644 index 0000000..7db2174 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php @@ -0,0 +1,25 @@ +make('config')->get('app.aliases'))->register(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php new file mode 100644 index 0000000..f18375c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php @@ -0,0 +1,19 @@ +registerConfiguredProviders(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/SetRequestForConsole.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/SetRequestForConsole.php new file mode 100644 index 0000000..ea9ef10 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/SetRequestForConsole.php @@ -0,0 +1,22 @@ +make('config')->get('app.url', 'http://localhost'); + + $app->instance('request', Request::create($url, 'GET', [], [], [], $_SERVER)); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Bus/DispatchesCommands.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bus/DispatchesCommands.php new file mode 100644 index 0000000..cc6678c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Bus/DispatchesCommands.php @@ -0,0 +1,11 @@ +dispatch($job); + } + + /** + * Marshal a job and dispatch it to its appropriate handler. + * + * @param mixed $job + * @param array $array + * @return mixed + */ + protected function dispatchFromArray($job, array $array) + { + return app('Illuminate\Contracts\Bus\Dispatcher')->dispatchFromArray($job, $array); + } + + /** + * Marshal a job and dispatch it to its appropriate handler. + * + * @param mixed $job + * @param \ArrayAccess $source + * @param array $extras + * @return mixed + */ + protected function dispatchFrom($job, ArrayAccess $source, $extras = []) + { + return app('Illuminate\Contracts\Bus\Dispatcher')->dispatchFrom($job, $source, $extras); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Composer.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Composer.php new file mode 100644 index 0000000..1027394 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Composer.php @@ -0,0 +1,106 @@ +files = $files; + $this->workingPath = $workingPath; + } + + /** + * Regenerate the Composer autoloader files. + * + * @param string $extra + * @return void + */ + public function dumpAutoloads($extra = '') + { + $process = $this->getProcess(); + + $process->setCommandLine(trim($this->findComposer().' dump-autoload '.$extra)); + + $process->run(); + } + + /** + * Regenerate the optimized Composer autoloader files. + * + * @return void + */ + public function dumpOptimized() + { + $this->dumpAutoloads('--optimize'); + } + + /** + * Get the composer command for the environment. + * + * @return string + */ + protected function findComposer() + { + if (! $this->files->exists($this->workingPath.'/composer.phar')) { + return 'composer'; + } + + $binary = ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)); + + if (defined('HHVM_VERSION')) { + $binary .= ' --php'; + } + + return "{$binary} composer.phar"; + } + + /** + * Get a new Symfony process instance. + * + * @return \Symfony\Component\Process\Process + */ + protected function getProcess() + { + return (new Process('', $this->workingPath))->setTimeout(null); + } + + /** + * Set the working path used by the class. + * + * @param string $path + * @return $this + */ + public function setWorkingPath($path) + { + $this->workingPath = realpath($path); + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/ComposerScripts.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/ComposerScripts.php new file mode 100644 index 0000000..604a0f8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/ComposerScripts.php @@ -0,0 +1,52 @@ +getComposer()->getConfig()->get('vendor-dir').'/autoload.php'; + + static::clearCompiled(); + } + + /** + * Handle the post-update Composer event. + * + * @param \Composer\Script\Event $event + * @return void + */ + public static function postUpdate(Event $event) + { + require_once $event->getComposer()->getConfig()->get('vendor-dir').'/autoload.php'; + + static::clearCompiled(); + } + + /** + * Clear the cached Laravel bootstrapping files. + * + * @return void + */ + protected static function clearCompiled() + { + $laravel = new Application(getcwd()); + + if (file_exists($compiledPath = $laravel->getCachedCompilePath())) { + @unlink($compiledPath); + } + + if (file_exists($servicesPath = $laravel->getCachedServicesPath())) { + @unlink($servicesPath); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/AppNameCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/AppNameCommand.php new file mode 100644 index 0000000..99a2798 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/AppNameCommand.php @@ -0,0 +1,329 @@ +files = $files; + $this->composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->currentRoot = trim($this->laravel->getNamespace(), '\\'); + + $this->setBootstrapNamespaces(); + + $this->setAppDirectoryNamespace(); + + $this->setConfigNamespaces(); + + $this->setComposerNamespace(); + + $this->setDatabaseFactoryNamespaces(); + + $this->setPhpSpecNamespace(); + + $this->info('Application namespace set!'); + + $this->composer->dumpAutoloads(); + + $this->call('clear-compiled'); + } + + /** + * Set the namespace on the files in the app directory. + * + * @return void + */ + protected function setAppDirectoryNamespace() + { + $files = Finder::create() + ->in($this->laravel['path']) + ->name('*.php'); + + foreach ($files as $file) { + $this->replaceNamespace($file->getRealPath()); + } + } + + /** + * Replace the App namespace at the given path. + * + * @param string $path + * @return void + */ + protected function replaceNamespace($path) + { + $search = [ + 'namespace '.$this->currentRoot.';', + $this->currentRoot.'\\', + ]; + + $replace = [ + 'namespace '.$this->argument('name').';', + $this->argument('name').'\\', + ]; + + $this->replaceIn($path, $search, $replace); + } + + /** + * Set the bootstrap namespaces. + * + * @return void + */ + protected function setBootstrapNamespaces() + { + $search = [ + $this->currentRoot.'\\Http', + $this->currentRoot.'\\Console', + $this->currentRoot.'\\Exceptions', + ]; + + $replace = [ + $this->argument('name').'\\Http', + $this->argument('name').'\\Console', + $this->argument('name').'\\Exceptions', + ]; + + $this->replaceIn($this->getBootstrapPath(), $search, $replace); + } + + /** + * Set the PSR-4 namespace in the Composer file. + * + * @return void + */ + protected function setComposerNamespace() + { + $this->replaceIn( + $this->getComposerPath(), str_replace('\\', '\\\\', $this->currentRoot).'\\\\', str_replace('\\', '\\\\', $this->argument('name')).'\\\\' + ); + } + + /** + * Set the namespace in the appropriate configuration files. + * + * @return void + */ + protected function setConfigNamespaces() + { + $this->setAppConfigNamespaces(); + + $this->setAuthConfigNamespace(); + + $this->setServicesConfigNamespace(); + } + + /** + * Set the application provider namespaces. + * + * @return void + */ + protected function setAppConfigNamespaces() + { + $search = [ + $this->currentRoot.'\\Providers', + $this->currentRoot.'\\Http\\Controllers\\', + ]; + + $replace = [ + $this->argument('name').'\\Providers', + $this->argument('name').'\\Http\\Controllers\\', + ]; + + $this->replaceIn($this->getConfigPath('app'), $search, $replace); + } + + /** + * Set the authentication User namespace. + * + * @return void + */ + protected function setAuthConfigNamespace() + { + $this->replaceIn( + $this->getAuthConfigPath(), $this->currentRoot.'\\User', $this->argument('name').'\\User' + ); + } + + /** + * Set the services User namespace. + * + * @return void + */ + protected function setServicesConfigNamespace() + { + $this->replaceIn( + $this->getServicesConfigPath(), $this->currentRoot.'\\User', $this->argument('name').'\\User' + ); + } + + /** + * Set the PHPSpec configuration namespace. + * + * @return void + */ + protected function setPhpSpecNamespace() + { + if ($this->files->exists($path = $this->getPhpSpecConfigPath())) { + $this->replaceIn($path, $this->currentRoot, $this->argument('name')); + } + } + + /** + * Set the namespace in database factory files. + * + * @return void + */ + protected function setDatabaseFactoryNamespaces() + { + $this->replaceIn( + $this->laravel->databasePath().'/factories/ModelFactory.php', $this->currentRoot, $this->argument('name') + ); + } + + /** + * Replace the given string in the given file. + * + * @param string $path + * @param string|array $search + * @param string|array $replace + * @return void + */ + protected function replaceIn($path, $search, $replace) + { + $this->files->put($path, str_replace($search, $replace, $this->files->get($path))); + } + + /** + * Get the path to the bootstrap/app.php file. + * + * @return string + */ + protected function getBootstrapPath() + { + return $this->laravel->basePath().'/bootstrap/app.php'; + } + + /** + * Get the path to the Composer.json file. + * + * @return string + */ + protected function getComposerPath() + { + return $this->laravel->basePath().'/composer.json'; + } + + /** + * Get the path to the given configuration file. + * + * @param string $name + * @return string + */ + protected function getConfigPath($name) + { + return $this->laravel['path.config'].'/'.$name.'.php'; + } + + /** + * Get the path to the authentication configuration file. + * + * @return string + */ + protected function getAuthConfigPath() + { + return $this->getConfigPath('auth'); + } + + /** + * Get the path to the services configuration file. + * + * @return string + */ + protected function getServicesConfigPath() + { + return $this->getConfigPath('services'); + } + + /** + * Get the path to the PHPSpec configuration file. + * + * @return string + */ + protected function getPhpSpecConfigPath() + { + return $this->laravel->basePath().'/phpspec.yml'; + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['name', InputArgument::REQUIRED, 'The desired namespace.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php new file mode 100644 index 0000000..a2f3c34 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php @@ -0,0 +1,41 @@ +laravel->getCachedCompilePath(); + $servicesPath = $this->laravel->getCachedServicesPath(); + + if (file_exists($compiledPath)) { + @unlink($compiledPath); + } + + if (file_exists($servicesPath)) { + @unlink($servicesPath); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/CommandMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/CommandMakeCommand.php new file mode 100644 index 0000000..d009dc2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/CommandMakeCommand.php @@ -0,0 +1,90 @@ +option('handler')) { + $this->call('handler:command', [ + 'name' => $this->argument('name').'Handler', + '--command' => $this->parseName($this->argument('name')), + ]); + } + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + if ($this->option('queued') && $this->option('handler')) { + return __DIR__.'/stubs/command-queued-with-handler.stub'; + } elseif ($this->option('queued')) { + return __DIR__.'/stubs/command-queued.stub'; + } elseif ($this->option('handler')) { + return __DIR__.'/stubs/command-with-handler.stub'; + } else { + return __DIR__.'/stubs/command.stub'; + } + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Commands'; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['handler', null, InputOption::VALUE_NONE, 'Indicates that handler class should be generated.'], + + ['queued', null, InputOption::VALUE_NONE, 'Indicates that command should be queued.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigCacheCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigCacheCommand.php new file mode 100644 index 0000000..2152cfa --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigCacheCommand.php @@ -0,0 +1,75 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->call('config:clear'); + + $config = $this->getFreshConfiguration(); + + $this->files->put( + $this->laravel->getCachedConfigPath(), 'info('Configuration cached successfully!'); + } + + /** + * Boot a fresh copy of the application configuration. + * + * @return array + */ + protected function getFreshConfiguration() + { + $app = require $this->laravel->basePath().'/bootstrap/app.php'; + + $app->make('Illuminate\Contracts\Console\Kernel')->bootstrap(); + + return $app['config']->all(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigClearCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigClearCommand.php new file mode 100644 index 0000000..bb25a8e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigClearCommand.php @@ -0,0 +1,55 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->files->delete($this->laravel->getCachedConfigPath()); + + $this->info('Configuration cache cleared!'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConsoleMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConsoleMakeCommand.php new file mode 100644 index 0000000..f69a5d6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConsoleMakeCommand.php @@ -0,0 +1,90 @@ +option('command'), $stub); + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + return __DIR__.'/stubs/console.stub'; + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Console\Commands'; + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['name', InputArgument::REQUIRED, 'The name of the command.'], + ]; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['command', null, InputOption::VALUE_OPTIONAL, 'The terminal command that should be assigned.', 'command:name'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/DownCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/DownCommand.php new file mode 100644 index 0000000..05ad35b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/DownCommand.php @@ -0,0 +1,34 @@ +laravel->storagePath().'/framework/down'); + + $this->comment('Application is now in maintenance mode.'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EnvironmentCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EnvironmentCommand.php new file mode 100644 index 0000000..b227165 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EnvironmentCommand.php @@ -0,0 +1,32 @@ +line('Current application environment: '.$this->laravel['env'].''); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EventGenerateCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EventGenerateCommand.php new file mode 100644 index 0000000..f08ebdd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EventGenerateCommand.php @@ -0,0 +1,51 @@ +laravel->getProvider( + 'Illuminate\Foundation\Support\Providers\EventServiceProvider' + ); + + foreach ($provider->listens() as $event => $listeners) { + if (! Str::contains($event, '\\')) { + continue; + } + + $this->callSilent('make:event', ['name' => $event]); + + foreach ($listeners as $listener) { + $listener = preg_replace('/@.+$/', '', $listener); + + $this->callSilent('make:listener', ['name' => $listener, '--event' => $event]); + } + } + + $this->info('Events and listeners generated successfully!'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EventMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EventMakeCommand.php new file mode 100644 index 0000000..f18719a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/EventMakeCommand.php @@ -0,0 +1,61 @@ +option('command')), $stub + ); + + $stub = str_replace( + 'DummyFullCommand', $this->option('command'), $stub + ); + + return $stub; + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + return __DIR__.'/stubs/command-handler.stub'; + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Handlers\Commands'; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['command', null, InputOption::VALUE_REQUIRED, 'The command class the handler handles.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/HandlerEventCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/HandlerEventCommand.php new file mode 100644 index 0000000..341ffe0 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/HandlerEventCommand.php @@ -0,0 +1,97 @@ +option('event'); + + if (! Str::startsWith($event, $this->laravel->getNamespace())) { + $event = $this->laravel->getNamespace().'Events\\'.$event; + } + + $stub = str_replace( + 'DummyEvent', class_basename($event), $stub + ); + + $stub = str_replace( + 'DummyFullEvent', $event, $stub + ); + + return $stub; + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + if ($this->option('queued')) { + return __DIR__.'/stubs/event-handler-queued.stub'; + } else { + return __DIR__.'/stubs/event-handler.stub'; + } + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Handlers\Events'; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['event', null, InputOption::VALUE_REQUIRED, 'The event class the handler handles.'], + + ['queued', null, InputOption::VALUE_NONE, 'Indicates the event handler should be queued.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/IlluminateCaster.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/IlluminateCaster.php new file mode 100644 index 0000000..e555c82 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/IlluminateCaster.php @@ -0,0 +1,97 @@ +$property(); + + if (! is_null($val)) { + $results[Caster::PREFIX_VIRTUAL.$property] = $val; + } + } catch (Exception $e) { + // + } + } + + return $results; + } + + /** + * Get an array representing the properties of a collection. + * + * @param \Illuminate\Support\Collection $collection + * @return array + */ + public static function castCollection(Collection $collection) + { + return [ + Caster::PREFIX_VIRTUAL.'all' => $collection->all(), + ]; + } + + /** + * Get an array representing the properties of a model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return array + */ + public static function castModel(Model $model) + { + $attributes = array_merge( + $model->getAttributes(), $model->getRelations() + ); + + $visible = array_flip( + $model->getVisible() ?: array_diff(array_keys($attributes), $model->getHidden()) + ); + + $results = []; + + foreach (array_intersect_key($attributes, $visible) as $key => $value) { + $results[(isset($visible[$key]) ? Caster::PREFIX_VIRTUAL : Caster::PREFIX_PROTECTED).$key] = $value; + } + + return $results; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/JobMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/JobMakeCommand.php new file mode 100644 index 0000000..8e1aeee --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/JobMakeCommand.php @@ -0,0 +1,67 @@ +option('queued')) { + return __DIR__.'/stubs/job-queued.stub'; + } else { + return __DIR__.'/stubs/job.stub'; + } + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Jobs'; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['queued', null, InputOption::VALUE_NONE, 'Indicates that job should be queued.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php new file mode 100644 index 0000000..991588f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php @@ -0,0 +1,264 @@ +app = $app; + $this->events = $events; + + $this->app->booted(function () { + $this->defineConsoleSchedule(); + }); + } + + /** + * Define the application's command schedule. + * + * @return void + */ + protected function defineConsoleSchedule() + { + $this->app->instance( + 'Illuminate\Console\Scheduling\Schedule', $schedule = new Schedule + ); + + $this->schedule($schedule); + } + + /** + * Run the console application. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return int + */ + public function handle($input, $output = null) + { + try { + $this->bootstrap(); + + return $this->getArtisan()->run($input, $output); + } catch (Exception $e) { + $this->reportException($e); + + $this->renderException($output, $e); + + return 1; + } catch (Throwable $e) { + $e = new FatalThrowableError($e); + + $this->reportException($e); + + $this->renderException($output, $e); + + return 1; + } + } + + /** + * Terminate the application. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param int $status + * @return void + */ + public function terminate($input, $status) + { + $this->app->terminate(); + } + + /** + * Define the application's command schedule. + * + * @param \Illuminate\Console\Scheduling\Schedule $schedule + * @return void + */ + protected function schedule(Schedule $schedule) + { + // + } + + /** + * Run an Artisan console command by name. + * + * @param string $command + * @param array $parameters + * @return int + */ + public function call($command, array $parameters = []) + { + $this->bootstrap(); + + return $this->getArtisan()->call($command, $parameters); + } + + /** + * Queue the given console command. + * + * @param string $command + * @param array $parameters + * @return void + */ + public function queue($command, array $parameters = []) + { + $this->app['Illuminate\Contracts\Queue\Queue']->push( + 'Illuminate\Foundation\Console\QueuedJob', func_get_args() + ); + } + + /** + * Get all of the commands registered with the console. + * + * @return array + */ + public function all() + { + $this->bootstrap(); + + return $this->getArtisan()->all(); + } + + /** + * Get the output for the last run command. + * + * @return string + */ + public function output() + { + $this->bootstrap(); + + return $this->getArtisan()->output(); + } + + /** + * Bootstrap the application for artisan commands. + * + * @return void + */ + public function bootstrap() + { + if (! $this->app->hasBeenBootstrapped()) { + $this->app->bootstrapWith($this->bootstrappers()); + } + + // If we are calling an arbitrary command from within the application, we'll load + // all of the available deferred providers which will make all of the commands + // available to an application. Otherwise the command will not be available. + $this->app->loadDeferredProviders(); + } + + /** + * Get the Artisan application instance. + * + * @return \Illuminate\Console\Application + */ + protected function getArtisan() + { + if (is_null($this->artisan)) { + return $this->artisan = (new Artisan($this->app, $this->events, $this->app->version())) + ->resolveCommands($this->commands); + } + + return $this->artisan; + } + + /** + * Get the bootstrap classes for the application. + * + * @return array + */ + protected function bootstrappers() + { + return $this->bootstrappers; + } + + /** + * Report the exception to the exception handler. + * + * @param \Exception $e + * @return void + */ + protected function reportException(Exception $e) + { + $this->app['Illuminate\Contracts\Debug\ExceptionHandler']->report($e); + } + + /** + * Report the exception to the exception handler. + * + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @param \Exception $e + * @return void + */ + protected function renderException($output, Exception $e) + { + $this->app['Illuminate\Contracts\Debug\ExceptionHandler']->renderForConsole($output, $e); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/KeyGenerateCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/KeyGenerateCommand.php new file mode 100644 index 0000000..12d8bc9 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/KeyGenerateCommand.php @@ -0,0 +1,77 @@ +getRandomKey($this->laravel['config']['app.cipher']); + + if ($this->option('show')) { + return $this->line(''.$key.''); + } + + $path = base_path('.env'); + + if (file_exists($path)) { + file_put_contents($path, str_replace( + 'APP_KEY='.$this->laravel['config']['app.key'], 'APP_KEY='.$key, file_get_contents($path) + )); + } + + $this->laravel['config']['app.key'] = $key; + + $this->info("Application key [$key] set successfully."); + } + + /** + * Generate a random key for the application. + * + * @param string $cipher + * @return string + */ + protected function getRandomKey($cipher) + { + if ($cipher === 'AES-128-CBC') { + return Str::random(16); + } + + return Str::random(32); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['show', null, InputOption::VALUE_NONE, 'Simply display the key instead of modifying files.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ListenerMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ListenerMakeCommand.php new file mode 100644 index 0000000..62861df --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ListenerMakeCommand.php @@ -0,0 +1,111 @@ +option('event')) { + return $this->error('Missing required option: --event'); + } + + parent::fire(); + } + + /** + * Build the class with the given name. + * + * @param string $name + * @return string + */ + protected function buildClass($name) + { + $stub = parent::buildClass($name); + + $event = $this->option('event'); + + if (! Str::startsWith($event, $this->laravel->getNamespace())) { + $event = $this->laravel->getNamespace().'Events\\'.$event; + } + + $stub = str_replace( + 'DummyEvent', class_basename($event), $stub + ); + + $stub = str_replace( + 'DummyFullEvent', $event, $stub + ); + + return $stub; + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + if ($this->option('queued')) { + return __DIR__.'/stubs/listener-queued.stub'; + } else { + return __DIR__.'/stubs/listener.stub'; + } + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Listeners'; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['event', null, InputOption::VALUE_REQUIRED, 'The event class being listened for.'], + + ['queued', null, InputOption::VALUE_NONE, 'Indicates the event listener should be queued.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ModelMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ModelMakeCommand.php new file mode 100644 index 0000000..ea95a51 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ModelMakeCommand.php @@ -0,0 +1,80 @@ +option('migration')) { + $table = Str::plural(Str::snake(class_basename($this->argument('name')))); + + $this->call('make:migration', ['name' => "create_{$table}_table", '--create' => $table]); + } + } + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + return __DIR__.'/stubs/model.stub'; + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['migration', 'm', InputOption::VALUE_NONE, 'Create a new migration file for the model.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/Optimize/config.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/Optimize/config.php new file mode 100644 index 0000000..1f024e0 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/Optimize/config.php @@ -0,0 +1,208 @@ +composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->info('Generating optimized class loader'); + + if ($this->option('psr')) { + $this->composer->dumpAutoloads(); + } else { + $this->composer->dumpOptimized(); + } + + if ($this->option('force') || ! $this->laravel['config']['app.debug']) { + $this->info('Compiling common classes'); + $this->compileClasses(); + } else { + $this->call('clear-compiled'); + } + } + + /** + * Generate the compiled class file. + * + * @return void + */ + protected function compileClasses() + { + $preloader = $this->getClassPreloader(); + + $handle = $preloader->prepareOutput($this->laravel->getCachedCompilePath()); + + foreach ($this->getClassFiles() as $file) { + try { + fwrite($handle, $preloader->getCode($file, false)."\n"); + } catch (SkipFileException $ex) { + // Class Preloader 2.x + } catch (VisitorExceptionInterface $e) { + // Class Preloader 3.x + } + } + + fclose($handle); + } + + /** + * Get the class preloader used by the command. + * + * @return \ClassPreloader\ClassPreloader + */ + protected function getClassPreloader() + { + // Class Preloader 3.x + if (class_exists(Factory::class)) { + return (new Factory)->create(['skip' => true]); + } + + // Class Preloader 2.x + return new ClassPreloader(new PrettyPrinter, new Parser(new Lexer), $this->getTraverser()); + } + + /** + * Get the node traverser used by the command. + * + * Note that this method is only called if we're using Class Preloader 2.x. + * + * @return \ClassPreloader\Parser\NodeTraverser + */ + protected function getTraverser() + { + $traverser = new NodeTraverser; + + $traverser->addVisitor(new DirVisitor(true)); + + $traverser->addVisitor(new FileVisitor(true)); + + return $traverser; + } + + /** + * Get the classes that should be combined and compiled. + * + * @return array + */ + protected function getClassFiles() + { + $app = $this->laravel; + + $core = require __DIR__.'/Optimize/config.php'; + + $files = array_merge($core, $app['config']->get('compile.files', [])); + + foreach ($app['config']->get('compile.providers', []) as $provider) { + $files = array_merge($files, forward_static_call([$provider, 'compiles'])); + } + + return array_map('realpath', $files); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['force', null, InputOption::VALUE_NONE, 'Force the compiled class file to be written.'], + + ['psr', null, InputOption::VALUE_NONE, 'Do not optimize Composer dump-autoload.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/PolicyMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/PolicyMakeCommand.php new file mode 100644 index 0000000..e071ca7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/PolicyMakeCommand.php @@ -0,0 +1,50 @@ +kernel = $kernel; + } + + /** + * Fire the job. + * + * @param \Illuminate\Queue\Jobs\Job $job + * @param array $data + * @return void + */ + public function fire($job, $data) + { + call_user_func_array([$this->kernel, 'call'], $data); + + $job->delete(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RequestMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RequestMakeCommand.php new file mode 100644 index 0000000..95b7a87 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RequestMakeCommand.php @@ -0,0 +1,50 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->call('route:clear'); + + $routes = $this->getFreshApplicationRoutes(); + + if (count($routes) == 0) { + return $this->error("Your application doesn't have any routes."); + } + + foreach ($routes as $route) { + $route->prepareForSerialization(); + } + + $this->files->put( + $this->laravel->getCachedRoutesPath(), $this->buildRouteCacheFile($routes) + ); + + $this->info('Routes cached successfully!'); + } + + /** + * Boot a fresh copy of the application and get the routes. + * + * @return \Illuminate\Routing\RouteCollection + */ + protected function getFreshApplicationRoutes() + { + $app = require $this->laravel->basePath().'/bootstrap/app.php'; + + $app->make('Illuminate\Contracts\Console\Kernel')->bootstrap(); + + return $app['router']->getRoutes(); + } + + /** + * Build the route cache file. + * + * @param \Illuminate\Routing\RouteCollection $routes + * @return string + */ + protected function buildRouteCacheFile(RouteCollection $routes) + { + $stub = $this->files->get(__DIR__.'/stubs/routes.stub'); + + return str_replace('{{routes}}', base64_encode(serialize($routes)), $stub); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteClearCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteClearCommand.php new file mode 100644 index 0000000..a14b280 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteClearCommand.php @@ -0,0 +1,55 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->files->delete($this->laravel->getCachedRoutesPath()); + + $this->info('Route cache cleared!'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteListCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteListCommand.php new file mode 100644 index 0000000..463f3ab --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteListCommand.php @@ -0,0 +1,281 @@ +router = $router; + $this->routes = $router->getRoutes(); + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + if (count($this->routes) == 0) { + return $this->error("Your application doesn't have any routes."); + } + + $this->displayRoutes($this->getRoutes()); + } + + /** + * Compile the routes into a displayable format. + * + * @return array + */ + protected function getRoutes() + { + $results = []; + + foreach ($this->routes as $route) { + $results[] = $this->getRouteInformation($route); + } + + if ($sort = $this->option('sort')) { + $results = Arr::sort($results, function ($value) use ($sort) { + return $value[$sort]; + }); + } + + if ($this->option('reverse')) { + $results = array_reverse($results); + } + + return array_filter($results); + } + + /** + * Get the route information for a given route. + * + * @param \Illuminate\Routing\Route $route + * @return array + */ + protected function getRouteInformation(Route $route) + { + return $this->filterRoute([ + 'host' => $route->domain(), + 'method' => implode('|', $route->methods()), + 'uri' => $route->uri(), + 'name' => $route->getName(), + 'action' => $route->getActionName(), + 'middleware' => $this->getMiddleware($route), + ]); + } + + /** + * Display the route information on the console. + * + * @param array $routes + * @return void + */ + protected function displayRoutes(array $routes) + { + $this->table($this->headers, $routes); + } + + /** + * Get before filters. + * + * @param \Illuminate\Routing\Route $route + * @return string + */ + protected function getMiddleware($route) + { + $middlewares = array_values($route->middleware()); + + $middlewares = array_unique( + array_merge($middlewares, $this->getPatternFilters($route)) + ); + + $actionName = $route->getActionName(); + + if (! empty($actionName) && $actionName !== 'Closure') { + $middlewares = array_merge($middlewares, $this->getControllerMiddleware($actionName)); + } + + return implode(',', $middlewares); + } + + /** + * Get the middleware for the given Controller@action name. + * + * @param string $actionName + * @return array + */ + protected function getControllerMiddleware($actionName) + { + Controller::setRouter($this->laravel['router']); + + $segments = explode('@', $actionName); + + return $this->getControllerMiddlewareFromInstance( + $this->laravel->make($segments[0]), $segments[1] + ); + } + + /** + * Get the middlewares for the given controller instance and method. + * + * @param \Illuminate\Routing\Controller $controller + * @param string $method + * @return array + */ + protected function getControllerMiddlewareFromInstance($controller, $method) + { + $middleware = $this->router->getMiddleware(); + + $results = []; + + foreach ($controller->getMiddleware() as $name => $options) { + if (! $this->methodExcludedByOptions($method, $options)) { + $results[] = Arr::get($middleware, $name, $name); + } + } + + return $results; + } + + /** + * Determine if the given options exclude a particular method. + * + * @param string $method + * @param array $options + * @return bool + */ + protected function methodExcludedByOptions($method, array $options) + { + return (! empty($options['only']) && ! in_array($method, (array) $options['only'])) || + (! empty($options['except']) && in_array($method, (array) $options['except'])); + } + + /** + * Get all of the pattern filters matching the route. + * + * @param \Illuminate\Routing\Route $route + * @return array + */ + protected function getPatternFilters($route) + { + $patterns = []; + + foreach ($route->methods() as $method) { + // For each method supported by the route we will need to gather up the patterned + // filters for that method. We will then merge these in with the other filters + // we have already gathered up then return them back out to these consumers. + $inner = $this->getMethodPatterns($route->uri(), $method); + + $patterns = array_merge($patterns, array_keys($inner)); + } + + return $patterns; + } + + /** + * Get the pattern filters for a given URI and method. + * + * @param string $uri + * @param string $method + * @return array + */ + protected function getMethodPatterns($uri, $method) + { + return $this->router->findPatternFilters( + Request::create($uri, $method) + ); + } + + /** + * Filter the route by URI and / or name. + * + * @param array $route + * @return array|null + */ + protected function filterRoute(array $route) + { + if (($this->option('name') && ! Str::contains($route['name'], $this->option('name'))) || + $this->option('path') && ! Str::contains($route['uri'], $this->option('path')) || + $this->option('method') && ! Str::contains($route['method'], $this->option('method'))) { + return; + } + + return $route; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['method', null, InputOption::VALUE_OPTIONAL, 'Filter the routes by method.'], + + ['name', null, InputOption::VALUE_OPTIONAL, 'Filter the routes by name.'], + + ['path', null, InputOption::VALUE_OPTIONAL, 'Filter the routes by path.'], + + ['reverse', 'r', InputOption::VALUE_NONE, 'Reverse the ordering of the routes.'], + + ['sort', null, InputOption::VALUE_OPTIONAL, 'The column (host, method, uri, name, action, middleware) to sort by.', 'uri'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php new file mode 100644 index 0000000..90e596e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php @@ -0,0 +1,72 @@ +laravel->publicPath()); + + $host = $this->input->getOption('host'); + + $port = $this->input->getOption('port'); + + $base = ProcessUtils::escapeArgument($this->laravel->basePath()); + + $binary = ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)); + + $this->info("Laravel development server started on http://{$host}:{$port}/"); + + if (defined('HHVM_VERSION')) { + if (version_compare(HHVM_VERSION, '3.8.0') >= 0) { + passthru("{$binary} -m server -v Server.Type=proxygen -v Server.SourceRoot={$base}/ -v Server.IP={$host} -v Server.Port={$port} -v Server.DefaultDocument=server.php -v Server.ErrorDocument404=server.php"); + } else { + throw new Exception("HHVM's built-in server requires HHVM >= 3.8.0."); + } + } else { + passthru("{$binary} -S {$host}:{$port} {$base}/server.php"); + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['host', null, InputOption::VALUE_OPTIONAL, 'The host address to serve the application on.', 'localhost'], + + ['port', null, InputOption::VALUE_OPTIONAL, 'The port to serve the application on.', 8000], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/TestMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/TestMakeCommand.php new file mode 100644 index 0000000..7f0efe8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/TestMakeCommand.php @@ -0,0 +1,63 @@ +laravel->getNamespace(), '', $name); + + return $this->laravel['path.base'].'/tests/'.str_replace('\\', '/', $name).'.php'; + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/TinkerCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/TinkerCommand.php new file mode 100644 index 0000000..68c48ae --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/TinkerCommand.php @@ -0,0 +1,100 @@ +getApplication()->setCatchExceptions(false); + + $config = new Configuration; + + $config->getPresenter()->addCasters( + $this->getCasters() + ); + + $shell = new Shell($config); + $shell->addCommands($this->getCommands()); + $shell->setIncludes($this->argument('include')); + + $shell->run(); + } + + /** + * Get artisan commands to pass through to PsySH. + * + * @return array + */ + protected function getCommands() + { + $commands = []; + + foreach ($this->getApplication()->all() as $name => $command) { + if (in_array($name, $this->commandWhitelist)) { + $commands[] = $command; + } + } + + return $commands; + } + + /** + * Get an array of Laravel tailored casters. + * + * @return array + */ + protected function getCasters() + { + return [ + 'Illuminate\Foundation\Application' => 'Illuminate\Foundation\Console\IlluminateCaster::castApplication', + 'Illuminate\Support\Collection' => 'Illuminate\Foundation\Console\IlluminateCaster::castCollection', + 'Illuminate\Database\Eloquent\Model' => 'Illuminate\Foundation\Console\IlluminateCaster::castModel', + ]; + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['include', InputArgument::IS_ARRAY, 'Include file(s) before starting tinker'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/UpCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/UpCommand.php new file mode 100644 index 0000000..82d8385 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/UpCommand.php @@ -0,0 +1,34 @@ +laravel->storagePath().'/framework/down'); + + $this->info('Application is now live.'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/VendorPublishCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/VendorPublishCommand.php new file mode 100644 index 0000000..2bb2990 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/VendorPublishCommand.php @@ -0,0 +1,167 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $tags = $this->option('tag'); + + $tags = $tags ?: [null]; + + foreach ($tags as $tag) { + $this->publishTag($tag); + } + } + + /** + * Publishes the assets for a tag. + * + * @param string $tag + * @return mixed + */ + private function publishTag($tag) + { + $paths = ServiceProvider::pathsToPublish( + $this->option('provider'), $tag + ); + + if (empty($paths)) { + return $this->comment("Nothing to publish for tag [{$tag}]."); + } + + foreach ($paths as $from => $to) { + if ($this->files->isFile($from)) { + $this->publishFile($from, $to); + } elseif ($this->files->isDirectory($from)) { + $this->publishDirectory($from, $to); + } else { + $this->error("Can't locate path: <{$from}>"); + } + } + + $this->info("Publishing complete for tag [{$tag}]!"); + } + + /** + * Publish the file to the given path. + * + * @param string $from + * @param string $to + * @return void + */ + protected function publishFile($from, $to) + { + if ($this->files->exists($to) && ! $this->option('force')) { + return; + } + + $this->createParentDirectory(dirname($to)); + + $this->files->copy($from, $to); + + $this->status($from, $to, 'File'); + } + + /** + * Publish the directory to the given directory. + * + * @param string $from + * @param string $to + * @return void + */ + protected function publishDirectory($from, $to) + { + $manager = new MountManager([ + 'from' => new Flysystem(new LocalAdapter($from)), + 'to' => new Flysystem(new LocalAdapter($to)), + ]); + + foreach ($manager->listContents('from://', true) as $file) { + if ($file['type'] === 'file' && (! $manager->has('to://'.$file['path']) || $this->option('force'))) { + $manager->put('to://'.$file['path'], $manager->read('from://'.$file['path'])); + } + } + + $this->status($from, $to, 'Directory'); + } + + /** + * Create the directory to house the published files if needed. + * + * @param string $directory + * @return void + */ + protected function createParentDirectory($directory) + { + if (! $this->files->isDirectory($directory)) { + $this->files->makeDirectory($directory, 0755, true); + } + } + + /** + * Write a status message to the console. + * + * @param string $from + * @param string $to + * @param string $type + * @return void + */ + protected function status($from, $to, $type) + { + $from = str_replace(base_path(), '', realpath($from)); + + $to = str_replace(base_path(), '', realpath($to)); + + $this->line('Copied '.$type.' ['.$from.'] To ['.$to.']'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ViewClearCommand.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ViewClearCommand.php new file mode 100644 index 0000000..c7e443e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/ViewClearCommand.php @@ -0,0 +1,64 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $path = $this->laravel['config']['view.compiled']; + + if (! $path) { + throw new RuntimeException('View path not found.'); + } + + foreach ($this->files->glob("{$path}/*") as $view) { + $this->files->delete($view); + } + + $this->info('Compiled views cleared!'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/command-handler.stub b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/command-handler.stub new file mode 100644 index 0000000..8fdf3ee --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/command-handler.stub @@ -0,0 +1,30 @@ +setRoutes( + unserialize(base64_decode('{{routes}}')) +); diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/test.stub b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/test.stub new file mode 100644 index 0000000..4a2858d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/test.stub @@ -0,0 +1,18 @@ +assertTrue(true); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/EnvironmentDetector.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/EnvironmentDetector.php new file mode 100644 index 0000000..9acdca8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/EnvironmentDetector.php @@ -0,0 +1,69 @@ +detectConsoleEnvironment($callback, $consoleArgs); + } + + return $this->detectWebEnvironment($callback); + } + + /** + * Set the application environment for a web request. + * + * @param \Closure $callback + * @return string + */ + protected function detectWebEnvironment(Closure $callback) + { + return call_user_func($callback); + } + + /** + * Set the application environment from command-line arguments. + * + * @param \Closure $callback + * @param array $args + * @return string + */ + protected function detectConsoleEnvironment(Closure $callback, array $args) + { + // First we will check if an environment argument was passed via console arguments + // and if it was that automatically overrides as the environment. Otherwise, we + // will check the environment as a "web" request like a typical HTTP request. + if (! is_null($value = $this->getEnvironmentArgument($args))) { + return head(array_slice(explode('=', $value), 1)); + } + + return $this->detectWebEnvironment($callback); + } + + /** + * Get the environment argument from the console. + * + * @param array $args + * @return string|null + */ + protected function getEnvironmentArgument(array $args) + { + return Arr::first($args, function ($k, $v) { + return Str::startsWith($v, '--env'); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php new file mode 100644 index 0000000..6c87455 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php @@ -0,0 +1,179 @@ +log = $log; + } + + /** + * Report or log an exception. + * + * @param \Exception $e + * @return void + */ + public function report(Exception $e) + { + if ($this->shouldReport($e)) { + $this->log->error($e); + } + } + + /** + * Determine if the exception should be reported. + * + * @param \Exception $e + * @return bool + */ + public function shouldReport(Exception $e) + { + return ! $this->shouldntReport($e); + } + + /** + * Determine if the exception is in the "do not report" list. + * + * @param \Exception $e + * @return bool + */ + protected function shouldntReport(Exception $e) + { + foreach ($this->dontReport as $type) { + if ($e instanceof $type) { + return true; + } + } + + return false; + } + + /** + * Render an exception into a response. + * + * @param \Illuminate\Http\Request $request + * @param \Exception $e + * @return \Illuminate\Http\Response + */ + public function render($request, Exception $e) + { + if ($this->isUnauthorizedException($e)) { + $e = new HttpException(403, $e->getMessage()); + } + + if ($this->isHttpException($e)) { + return $this->toIlluminateResponse($this->renderHttpException($e), $e); + } else { + return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e); + } + } + + /** + * Map exception into an illuminate response. + * + * @param \Symfony\Component\HttpFoundation\Response $response + * @param \Exception $e + * @return \Illuminate\Http\Response + */ + protected function toIlluminateResponse($response, Exception $e) + { + $response = new Response($response->getContent(), $response->getStatusCode(), $response->headers->all()); + + $response->exception = $e; + + return $response; + } + + /** + * Render an exception to the console. + * + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @param \Exception $e + * @return void + */ + public function renderForConsole($output, Exception $e) + { + (new ConsoleApplication)->renderException($e, $output); + } + + /** + * Render the given HttpException. + * + * @param \Symfony\Component\HttpKernel\Exception\HttpException $e + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + + if (view()->exists("errors.{$status}")) { + return response()->view("errors.{$status}", ['exception' => $e], $status); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * Convert the given exception into a Response instance. + * + * @param \Exception $e + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function convertExceptionToResponse(Exception $e) + { + return (new SymfonyDisplayer(config('app.debug')))->createResponse($e); + } + + /** + * Determine if the given exception is an access unauthorized exception. + * + * @param \Exception $e + * @return bool + */ + protected function isUnauthorizedException(Exception $e) + { + return $e instanceof UnauthorizedException; + } + + /** + * Determine if the given exception is an HTTP exception. + * + * @param \Exception $e + * @return bool + */ + protected function isHttpException(Exception $e) + { + return $e instanceof HttpException; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php new file mode 100644 index 0000000..e390609 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php @@ -0,0 +1,227 @@ +container->make(ValidationFactory::class); + + if (method_exists($this, 'validator')) { + return $this->container->call([$this, 'validator'], compact('factory')); + } + + return $factory->make( + $this->all(), $this->container->call([$this, 'rules']), $this->messages(), $this->attributes() + ); + } + + /** + * Handle a failed validation attempt. + * + * @param \Illuminate\Contracts\Validation\Validator $validator + * @return mixed + */ + protected function failedValidation(Validator $validator) + { + throw new HttpResponseException($this->response( + $this->formatErrors($validator) + )); + } + + /** + * Determine if the request passes the authorization check. + * + * @return bool + */ + protected function passesAuthorization() + { + if (method_exists($this, 'authorize')) { + return $this->container->call([$this, 'authorize']); + } + + return false; + } + + /** + * Handle a failed authorization attempt. + * + * @return mixed + */ + protected function failedAuthorization() + { + throw new HttpResponseException($this->forbiddenResponse()); + } + + /** + * Get the proper failed validation response for the request. + * + * @param array $errors + * @return \Symfony\Component\HttpFoundation\Response + */ + public function response(array $errors) + { + if ($this->ajax() || $this->wantsJson()) { + return new JsonResponse($errors, 422); + } + + return $this->redirector->to($this->getRedirectUrl()) + ->withInput($this->except($this->dontFlash)) + ->withErrors($errors, $this->errorBag); + } + + /** + * Get the response for a forbidden operation. + * + * @return \Illuminate\Http\Response + */ + public function forbiddenResponse() + { + return new Response('Forbidden', 403); + } + + /** + * Format the errors from the given Validator instance. + * + * @param \Illuminate\Contracts\Validation\Validator $validator + * @return array + */ + protected function formatErrors(Validator $validator) + { + return $validator->getMessageBag()->toArray(); + } + + /** + * Get the URL to redirect to on a validation error. + * + * @return string + */ + protected function getRedirectUrl() + { + $url = $this->redirector->getUrlGenerator(); + + if ($this->redirect) { + return $url->to($this->redirect); + } elseif ($this->redirectRoute) { + return $url->route($this->redirectRoute); + } elseif ($this->redirectAction) { + return $url->action($this->redirectAction); + } + + return $url->previous(); + } + + /** + * Set the Redirector instance. + * + * @param \Illuminate\Routing\Redirector $redirector + * @return \Illuminate\Foundation\Http\FormRequest + */ + public function setRedirector(Redirector $redirector) + { + $this->redirector = $redirector; + + return $this; + } + + /** + * Set the container implementation. + * + * @param \Illuminate\Container\Container $container + * @return $this + */ + public function setContainer(Container $container) + { + $this->container = $container; + + return $this; + } + + /** + * Set custom messages for validator errors. + * + * @return array + */ + public function messages() + { + return []; + } + + /** + * Set custom attributes for validator errors. + * + * @return array + */ + public function attributes() + { + return []; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php new file mode 100644 index 0000000..f18e020 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php @@ -0,0 +1,293 @@ +app = $app; + $this->router = $router; + + foreach ($this->routeMiddleware as $key => $middleware) { + $router->middleware($key, $middleware); + } + } + + /** + * Handle an incoming HTTP request. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function handle($request) + { + try { + $request->enableHttpMethodParameterOverride(); + + $response = $this->sendRequestThroughRouter($request); + } catch (Exception $e) { + $this->reportException($e); + + $response = $this->renderException($request, $e); + } catch (Throwable $e) { + $e = new FatalThrowableError($e); + + $this->reportException($e); + + $response = $this->renderException($request, $e); + } + + $this->app['events']->fire('kernel.handled', [$request, $response]); + + return $response; + } + + /** + * Send the given request through the middleware / router. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + protected function sendRequestThroughRouter($request) + { + $this->app->instance('request', $request); + + Facade::clearResolvedInstance('request'); + + $this->bootstrap(); + + return (new Pipeline($this->app)) + ->send($request) + ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) + ->then($this->dispatchToRouter()); + } + + /** + * Call the terminate method on any terminable middleware. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return void + */ + public function terminate($request, $response) + { + $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge( + $this->gatherRouteMiddlewares($request), + $this->middleware + ); + + foreach ($middlewares as $middleware) { + list($name, $parameters) = $this->parseMiddleware($middleware); + + $instance = $this->app->make($name); + + if (method_exists($instance, 'terminate')) { + $instance->terminate($request, $response); + } + } + + $this->app->terminate(); + } + + /** + * Gather the route middleware for the given request. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function gatherRouteMiddlewares($request) + { + if ($route = $request->route()) { + return $this->router->gatherRouteMiddlewares($route); + } + + return []; + } + + /** + * Parse a middleware string to get the name and parameters. + * + * @param string $middleware + * @return array + */ + protected function parseMiddleware($middleware) + { + list($name, $parameters) = array_pad(explode(':', $middleware, 2), 2, []); + + if (is_string($parameters)) { + $parameters = explode(',', $parameters); + } + + return [$name, $parameters]; + } + + /** + * Add a new middleware to beginning of the stack if it does not already exist. + * + * @param string $middleware + * @return $this + */ + public function prependMiddleware($middleware) + { + if (array_search($middleware, $this->middleware) === false) { + array_unshift($this->middleware, $middleware); + } + + return $this; + } + + /** + * Add a new middleware to end of the stack if it does not already exist. + * + * @param string $middleware + * @return $this + */ + public function pushMiddleware($middleware) + { + if (array_search($middleware, $this->middleware) === false) { + $this->middleware[] = $middleware; + } + + return $this; + } + + /** + * Bootstrap the application for HTTP requests. + * + * @return void + */ + public function bootstrap() + { + if (! $this->app->hasBeenBootstrapped()) { + $this->app->bootstrapWith($this->bootstrappers()); + } + } + + /** + * Get the route dispatcher callback. + * + * @return \Closure + */ + protected function dispatchToRouter() + { + return function ($request) { + $this->app->instance('request', $request); + + return $this->router->dispatch($request); + }; + } + + /** + * Determine if the kernel has a given middleware. + * + * @param string $middleware + * @return bool + */ + public function hasMiddleware($middleware) + { + return in_array($middleware, $this->middleware); + } + + /** + * Get the bootstrap classes for the application. + * + * @return array + */ + protected function bootstrappers() + { + return $this->bootstrappers; + } + + /** + * Report the exception to the exception handler. + * + * @param \Exception $e + * @return void + */ + protected function reportException(Exception $e) + { + $this->app['Illuminate\Contracts\Debug\ExceptionHandler']->report($e); + } + + /** + * Render the exception to a response. + * + * @param \Illuminate\Http\Request $request + * @param \Exception $e + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function renderException($request, Exception $e) + { + return $this->app['Illuminate\Contracts\Debug\ExceptionHandler']->render($request, $e); + } + + /** + * Get the Laravel application instance. + * + * @return \Illuminate\Contracts\Foundation\Application + */ + public function getApplication() + { + return $this->app; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php new file mode 100644 index 0000000..a5b0786 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php @@ -0,0 +1,46 @@ +app = $app; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + */ + public function handle($request, Closure $next) + { + if ($this->app->isDownForMaintenance()) { + throw new HttpException(503); + } + + return $next($request); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php new file mode 100644 index 0000000..9caf7fc --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php @@ -0,0 +1,131 @@ +encrypter = $encrypter; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Illuminate\Session\TokenMismatchException + */ + public function handle($request, Closure $next) + { + if ($this->isReading($request) || $this->shouldPassThrough($request) || $this->tokensMatch($request)) { + return $this->addCookieToResponse($request, $next($request)); + } + + throw new TokenMismatchException; + } + + /** + * Determine if the request has a URI that should pass through CSRF verification. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + protected function shouldPassThrough($request) + { + foreach ($this->except as $except) { + if ($except !== '/') { + $except = trim($except, '/'); + } + + if ($request->is($except)) { + return true; + } + } + + return false; + } + + /** + * Determine if the session and input CSRF tokens match. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + protected function tokensMatch($request) + { + $sessionToken = $request->session()->token(); + + $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN'); + + if (! $token && $header = $request->header('X-XSRF-TOKEN')) { + $token = $this->encrypter->decrypt($header); + } + + if (! is_string($sessionToken) || ! is_string($token)) { + return false; + } + + return Str::equals($sessionToken, $token); + } + + /** + * Add the CSRF token to the response cookies. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return \Illuminate\Http\Response + */ + protected function addCookieToResponse($request, $response) + { + $config = config('session'); + + $response->headers->setCookie( + new Cookie( + 'XSRF-TOKEN', $request->session()->token(), time() + 60 * 120, + $config['path'], $config['domain'], $config['secure'], false + ) + ); + + return $response; + } + + /** + * Determine if the HTTP request uses a ‘read’ verb. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + protected function isReading($request) + { + return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyPostSize.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyPostSize.php new file mode 100644 index 0000000..a3e0f55 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyPostSize.php @@ -0,0 +1,51 @@ +server('CONTENT_LENGTH') > $this->getPostMaxSize()) { + throw new PostTooLargeException; + } + + return $next($request); + } + + /** + * Determine the server 'post_max_size' as bytes. + * + * @return int + */ + protected function getPostMaxSize() + { + $postMaxSize = ini_get('post_max_size'); + + switch (substr($postMaxSize, -1)) { + case 'M': + case 'm': + return (int) $postMaxSize * 1048576; + case 'K': + case 'k': + return (int) $postMaxSize * 1024; + case 'G': + case 'g': + return (int) $postMaxSize * 1073741824; + } + + return (int) $postMaxSize; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Inspiring.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Inspiring.php new file mode 100644 index 0000000..7f56c4f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Inspiring.php @@ -0,0 +1,32 @@ +random(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php new file mode 100644 index 0000000..6048de8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php @@ -0,0 +1,205 @@ +app = $app; + $this->files = $files; + $this->manifestPath = $manifestPath; + } + + /** + * Register the application service providers. + * + * @param array $providers + * @return void + */ + public function load(array $providers) + { + $manifest = $this->loadManifest(); + + // First we will load the service manifest, which contains information on all + // service providers registered with the application and which services it + // provides. This is used to know which services are "deferred" loaders. + if ($this->shouldRecompile($manifest, $providers)) { + $manifest = $this->compileManifest($providers); + } + + // Next, we will register events to load the providers for each of the events + // that it has requested. This allows the service provider to defer itself + // while still getting automatically loaded when a certain event occurs. + foreach ($manifest['when'] as $provider => $events) { + $this->registerLoadEvents($provider, $events); + } + + // We will go ahead and register all of the eagerly loaded providers with the + // application so their services can be registered with the application as + // a provided service. Then we will set the deferred service list on it. + foreach ($manifest['eager'] as $provider) { + $this->app->register($this->createProvider($provider)); + } + + $this->app->addDeferredServices($manifest['deferred']); + } + + /** + * Register the load events for the given provider. + * + * @param string $provider + * @param array $events + * @return void + */ + protected function registerLoadEvents($provider, array $events) + { + if (count($events) < 1) { + return; + } + + $app = $this->app; + + $app->make('events')->listen($events, function () use ($app, $provider) { + $app->register($provider); + }); + } + + /** + * Compile the application manifest file. + * + * @param array $providers + * @return array + */ + protected function compileManifest($providers) + { + // The service manifest should contain a list of all of the providers for + // the application so we can compare it on each request to the service + // and determine if the manifest should be recompiled or is current. + $manifest = $this->freshManifest($providers); + + foreach ($providers as $provider) { + $instance = $this->createProvider($provider); + + // When recompiling the service manifest, we will spin through each of the + // providers and check if it's a deferred provider or not. If so we'll + // add it's provided services to the manifest and note the provider. + if ($instance->isDeferred()) { + foreach ($instance->provides() as $service) { + $manifest['deferred'][$service] = $provider; + } + + $manifest['when'][$provider] = $instance->when(); + } + + // If the service providers are not deferred, we will simply add it to an + // array of eagerly loaded providers that will get registered on every + // request to this application instead of "lazy" loading every time. + else { + $manifest['eager'][] = $provider; + } + } + + return $this->writeManifest($manifest); + } + + /** + * Create a new provider instance. + * + * @param string $provider + * @return \Illuminate\Support\ServiceProvider + */ + public function createProvider($provider) + { + return new $provider($this->app); + } + + /** + * Determine if the manifest should be compiled. + * + * @param array $manifest + * @param array $providers + * @return bool + */ + public function shouldRecompile($manifest, $providers) + { + return is_null($manifest) || $manifest['providers'] != $providers; + } + + /** + * Load the service provider manifest JSON file. + * + * @return array|null + */ + public function loadManifest() + { + // The service manifest is a file containing a JSON representation of every + // service provided by the application and whether its provider is using + // deferred loading or should be eagerly loaded on each request to us. + if ($this->files->exists($this->manifestPath)) { + $manifest = json_decode($this->files->get($this->manifestPath), true); + + if ($manifest) { + return array_merge(['when' => []], $manifest); + } + } + } + + /** + * Write the service manifest file to disk. + * + * @param array $manifest + * @return array + */ + public function writeManifest($manifest) + { + $this->files->put( + $this->manifestPath, json_encode($manifest, JSON_PRETTY_PRINT) + ); + + return array_merge(['when' => []], $manifest); + } + + /** + * Create a fresh service manifest data structure. + * + * @param array $providers + * @return array + */ + protected function freshManifest(array $providers) + { + return ['providers' => $providers, 'eager' => [], 'deferred' => []]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php new file mode 100644 index 0000000..f74448d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -0,0 +1,455 @@ + 'command.app.name', + 'ClearCompiled' => 'command.clear-compiled', + 'CommandMake' => 'command.command.make', + 'ConfigCache' => 'command.config.cache', + 'ConfigClear' => 'command.config.clear', + 'ConsoleMake' => 'command.console.make', + 'EventGenerate' => 'command.event.generate', + 'EventMake' => 'command.event.make', + 'Down' => 'command.down', + 'Environment' => 'command.environment', + 'HandlerCommand' => 'command.handler.command', + 'HandlerEvent' => 'command.handler.event', + 'JobMake' => 'command.job.make', + 'KeyGenerate' => 'command.key.generate', + 'ListenerMake' => 'command.listener.make', + 'ModelMake' => 'command.model.make', + 'Optimize' => 'command.optimize', + 'PolicyMake' => 'command.policy.make', + 'ProviderMake' => 'command.provider.make', + 'RequestMake' => 'command.request.make', + 'RouteCache' => 'command.route.cache', + 'RouteClear' => 'command.route.clear', + 'RouteList' => 'command.route.list', + 'Serve' => 'command.serve', + 'TestMake' => 'command.test.make', + 'Tinker' => 'command.tinker', + 'Up' => 'command.up', + 'VendorPublish' => 'command.vendor.publish', + 'ViewClear' => 'command.view.clear', + ]; + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + foreach (array_keys($this->commands) as $command) { + $method = "register{$command}Command"; + + call_user_func_array([$this, $method], []); + } + + $this->commands(array_values($this->commands)); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerAppNameCommand() + { + $this->app->singleton('command.app.name', function ($app) { + return new AppNameCommand($app['composer'], $app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerClearCompiledCommand() + { + $this->app->singleton('command.clear-compiled', function () { + return new ClearCompiledCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerCommandMakeCommand() + { + $this->app->singleton('command.command.make', function ($app) { + return new CommandMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerConfigCacheCommand() + { + $this->app->singleton('command.config.cache', function ($app) { + return new ConfigCacheCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerConfigClearCommand() + { + $this->app->singleton('command.config.clear', function ($app) { + return new ConfigClearCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerConsoleMakeCommand() + { + $this->app->singleton('command.console.make', function ($app) { + return new ConsoleMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerEventGenerateCommand() + { + $this->app->singleton('command.event.generate', function () { + return new EventGenerateCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerEventMakeCommand() + { + $this->app->singleton('command.event.make', function ($app) { + return new EventMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerDownCommand() + { + $this->app->singleton('command.down', function () { + return new DownCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerEnvironmentCommand() + { + $this->app->singleton('command.environment', function () { + return new EnvironmentCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerHandlerCommandCommand() + { + $this->app->singleton('command.handler.command', function ($app) { + return new HandlerCommandCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerHandlerEventCommand() + { + $this->app->singleton('command.handler.event', function ($app) { + return new HandlerEventCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerJobMakeCommand() + { + $this->app->singleton('command.job.make', function ($app) { + return new JobMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerKeyGenerateCommand() + { + $this->app->singleton('command.key.generate', function () { + return new KeyGenerateCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerListenerMakeCommand() + { + $this->app->singleton('command.listener.make', function ($app) { + return new ListenerMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerModelMakeCommand() + { + $this->app->singleton('command.model.make', function ($app) { + return new ModelMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerOptimizeCommand() + { + $this->app->singleton('command.optimize', function ($app) { + return new OptimizeCommand($app['composer']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerProviderMakeCommand() + { + $this->app->singleton('command.provider.make', function ($app) { + return new ProviderMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerRequestMakeCommand() + { + $this->app->singleton('command.request.make', function ($app) { + return new RequestMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerRouteCacheCommand() + { + $this->app->singleton('command.route.cache', function ($app) { + return new RouteCacheCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerRouteClearCommand() + { + $this->app->singleton('command.route.clear', function ($app) { + return new RouteClearCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerRouteListCommand() + { + $this->app->singleton('command.route.list', function ($app) { + return new RouteListCommand($app['router']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerServeCommand() + { + $this->app->singleton('command.serve', function () { + return new ServeCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerTestMakeCommand() + { + $this->app->singleton('command.test.make', function ($app) { + return new TestMakeCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerTinkerCommand() + { + $this->app->singleton('command.tinker', function () { + return new TinkerCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerUpCommand() + { + $this->app->singleton('command.up', function () { + return new UpCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerVendorPublishCommand() + { + $this->app->singleton('command.vendor.publish', function ($app) { + return new VendorPublishCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerViewClearCommand() + { + $this->app->singleton('command.view.clear', function ($app) { + return new ViewClearCommand($app['files']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerPolicyMakeCommand() + { + $this->app->singleton('command.policy.make', function ($app) { + return new PolicyMakeCommand($app['files']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array_values($this->commands); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ComposerServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ComposerServiceProvider.php new file mode 100644 index 0000000..df68d3c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ComposerServiceProvider.php @@ -0,0 +1,38 @@ +app->singleton('composer', function ($app) { + return new Composer($app['files'], $app->basePath()); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['composer']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ConsoleSupportServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ConsoleSupportServiceProvider.php new file mode 100644 index 0000000..9dc4a20 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ConsoleSupportServiceProvider.php @@ -0,0 +1,31 @@ +app['events']->listen('router.matched', function () { + $this->app->resolving(function (FormRequest $request, $app) { + $this->initializeRequest($request, $app['request']); + + $request->setContainer($app)->setRedirector($app->make(Redirector::class)); + }); + }); + } + + /** + * Initialize the form request with data from the given request. + * + * @param \Illuminate\Foundation\Http\FormRequest $form + * @param \Symfony\Component\HttpFoundation\Request $current + * @return void + */ + protected function initializeRequest(FormRequest $form, Request $current) + { + $files = $current->files->all(); + + $files = is_array($files) ? array_filter($files) : $files; + + $form->initialize( + $current->query->all(), $current->request->all(), $current->attributes->all(), + $current->cookies->all(), $files, $current->server->all(), $current->getContent() + ); + + if ($session = $current->getSession()) { + $form->setSession($session); + } + + $form->setUserResolver($current->getUserResolver()); + + $form->setRouteResolver($current->getRouteResolver()); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php new file mode 100644 index 0000000..f1f4837 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php @@ -0,0 +1,17 @@ +policies as $key => $value) { + $gate->policy($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function register() + { + // + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php new file mode 100644 index 0000000..602a882 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php @@ -0,0 +1,60 @@ +listen as $event => $listeners) { + foreach ($listeners as $listener) { + $events->listen($event, $listener); + } + } + + foreach ($this->subscribe as $subscriber) { + $events->subscribe($subscriber); + } + } + + /** + * {@inheritdoc} + */ + public function register() + { + // + } + + /** + * Get the events and handlers. + * + * @return array + */ + public function listens() + { + return $this->listen; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..21a69cb --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php @@ -0,0 +1,115 @@ +setRootControllerNamespace(); + + if ($this->app->routesAreCached()) { + $this->loadCachedRoutes(); + } else { + $this->loadRoutes(); + + $this->app->booted(function () use ($router) { + $router->getRoutes()->refreshNameLookups(); + }); + } + } + + /** + * Set the root controller namespace for the application. + * + * @return void + */ + protected function setRootControllerNamespace() + { + if (is_null($this->namespace)) { + return; + } + + $this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace); + } + + /** + * Load the cached routes for the application. + * + * @return void + */ + protected function loadCachedRoutes() + { + $this->app->booted(function () { + require $this->app->getCachedRoutesPath(); + }); + } + + /** + * Load the application routes. + * + * @return void + */ + protected function loadRoutes() + { + $this->app->call([$this, 'map']); + } + + /** + * Load the standard routes file for the application. + * + * @param string $path + * @return mixed + */ + protected function loadRoutesFrom($path) + { + $router = $this->app->make(Router::class); + + if (is_null($this->namespace)) { + return require $path; + } + + $router->group(['namespace' => $this->namespace], function (Router $router) use ($path) { + require $path; + }); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + // + } + + /** + * Pass dynamic methods onto the router instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->app->make(Router::class), $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php new file mode 100644 index 0000000..f8d4450 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php @@ -0,0 +1,314 @@ +app = $this->createApplication(); + } + + /** + * Register an instance of an object in the container. + * + * @param string $abstract + * @param object $instance + * @return object + */ + protected function instance($abstract, $instance) + { + $this->app->instance($abstract, $instance); + + return $instance; + } + + /** + * Specify a list of events that should be fired for the given operation. + * + * These events will be mocked, so that handlers will not actually be executed. + * + * @param array|mixed $events + * @return $this + */ + public function expectsEvents($events) + { + $events = is_array($events) ? $events : func_get_args(); + + $mock = Mockery::spy('Illuminate\Contracts\Events\Dispatcher'); + + $mock->shouldReceive('fire')->andReturnUsing(function ($called) use (&$events) { + foreach ($events as $key => $event) { + if ((is_string($called) && $called === $event) || + (is_string($called) && is_subclass_of($called, $event)) || + (is_object($called) && $called instanceof $event)) { + unset($events[$key]); + } + } + }); + + $this->beforeApplicationDestroyed(function () use (&$events) { + if ($events) { + throw new Exception( + 'The following events were not fired: ['.implode(', ', $events).']' + ); + } + }); + + $this->app->instance('events', $mock); + + return $this; + } + + /** + * Mock the event dispatcher so all events are silenced. + * + * @return $this + */ + protected function withoutEvents() + { + $mock = Mockery::mock('Illuminate\Contracts\Events\Dispatcher'); + + $mock->shouldReceive('fire'); + + $this->app->instance('events', $mock); + + return $this; + } + + /** + * Specify a list of jobs that should be dispatched for the given operation. + * + * These jobs will be mocked, so that handlers will not actually be executed. + * + * @param array|mixed $jobs + * @return $this + */ + protected function expectsJobs($jobs) + { + $jobs = is_array($jobs) ? $jobs : func_get_args(); + + $mock = Mockery::mock('Illuminate\Bus\Dispatcher[dispatch]', [$this->app]); + + foreach ($jobs as $job) { + $mock->shouldReceive('dispatch')->atLeast()->once() + ->with(Mockery::type($job)); + } + + $this->app->instance( + 'Illuminate\Contracts\Bus\Dispatcher', $mock + ); + + return $this; + } + + /** + * Set the session to the given array. + * + * @param array $data + * @return $this + */ + public function withSession(array $data) + { + $this->session($data); + + return $this; + } + + /** + * Set the session to the given array. + * + * @param array $data + * @return void + */ + public function session(array $data) + { + $this->startSession(); + + foreach ($data as $key => $value) { + $this->app['session']->put($key, $value); + } + } + + /** + * Start the session for the application. + * + * @return void + */ + protected function startSession() + { + if (! $this->app['session']->isStarted()) { + $this->app['session']->start(); + } + } + + /** + * Flush all of the current session data. + * + * @return void + */ + public function flushSession() + { + $this->startSession(); + + $this->app['session']->flush(); + } + + /** + * Disable middleware for the test. + * + * @return $this + */ + public function withoutMiddleware() + { + $this->app->instance('middleware.disable', true); + + return $this; + } + + /** + * Set the currently logged in user for the application. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string|null $driver + * @return $this + */ + public function actingAs(UserContract $user, $driver = null) + { + $this->be($user, $driver); + + return $this; + } + + /** + * Set the currently logged in user for the application. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string|null $driver + * @return void + */ + public function be(UserContract $user, $driver = null) + { + $this->app['auth']->driver($driver)->setUser($user); + } + + /** + * Assert that a given where condition exists in the database. + * + * @param string $table + * @param array $data + * @param string $connection + * @return $this + */ + protected function seeInDatabase($table, array $data, $connection = null) + { + $database = $this->app->make('db'); + + $connection = $connection ?: $database->getDefaultConnection(); + + $count = $database->connection($connection)->table($table)->where($data)->count(); + + $this->assertGreaterThan(0, $count, sprintf( + 'Unable to find row in database table [%s] that matched attributes [%s].', $table, json_encode($data) + )); + + return $this; + } + + /** + * Assert that a given where condition does not exist in the database. + * + * @param string $table + * @param array $data + * @param string $connection + * @return $this + */ + protected function missingFromDatabase($table, array $data, $connection = null) + { + return $this->notSeeInDatabase($table, $data, $connection); + } + + /** + * Assert that a given where condition does not exist in the database. + * + * @param string $table + * @param array $data + * @param string $connection + * @return $this + */ + protected function dontSeeInDatabase($table, array $data, $connection = null) + { + return $this->notSeeInDatabase($table, $data, $connection); + } + + /** + * Assert that a given where condition does not exist in the database. + * + * @param string $table + * @param array $data + * @param string $connection + * @return $this + */ + protected function notSeeInDatabase($table, array $data, $connection = null) + { + $database = $this->app->make('db'); + + $connection = $connection ?: $database->getDefaultConnection(); + + $count = $database->connection($connection)->table($table)->where($data)->count(); + + $this->assertEquals(0, $count, sprintf( + 'Found unexpected records in database table [%s] that matched attributes [%s].', $table, json_encode($data) + )); + + return $this; + } + + /** + * Seed a given database connection. + * + * @param string $class + * @return void + */ + public function seed($class = 'DatabaseSeeder') + { + $this->artisan('db:seed', ['--class' => $class]); + } + + /** + * Call artisan command and return code. + * + * @param string $command + * @param array $parameters + * @return int + */ + public function artisan($command, $parameters = []) + { + return $this->code = $this->app['Illuminate\Contracts\Console\Kernel']->call($command, $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/AssertionsTrait.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/AssertionsTrait.php new file mode 100644 index 0000000..26b288e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/AssertionsTrait.php @@ -0,0 +1,203 @@ +response->getStatusCode(); + + return PHPUnit::assertTrue($this->response->isOk(), "Expected status code 200, got {$actual}."); + } + + /** + * Assert that the client response has a given code. + * + * @param int $code + * @return void + */ + public function assertResponseStatus($code) + { + $actual = $this->response->getStatusCode(); + + return PHPUnit::assertEquals($code, $this->response->getStatusCode(), "Expected status code {$code}, got {$actual}."); + } + + /** + * Assert that the response view has a given piece of bound data. + * + * @param string|array $key + * @param mixed $value + * @return void + */ + public function assertViewHas($key, $value = null) + { + if (is_array($key)) { + return $this->assertViewHasAll($key); + } + + if (! isset($this->response->original) || ! $this->response->original instanceof View) { + return PHPUnit::assertTrue(false, 'The response was not a view.'); + } + + if (is_null($value)) { + PHPUnit::assertArrayHasKey($key, $this->response->original->getData()); + } else { + PHPUnit::assertEquals($value, $this->response->original->$key); + } + } + + /** + * Assert that the view has a given list of bound data. + * + * @param array $bindings + * @return void + */ + public function assertViewHasAll(array $bindings) + { + foreach ($bindings as $key => $value) { + if (is_int($key)) { + $this->assertViewHas($value); + } else { + $this->assertViewHas($key, $value); + } + } + } + + /** + * Assert that the response view is missing a piece of bound data. + * + * @param string $key + * @return void + */ + public function assertViewMissing($key) + { + if (! isset($this->response->original) || ! $this->response->original instanceof View) { + return PHPUnit::assertTrue(false, 'The response was not a view.'); + } + + PHPUnit::assertArrayNotHasKey($key, $this->response->original->getData()); + } + + /** + * Assert whether the client was redirected to a given URI. + * + * @param string $uri + * @param array $with + * @return void + */ + public function assertRedirectedTo($uri, $with = []) + { + PHPUnit::assertInstanceOf('Illuminate\Http\RedirectResponse', $this->response); + + PHPUnit::assertEquals($this->app['url']->to($uri), $this->response->headers->get('Location')); + + $this->assertSessionHasAll($with); + } + + /** + * Assert whether the client was redirected to a given route. + * + * @param string $name + * @param array $parameters + * @param array $with + * @return void + */ + public function assertRedirectedToRoute($name, $parameters = [], $with = []) + { + $this->assertRedirectedTo($this->app['url']->route($name, $parameters), $with); + } + + /** + * Assert whether the client was redirected to a given action. + * + * @param string $name + * @param array $parameters + * @param array $with + * @return void + */ + public function assertRedirectedToAction($name, $parameters = [], $with = []) + { + $this->assertRedirectedTo($this->app['url']->action($name, $parameters), $with); + } + + /** + * Assert that the session has a given value. + * + * @param string|array $key + * @param mixed $value + * @return void + */ + public function assertSessionHas($key, $value = null) + { + if (is_array($key)) { + return $this->assertSessionHasAll($key); + } + + if (is_null($value)) { + PHPUnit::assertTrue($this->app['session.store']->has($key), "Session missing key: $key"); + } else { + PHPUnit::assertEquals($value, $this->app['session.store']->get($key)); + } + } + + /** + * Assert that the session has a given list of values. + * + * @param array $bindings + * @return void + */ + public function assertSessionHasAll(array $bindings) + { + foreach ($bindings as $key => $value) { + if (is_int($key)) { + $this->assertSessionHas($value); + } else { + $this->assertSessionHas($key, $value); + } + } + } + + /** + * Assert that the session has errors bound. + * + * @param string|array $bindings + * @param mixed $format + * @return void + */ + public function assertSessionHasErrors($bindings = [], $format = null) + { + $this->assertSessionHas('errors'); + + $bindings = (array) $bindings; + + $errors = $this->app['session.store']->get('errors'); + + foreach ($bindings as $key => $value) { + if (is_int($key)) { + PHPUnit::assertTrue($errors->has($value), "Session missing error: $value"); + } else { + PHPUnit::assertContains($value, $errors->get($key, $format)); + } + } + } + + /** + * Assert that the session has old input. + * + * @return void + */ + public function assertHasOldInput() + { + $this->assertSessionHas('_old_input'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/CrawlerTrait.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/CrawlerTrait.php new file mode 100644 index 0000000..c3c59dd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/CrawlerTrait.php @@ -0,0 +1,520 @@ + mb_strlen($content, '8bit'), + 'CONTENT_TYPE' => 'application/json', + 'Accept' => 'application/json', + ], $headers); + + $this->call( + $method, $uri, [], [], [], $this->transformHeadersToServerVars($headers), $content + ); + + return $this; + } + + /** + * Visit the given URI with a GET request. + * + * @param string $uri + * @param array $headers + * @return $this + */ + public function get($uri, array $headers = []) + { + $server = $this->transformHeadersToServerVars($headers); + + $this->call('GET', $uri, [], [], [], $server); + + return $this; + } + + /** + * Visit the given URI with a POST request. + * + * @param string $uri + * @param array $data + * @param array $headers + * @return $this + */ + public function post($uri, array $data = [], array $headers = []) + { + $server = $this->transformHeadersToServerVars($headers); + + $this->call('POST', $uri, $data, [], [], $server); + + return $this; + } + + /** + * Visit the given URI with a PUT request. + * + * @param string $uri + * @param array $data + * @param array $headers + * @return $this + */ + public function put($uri, array $data = [], array $headers = []) + { + $server = $this->transformHeadersToServerVars($headers); + + $this->call('PUT', $uri, $data, [], [], $server); + + return $this; + } + + /** + * Visit the given URI with a PATCH request. + * + * @param string $uri + * @param array $data + * @param array $headers + * @return $this + */ + public function patch($uri, array $data = [], array $headers = []) + { + $server = $this->transformHeadersToServerVars($headers); + + $this->call('PATCH', $uri, $data, [], [], $server); + + return $this; + } + + /** + * Visit the given URI with a DELETE request. + * + * @param string $uri + * @param array $data + * @param array $headers + * @return $this + */ + public function delete($uri, array $data = [], array $headers = []) + { + $server = $this->transformHeadersToServerVars($headers); + + $this->call('DELETE', $uri, $data, [], [], $server); + + return $this; + } + + /** + * Send the given request through the application. + * + * This method allows you to fully customize the entire Request object. + * + * @param \Illuminate\Http\Request $request + * @return $this + */ + public function handle(Request $request) + { + $this->currentUri = $request->fullUrl(); + + $this->response = $this->app->prepareResponse($this->app->handle($request)); + + return $this; + } + + /** + * Assert that the response contains JSON. + * + * @param array|null $data + * @return $this + */ + protected function shouldReturnJson(array $data = null) + { + return $this->receiveJson($data); + } + + /** + * Assert that the response contains JSON. + * + * @param array|null $data + * @return $this|null + */ + protected function receiveJson($data = null) + { + $this->seeJson(); + + if (! is_null($data)) { + return $this->seeJson($data); + } + } + + /** + * Assert that the response contains an exact JSON array. + * + * @param array $data + * @return $this + */ + public function seeJsonEquals(array $data) + { + $actual = json_encode(Arr::sortRecursive( + json_decode($this->response->getContent(), true) + )); + + $this->assertEquals(json_encode(Arr::sortRecursive($data)), $actual); + + return $this; + } + + /** + * Assert that the response contains JSON. + * + * @param array|null $data + * @param bool $negate + * @return $this + */ + public function seeJson(array $data = null, $negate = false) + { + if (is_null($data)) { + $this->assertJson( + $this->response->getContent(), "JSON was not returned from [{$this->currentUri}]." + ); + + return $this; + } + + return $this->seeJsonContains($data, $negate); + } + + /** + * Assert that the response doesn't contain JSON. + * + * @param array|null $data + * @return $this + */ + public function dontSeeJson(array $data = null) + { + return $this->seeJson($data, true); + } + + /** + * Assert that the response contains the given JSON. + * + * @param array $data + * @param bool $negate + * @return $this + */ + protected function seeJsonContains(array $data, $negate = false) + { + $method = $negate ? 'assertFalse' : 'assertTrue'; + + $actual = json_decode($this->response->getContent(), true); + + if (is_null($actual) || $actual === false) { + return $this->fail('Invalid JSON was returned from the route. Perhaps an exception was thrown?'); + } + + $actual = json_encode(Arr::sortRecursive( + (array) $actual + )); + + foreach (Arr::sortRecursive($data) as $key => $value) { + $expected = $this->formatToExpectedJson($key, $value); + + $this->{$method}( + Str::contains($actual, $expected), + ($negate ? 'Found unexpected' : 'Unable to find')." JSON fragment [{$expected}] within [{$actual}]." + ); + } + + return $this; + } + + /** + * Format the given key and value into a JSON string for expectation checks. + * + * @param string $key + * @param mixed $value + * @return string + */ + protected function formatToExpectedJson($key, $value) + { + $expected = json_encode([$key => $value]); + + if (Str::startsWith($expected, '{')) { + $expected = substr($expected, 1); + } + + if (Str::endsWith($expected, '}')) { + $expected = substr($expected, 0, -1); + } + + return $expected; + } + + /** + * Asserts that the status code of the response matches the given code. + * + * @param int $status + * @return $this + */ + protected function seeStatusCode($status) + { + $this->assertEquals($status, $this->response->getStatusCode()); + + return $this; + } + + /** + * Asserts that the response contains the given header and equals the optional value. + * + * @param string $headerName + * @param mixed $value + * @return $this + */ + protected function seeHeader($headerName, $value = null) + { + $headers = $this->response->headers; + + $this->assertTrue($headers->has($headerName), "Header [{$headerName}] not present on response."); + + if (! is_null($value)) { + $this->assertEquals( + $headers->get($headerName), $value, + "Header [{$headerName}] was found, but value [{$headers->get($headerName)}] does not match [{$value}]." + ); + } + + return $this; + } + + /** + * Asserts that the response contains the given cookie and equals the optional value. + * + * @param string $cookieName + * @param mixed $value + * @return $this + */ + protected function seeCookie($cookieName, $value = null) + { + $headers = $this->response->headers; + + $exist = false; + + foreach ($headers->getCookies() as $cookie) { + if ($cookie->getName() === $cookieName) { + $exist = true; + break; + } + } + + $this->assertTrue($exist, "Cookie [{$cookieName}] not present on response."); + + if (! is_null($value)) { + $this->assertEquals( + $cookie->getValue(), $value, + "Cookie [{$cookieName}] was found, but value [{$cookie->getValue()}] does not match [{$value}]." + ); + } + + return $this; + } + + /** + * Define a set of server variables to be sent with the requests. + * + * @param array $server + * @return $this + */ + protected function withServerVariables(array $server) + { + $this->serverVariables = $server; + + return $this; + } + + /** + * Call the given URI and return the Response. + * + * @param string $method + * @param string $uri + * @param array $parameters + * @param array $cookies + * @param array $files + * @param array $server + * @param string $content + * @return \Illuminate\Http\Response + */ + public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null) + { + $kernel = $this->app->make('Illuminate\Contracts\Http\Kernel'); + + $this->currentUri = $this->prepareUrlForRequest($uri); + + $request = Request::create( + $this->currentUri, $method, $parameters, + $cookies, $files, array_replace($this->serverVariables, $server), $content + ); + + $response = $kernel->handle($request); + + $kernel->terminate($request, $response); + + return $this->response = $response; + } + + /** + * Call the given HTTPS URI and return the Response. + * + * @param string $method + * @param string $uri + * @param array $parameters + * @param array $cookies + * @param array $files + * @param array $server + * @param string $content + * @return \Illuminate\Http\Response + */ + public function callSecure($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null) + { + $uri = $this->app['url']->secure(ltrim($uri, '/')); + + return $this->response = $this->call($method, $uri, $parameters, $cookies, $files, $server, $content); + } + + /** + * Call a controller action and return the Response. + * + * @param string $method + * @param string $action + * @param array $wildcards + * @param array $parameters + * @param array $cookies + * @param array $files + * @param array $server + * @param string $content + * @return \Illuminate\Http\Response + */ + public function action($method, $action, $wildcards = [], $parameters = [], $cookies = [], $files = [], $server = [], $content = null) + { + $uri = $this->app['url']->action($action, $wildcards, true); + + return $this->response = $this->call($method, $uri, $parameters, $cookies, $files, $server, $content); + } + + /** + * Call a named route and return the Response. + * + * @param string $method + * @param string $name + * @param array $routeParameters + * @param array $parameters + * @param array $cookies + * @param array $files + * @param array $server + * @param string $content + * @return \Illuminate\Http\Response + */ + public function route($method, $name, $routeParameters = [], $parameters = [], $cookies = [], $files = [], $server = [], $content = null) + { + $uri = $this->app['url']->route($name, $routeParameters); + + return $this->response = $this->call($method, $uri, $parameters, $cookies, $files, $server, $content); + } + + /** + * Turn the given URI into a fully qualified URL. + * + * @param string $uri + * @return string + */ + protected function prepareUrlForRequest($uri) + { + if (Str::startsWith($uri, '/')) { + $uri = substr($uri, 1); + } + + if (! Str::startsWith($uri, 'http')) { + $uri = $this->baseUrl.'/'.$uri; + } + + return trim($uri, '/'); + } + + /** + * Transform headers array to array of $_SERVER vars with HTTP_* format. + * + * @param array $headers + * @return array + */ + protected function transformHeadersToServerVars(array $headers) + { + $server = []; + $prefix = 'HTTP_'; + + foreach ($headers as $name => $value) { + $name = strtr(strtoupper($name), '-', '_'); + + if (! starts_with($name, $prefix) && $name != 'CONTENT_TYPE') { + $name = $prefix.$name; + } + + $server[$name] = $value; + } + + return $server; + } + + /** + * Dump the content from the last response. + * + * @return void + */ + public function dump() + { + $content = $this->response->getContent(); + + $json = json_decode($content); + + if (json_last_error() === JSON_ERROR_NONE) { + $content = $json; + } + + dd($content); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/DatabaseMigrations.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/DatabaseMigrations.php new file mode 100644 index 0000000..d53b34e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/DatabaseMigrations.php @@ -0,0 +1,18 @@ +artisan('migrate'); + + $this->beforeApplicationDestroyed(function () { + $this->artisan('migrate:rollback'); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/DatabaseTransactions.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/DatabaseTransactions.php new file mode 100644 index 0000000..e580f5a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/DatabaseTransactions.php @@ -0,0 +1,18 @@ +app->make('db')->beginTransaction(); + + $this->beforeApplicationDestroyed(function () { + $this->app->make('db')->rollBack(); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/HttpException.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/HttpException.php new file mode 100644 index 0000000..67d2b2f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/HttpException.php @@ -0,0 +1,10 @@ +makeRequest('GET', $uri); + } + + /** + * Make a request to the application and create a Crawler instance. + * + * @param string $method + * @param string $uri + * @param array $parameters + * @param array $cookies + * @param array $files + * @return $this + */ + protected function makeRequest($method, $uri, $parameters = [], $cookies = [], $files = []) + { + $uri = $this->prepareUrlForRequest($uri); + + $this->call($method, $uri, $parameters, $cookies, $files); + + $this->clearInputs()->followRedirects()->assertPageLoaded($uri); + + $this->currentUri = $this->app->make('request')->fullUrl(); + + $this->crawler = new Crawler($this->response->getContent(), $this->currentUri); + + return $this; + } + + /** + * Make a request to the application using the given form. + * + * @param \Symfony\Component\DomCrawler\Form $form + * @param array $uploads + * @return $this + */ + protected function makeRequestUsingForm(Form $form, array $uploads = []) + { + $files = $this->convertUploadsForTesting($form, $uploads); + + return $this->makeRequest( + $form->getMethod(), $form->getUri(), $this->extractParametersFromForm($form), [], $files + ); + } + + /** + * Extract the parameters from the given form. + * + * @param \Symfony\Component\DomCrawler\Form $form + * @return array + */ + protected function extractParametersFromForm(Form $form) + { + parse_str(http_build_query($form->getValues()), $parameters); + + return $parameters; + } + + /** + * Follow redirects from the last response. + * + * @return $this + */ + protected function followRedirects() + { + while ($this->response->isRedirect()) { + $this->makeRequest('GET', $this->response->getTargetUrl()); + } + + return $this; + } + + /** + * Clear the inputs for the current page. + * + * @return $this + */ + protected function clearInputs() + { + $this->inputs = []; + + $this->uploads = []; + + return $this; + } + + /** + * Assert that the current page matches a given URI. + * + * @param string $uri + * @return $this + */ + protected function seePageIs($uri) + { + $this->assertPageLoaded($uri = $this->prepareUrlForRequest($uri)); + + $this->assertEquals( + $uri, $this->currentUri, "Did not land on expected page [{$uri}].\n" + ); + + return $this; + } + + /** + * Assert that a given page successfully loaded. + * + * @param string $uri + * @param string|null $message + * @return void + */ + protected function assertPageLoaded($uri, $message = null) + { + $status = $this->response->getStatusCode(); + + try { + $this->assertEquals(200, $status); + } catch (PHPUnitException $e) { + $message = $message ?: "A request to [{$uri}] failed. Received status code [{$status}]."; + + $responseException = isset($this->response->exception) + ? $this->response->exception : null; + + throw new HttpException($message, null, $responseException); + } + } + + /** + * Assert that a given string is seen on the page. + * + * @param string $text + * @param bool $negate + * @return $this + */ + protected function see($text, $negate = false) + { + $method = $negate ? 'assertNotRegExp' : 'assertRegExp'; + + $rawPattern = preg_quote($text, '/'); + + $escapedPattern = preg_quote(e($text), '/'); + + $pattern = $rawPattern == $escapedPattern + ? $rawPattern : "({$rawPattern}|{$escapedPattern})"; + + $this->$method("/$pattern/i", $this->response->getContent()); + + return $this; + } + + /** + * Assert that a given string is not seen on the page. + * + * @param string $text + * @return $this + */ + protected function dontSee($text) + { + return $this->see($text, true); + } + + /** + * Assert that a given string is seen inside an element. + * + * @param string $element + * @param string $text + * @param bool $negate + * @return $this + */ + public function seeInElement($element, $text, $negate = false) + { + if ($negate) { + return $this->dontSeeInElement($element, $text); + } + + $this->assertTrue( + $this->hasInElement($element, $text), + "Element [$element] should contain the expected text [{$text}]" + ); + + return $this; + } + + /** + * Assert that a given string is not seen inside an element. + * + * @param string $element + * @param string $text + * @return $this + */ + public function dontSeeInElement($element, $text) + { + $this->assertFalse( + $this->hasInElement($element, $text), + "Element [$element] should not contain the expected text [{$text}]" + ); + + return $this; + } + + /** + * Check if the page contains text within the given element. + * + * @param string $element + * @param string $text + * @return bool + */ + protected function hasInElement($element, $text) + { + $elements = $this->crawler->filter($element); + + $rawPattern = preg_quote($text, '/'); + + $escapedPattern = preg_quote(e($text), '/'); + + $pattern = $rawPattern == $escapedPattern + ? $rawPattern : "({$rawPattern}|{$escapedPattern})"; + + foreach ($elements as $element) { + $element = new Crawler($element); + + if (preg_match("/$pattern/i", $element->html())) { + return true; + } + } + + return false; + } + + /** + * Assert that a given link is seen on the page. + * + * @param string $text + * @param string|null $url + * @return $this + */ + public function seeLink($text, $url = null) + { + $message = "No links were found with expected text [{$text}]"; + + if ($url) { + $message .= " and URL [{$url}]"; + } + + $this->assertTrue($this->hasLink($text, $url), "{$message}."); + + return $this; + } + + /** + * Assert that a given link is not seen on the page. + * + * @param string $text + * @param string|null $url + * @return $this + */ + public function dontSeeLink($text, $url = null) + { + $message = "A link was found with expected text [{$text}]"; + + if ($url) { + $message .= " and URL [{$url}]"; + } + + $this->assertFalse($this->hasLink($text, $url), "{$message}."); + + return $this; + } + + /** + * Check if the page has a link with the given $text and optional $url. + * + * @param string $text + * @param string|null $url + * @return bool + */ + protected function hasLink($text, $url = null) + { + $links = $this->crawler->selectLink($text); + + if ($links->count() == 0) { + return false; + } + + // If the URL is null, we assume the developer only wants to find a link + // with the given text regardless of the URL. So, if we find the link + // we will return true now. Otherwise, we look for the given URL. + if ($url == null) { + return true; + } + + $url = $this->addRootToRelativeUrl($url); + + foreach ($links as $link) { + if ($link->getAttribute('href') == $url) { + return true; + } + } + + return false; + } + + /** + * Add a root if the URL is relative (helper method of the hasLink function). + * + * @param string $url + * @return string + */ + protected function addRootToRelativeUrl($url) + { + if (! Str::startsWith($url, ['http', 'https'])) { + return $this->app->make('url')->to($url); + } + + return $url; + } + + /** + * Assert that an input field contains the given value. + * + * @param string $selector + * @param string $expected + * @return $this + */ + public function seeInField($selector, $expected) + { + $this->assertSame( + $expected, $this->getInputOrTextAreaValue($selector), + "The field [{$selector}] does not contain the expected value [{$expected}]." + ); + + return $this; + } + + /** + * Assert that an input field does not contain the given value. + * + * @param string $selector + * @param string $value + * @return $this + */ + public function dontSeeInField($selector, $value) + { + $this->assertNotSame( + $this->getInputOrTextAreaValue($selector), $value, + "The input [{$selector}] should not contain the value [{$value}]." + ); + + return $this; + } + + /** + * Assert that the given checkbox is selected. + * + * @param string $selector + * @return $this + */ + public function seeIsChecked($selector) + { + $this->assertTrue( + $this->isChecked($selector), + "The checkbox [{$selector}] is not checked." + ); + + return $this; + } + + /** + * Assert that the given checkbox is not selected. + * + * @param string $selector + * @return $this + */ + public function dontSeeIsChecked($selector) + { + $this->assertFalse( + $this->isChecked($selector), + "The checkbox [{$selector}] is checked." + ); + + return $this; + } + + /** + * Assert that the expected value is selected. + * + * @param string $selector + * @param string $expected + * @return $this + */ + public function seeIsSelected($selector, $expected) + { + $this->assertEquals( + $expected, $this->getSelectedValue($selector), + "The field [{$selector}] does not contain the selected value [{$expected}]." + ); + + return $this; + } + + /** + * Assert that the given value is not selected. + * + * @param string $selector + * @param string $value + * @return $this + */ + public function dontSeeIsSelected($selector, $value) + { + $this->assertNotEquals( + $value, $this->getSelectedValue($selector), + "The field [{$selector}] contains the selected value [{$value}]." + ); + + return $this; + } + + /** + * Get the value of an input or textarea. + * + * @param string $selector + * @return string + * + * @throws \Exception + */ + protected function getInputOrTextAreaValue($selector) + { + $field = $this->filterByNameOrId($selector, ['input', 'textarea']); + + if ($field->count() == 0) { + throw new Exception("There are no elements with the name or ID [$selector]."); + } + + $element = $field->nodeName(); + + if ($element == 'input') { + return $field->attr('value'); + } + + if ($element == 'textarea') { + return $field->text(); + } + + throw new Exception("Given selector [$selector] is not an input or textarea."); + } + + /** + * Get the selected value of a select field or radio group. + * + * @param string $selector + * @return string|null + * + * @throws \Exception + */ + protected function getSelectedValue($selector) + { + $field = $this->filterByNameOrId($selector); + + if ($field->count() == 0) { + throw new Exception("There are no elements with the name or ID [$selector]."); + } + + $element = $field->nodeName(); + + if ($element == 'select') { + return $this->getSelectedValueFromSelect($field); + } + + if ($element == 'input') { + return $this->getCheckedValueFromRadioGroup($field); + } + + throw new Exception("Given selector [$selector] is not a select or radio group."); + } + + /** + * Get the selected value from a select field. + * + * @param \Symfony\Component\DomCrawler\Crawler $field + * @return string|null + * + * @throws \Exception + */ + protected function getSelectedValueFromSelect(Crawler $field) + { + if ($field->nodeName() !== 'select') { + throw new Exception('Given element is not a select element.'); + } + + foreach ($field->children() as $option) { + if ($option->hasAttribute('selected')) { + return $option->getAttribute('value'); + } + } + } + + /** + * Get the checked value from a radio group. + * + * @param \Symfony\Component\DomCrawler\Crawler $radioGroup + * @return string|null + * + * @throws \Exception + */ + protected function getCheckedValueFromRadioGroup(Crawler $radioGroup) + { + if ($radioGroup->nodeName() !== 'input' || $radioGroup->attr('type') !== 'radio') { + throw new Exception('Given element is not a radio button.'); + } + + foreach ($radioGroup as $radio) { + if ($radio->hasAttribute('checked')) { + return $radio->getAttribute('value'); + } + } + } + + /** + * Return true if the given checkbox is checked, false otherwise. + * + * @param string $selector + * @return bool + * + * @throws \Exception + */ + protected function isChecked($selector) + { + $checkbox = $this->filterByNameOrId($selector, "input[type='checkbox']"); + + if ($checkbox->count() == 0) { + throw new Exception("There are no checkbox elements with the name or ID [$selector]."); + } + + return $checkbox->attr('checked') !== null; + } + + /** + * Click a link with the given body, name, or ID attribute. + * + * @param string $name + * @return $this + */ + protected function click($name) + { + $link = $this->crawler->selectLink($name); + + if (! count($link)) { + $link = $this->filterByNameOrId($name, 'a'); + + if (! count($link)) { + throw new InvalidArgumentException( + "Could not find a link with a body, name, or ID attribute of [{$name}]." + ); + } + } + + $this->visit($link->link()->getUri()); + + return $this; + } + + /** + * Fill an input field with the given text. + * + * @param string $text + * @param string $element + * @return $this + */ + protected function type($text, $element) + { + return $this->storeInput($element, $text); + } + + /** + * Check a checkbox on the page. + * + * @param string $element + * @return $this + */ + protected function check($element) + { + return $this->storeInput($element, true); + } + + /** + * Uncheck a checkbox on the page. + * + * @param string $element + * @return $this + */ + protected function uncheck($element) + { + return $this->storeInput($element, false); + } + + /** + * Select an option from a drop-down. + * + * @param string $option + * @param string $element + * @return $this + */ + protected function select($option, $element) + { + return $this->storeInput($element, $option); + } + + /** + * Attach a file to a form field on the page. + * + * @param string $absolutePath + * @param string $element + * @return $this + */ + protected function attach($absolutePath, $element) + { + $this->uploads[$element] = $absolutePath; + + return $this->storeInput($element, $absolutePath); + } + + /** + * Submit a form using the button with the given text value. + * + * @param string $buttonText + * @return $this + */ + protected function press($buttonText) + { + return $this->submitForm($buttonText, $this->inputs, $this->uploads); + } + + /** + * Submit a form on the page with the given input. + * + * @param string $buttonText + * @param array $inputs + * @param array $uploads + * @return $this + */ + protected function submitForm($buttonText, $inputs = [], $uploads = []) + { + $this->makeRequestUsingForm($this->fillForm($buttonText, $inputs), $uploads); + + return $this; + } + + /** + * Fill the form with the given data. + * + * @param string $buttonText + * @param array $inputs + * @return \Symfony\Component\DomCrawler\Form + */ + protected function fillForm($buttonText, $inputs = []) + { + if (! is_string($buttonText)) { + $inputs = $buttonText; + + $buttonText = null; + } + + return $this->getForm($buttonText)->setValues($inputs); + } + + /** + * Get the form from the page with the given submit button text. + * + * @param string|null $buttonText + * @return \Symfony\Component\DomCrawler\Form + */ + protected function getForm($buttonText = null) + { + try { + if ($buttonText) { + return $this->crawler->selectButton($buttonText)->form(); + } + + return $this->crawler->filter('form')->form(); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException( + "Could not find a form that has submit button [{$buttonText}]." + ); + } + } + + /** + * Store a form input in the local array. + * + * @param string $element + * @param string $text + * @return $this + */ + protected function storeInput($element, $text) + { + $this->assertFilterProducesResults($element); + + $element = str_replace('#', '', $element); + + $this->inputs[$element] = $text; + + return $this; + } + + /** + * Assert that a filtered Crawler returns nodes. + * + * @param string $filter + * @return void + */ + protected function assertFilterProducesResults($filter) + { + $crawler = $this->filterByNameOrId($filter); + + if (! count($crawler)) { + throw new InvalidArgumentException( + "Nothing matched the filter [{$filter}] CSS query provided for [{$this->currentUri}]." + ); + } + } + + /** + * Filter elements according to the given name or ID attribute. + * + * @param string $name + * @param array|string $elements + * @return \Symfony\Component\DomCrawler\Crawler + */ + protected function filterByNameOrId($name, $elements = '*') + { + $name = str_replace('#', '', $name); + + $id = str_replace(['[', ']'], ['\\[', '\\]'], $name); + + $elements = is_array($elements) ? $elements : [$elements]; + + array_walk($elements, function (&$element) use ($name, $id) { + $element = "{$element}#{$id}, {$element}[name='{$name}']"; + }); + + return $this->crawler->filter(implode(', ', $elements)); + } + + /** + * Convert the given uploads to UploadedFile instances. + * + * @param \Symfony\Component\DomCrawler\Form $form + * @param array $uploads + * @return array + */ + protected function convertUploadsForTesting(Form $form, array $uploads) + { + $files = $form->getFiles(); + + $names = array_keys($files); + + $files = array_map(function (array $file, $name) use ($uploads) { + return isset($uploads[$name]) + ? $this->getUploadedFileForTesting($file, $uploads, $name) + : $file; + }, $files, $names); + + return array_combine($names, $files); + } + + /** + * Create an UploadedFile instance for testing. + * + * @param array $file + * @param array $uploads + * @param string $name + * @return \Symfony\Component\HttpFoundation\File\UploadedFile + */ + protected function getUploadedFileForTesting($file, $uploads, $name) + { + return new UploadedFile( + $file['tmp_name'], basename($uploads[$name]), $file['type'], $file['size'], $file['error'], true + ); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php new file mode 100644 index 0000000..ffbbfae --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php @@ -0,0 +1,76 @@ +app) { + $this->refreshApplication(); + } + } + + /** + * Clean up the testing environment before the next test. + * + * @return void + */ + public function tearDown() + { + if ($this->app) { + foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { + call_user_func($callback); + } + + $this->app->flush(); + + $this->app = null; + } + + if (property_exists($this, 'serverVariables')) { + $this->serverVariables = []; + } + + if (class_exists('Mockery')) { + Mockery::close(); + } + } + + /** + * Register a callback to be run before the application is destroyed. + * + * @param callable $callback + * @return void + */ + protected function beforeApplicationDestroyed(callable $callback) + { + $this->beforeApplicationDestroyedCallbacks[] = $callback; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutEvents.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutEvents.php new file mode 100644 index 0000000..80bf471 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutEvents.php @@ -0,0 +1,20 @@ +withoutEvents(); + } else { + throw new Exception('Unable to disable middleware. ApplicationTrait not used.'); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutMiddleware.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutMiddleware.php new file mode 100644 index 0000000..91d1bf5 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutMiddleware.php @@ -0,0 +1,20 @@ +withoutMiddleware(); + } else { + throw new Exception('Unable to disable middleware. CrawlerTrait not used.'); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/Validation/ValidatesRequests.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/Validation/ValidatesRequests.php new file mode 100644 index 0000000..c3f896b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/Validation/ValidatesRequests.php @@ -0,0 +1,150 @@ +getValidationFactory()->make($request->all(), $rules, $messages, $customAttributes); + + if ($validator->fails()) { + $this->throwValidationException($request, $validator); + } + } + + /** + * Validate the given request with the given rules. + * + * @param string $errorBag + * @param \Illuminate\Http\Request $request + * @param array $rules + * @param array $messages + * @param array $customAttributes + * @return void + * + * @throws \Illuminate\Http\Exception\HttpResponseException + */ + public function validateWithBag($errorBag, Request $request, array $rules, array $messages = [], array $customAttributes = []) + { + $this->withErrorBag($errorBag, function () use ($request, $rules, $messages, $customAttributes) { + $this->validate($request, $rules, $messages, $customAttributes); + }); + } + + /** + * Throw the failed validation exception. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Contracts\Validation\Validator $validator + * @return void + * + * @throws \Illuminate\Http\Exception\HttpResponseException + */ + protected function throwValidationException(Request $request, $validator) + { + throw new HttpResponseException($this->buildFailedValidationResponse( + $request, $this->formatValidationErrors($validator) + )); + } + + /** + * Create the response for when a request fails validation. + * + * @param \Illuminate\Http\Request $request + * @param array $errors + * @return \Illuminate\Http\Response + */ + protected function buildFailedValidationResponse(Request $request, array $errors) + { + if ($request->ajax() || $request->wantsJson()) { + return new JsonResponse($errors, 422); + } + + return redirect()->to($this->getRedirectUrl()) + ->withInput($request->input()) + ->withErrors($errors, $this->errorBag()); + } + + /** + * Format the validation errors to be returned. + * + * @param \Illuminate\Contracts\Validation\Validator $validator + * @return array + */ + protected function formatValidationErrors(Validator $validator) + { + return $validator->errors()->getMessages(); + } + + /** + * Get the URL we should redirect to. + * + * @return string + */ + protected function getRedirectUrl() + { + return app(UrlGenerator::class)->previous(); + } + + /** + * Get a validation factory instance. + * + * @return \Illuminate\Contracts\Validation\Factory + */ + protected function getValidationFactory() + { + return app(Factory::class); + } + + /** + * Execute a Closure within with a given error bag set as the default bag. + * + * @param string $errorBag + * @param callable $callback + * @return void + */ + protected function withErrorBag($errorBag, callable $callback) + { + $this->validatesRequestErrorBag = $errorBag; + + call_user_func($callback); + + $this->validatesRequestErrorBag = null; + } + + /** + * Get the key to be used for the view error bag. + * + * @return string + */ + protected function errorBag() + { + return $this->validatesRequestErrorBag ?: 'default'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php b/core/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php new file mode 100644 index 0000000..fa19a77 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php @@ -0,0 +1,750 @@ +abort($code, $message, $headers); + } +} + +if (! function_exists('action')) { + /** + * Generate a URL to a controller action. + * + * @param string $name + * @param array $parameters + * @param bool $absolute + * @return string + */ + function action($name, $parameters = [], $absolute = true) + { + return app('url')->action($name, $parameters, $absolute); + } +} + +if (! function_exists('app')) { + /** + * Get the available container instance. + * + * @param string $make + * @param array $parameters + * @return mixed|\Illuminate\Foundation\Application + */ + function app($make = null, $parameters = []) + { + if (is_null($make)) { + return Container::getInstance(); + } + + return Container::getInstance()->make($make, $parameters); + } +} + +if (! function_exists('app_path')) { + /** + * Get the path to the application folder. + * + * @param string $path + * @return string + */ + function app_path($path = '') + { + return app('path').($path ? DIRECTORY_SEPARATOR.$path : $path); + } +} + +if (! function_exists('asset')) { + /** + * Generate an asset path for the application. + * + * @param string $path + * @param bool $secure + * @return string + */ + function asset($path, $secure = null) + { + return app('url')->asset($path, $secure); + } +} + +if (! function_exists('auth')) { + /** + * Get the available auth instance. + * + * @return \Illuminate\Contracts\Auth\Guard + */ + function auth() + { + return app(Guard::class); + } +} + +if (! function_exists('back')) { + /** + * Create a new redirect response to the previous location. + * + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + function back($status = 302, $headers = []) + { + return app('redirect')->back($status, $headers); + } +} + +if (! function_exists('base_path')) { + /** + * Get the path to the base of the install. + * + * @param string $path + * @return string + */ + function base_path($path = '') + { + return app()->basePath().($path ? DIRECTORY_SEPARATOR.$path : $path); + } +} + +if (! function_exists('bcrypt')) { + /** + * Hash the given value. + * + * @param string $value + * @param array $options + * @return string + */ + function bcrypt($value, $options = []) + { + return app('hash')->make($value, $options); + } +} + +if (! function_exists('config')) { + /** + * Get / set the specified configuration value. + * + * If an array is passed as the key, we will assume you want to set an array of values. + * + * @param array|string $key + * @param mixed $default + * @return mixed + */ + function config($key = null, $default = null) + { + if (is_null($key)) { + return app('config'); + } + + if (is_array($key)) { + return app('config')->set($key); + } + + return app('config')->get($key, $default); + } +} + +if (! function_exists('config_path')) { + /** + * Get the configuration path. + * + * @param string $path + * @return string + */ + function config_path($path = '') + { + return app()->make('path.config').($path ? DIRECTORY_SEPARATOR.$path : $path); + } +} + +if (! function_exists('cookie')) { + /** + * Create a new cookie instance. + * + * @param string $name + * @param string $value + * @param int $minutes + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + * @return \Symfony\Component\HttpFoundation\Cookie + */ + function cookie($name = null, $value = null, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true) + { + $cookie = app(CookieFactory::class); + + if (is_null($name)) { + return $cookie; + } + + return $cookie->make($name, $value, $minutes, $path, $domain, $secure, $httpOnly); + } +} + +if (! function_exists('csrf_field')) { + /** + * Generate a CSRF token form field. + * + * @return string + */ + function csrf_field() + { + return new Expression(''); + } +} + +if (! function_exists('csrf_token')) { + /** + * Get the CSRF token value. + * + * @return string + * + * @throws RuntimeException + */ + function csrf_token() + { + $session = app('session'); + + if (isset($session)) { + return $session->getToken(); + } + + throw new RuntimeException('Application session store not set.'); + } +} + +if (! function_exists('database_path')) { + /** + * Get the database path. + * + * @param string $path + * @return string + */ + function database_path($path = '') + { + return app()->databasePath().($path ? DIRECTORY_SEPARATOR.$path : $path); + } +} + +if (! function_exists('delete')) { + /** + * Register a new DELETE route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + function delete($uri, $action) + { + return app('router')->delete($uri, $action); + } +} + +if (! function_exists('dispatch')) { + /** + * Dispatch a job to its appropriate handler. + * + * @param mixed $job + * @return mixed + */ + function dispatch($job) + { + return app(Dispatcher::class)->dispatch($job); + } +} + +if (! function_exists('elixir')) { + /** + * Get the path to a versioned Elixir file. + * + * @param string $file + * @return string + * + * @throws \InvalidArgumentException + */ + function elixir($file) + { + static $manifest = null; + + if (is_null($manifest)) { + $manifest = json_decode(file_get_contents(public_path('build/rev-manifest.json')), true); + } + + if (isset($manifest[$file])) { + return '/build/'.$manifest[$file]; + } + + throw new InvalidArgumentException("File {$file} not defined in asset manifest."); + } +} + +if (! function_exists('env')) { + /** + * Gets the value of an environment variable. Supports boolean, empty and null. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + function env($key, $default = null) + { + $value = getenv($key); + + if ($value === false) { + return value($default); + } + + switch (strtolower($value)) { + case 'true': + case '(true)': + return true; + case 'false': + case '(false)': + return false; + case 'empty': + case '(empty)': + return ''; + case 'null': + case '(null)': + return; + } + + if (strlen($value) > 1 && Str::startsWith($value, '"') && Str::endsWith($value, '"')) { + return substr($value, 1, -1); + } + + return $value; + } +} + +if (! function_exists('event')) { + /** + * Fire an event and call the listeners. + * + * @param string|object $event + * @param mixed $payload + * @param bool $halt + * @return array|null + */ + function event($event, $payload = [], $halt = false) + { + return app('events')->fire($event, $payload, $halt); + } +} + +if (! function_exists('factory')) { + /** + * Create a model factory builder for a given class, name, and amount. + * + * @param dynamic class|class,name|class,amount|class,name,amount + * @return \Illuminate\Database\Eloquent\FactoryBuilder + */ + function factory() + { + $factory = app(EloquentFactory::class); + + $arguments = func_get_args(); + + if (isset($arguments[1]) && is_string($arguments[1])) { + return $factory->of($arguments[0], $arguments[1])->times(isset($arguments[2]) ? $arguments[2] : 1); + } elseif (isset($arguments[1])) { + return $factory->of($arguments[0])->times($arguments[1]); + } else { + return $factory->of($arguments[0]); + } + } +} + +if (! function_exists('get')) { + /** + * Register a new GET route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + function get($uri, $action) + { + return app('router')->get($uri, $action); + } +} + +if (! function_exists('info')) { + /** + * Write some information to the log. + * + * @param string $message + * @param array $context + * @return void + */ + function info($message, $context = []) + { + return app('log')->info($message, $context); + } +} + +if (! function_exists('logger')) { + /** + * Log a debug message to the logs. + * + * @param string $message + * @param array $context + * @return null|\Illuminate\Contracts\Logging\Log + */ + function logger($message = null, array $context = []) + { + if (is_null($message)) { + return app('log'); + } + + return app('log')->debug($message, $context); + } +} + +if (! function_exists('method_field')) { + /** + * Generate a form field to spoof the HTTP verb used by forms. + * + * @param string $method + * @return string + */ + function method_field($method) + { + return new Expression(''); + } +} + +if (! function_exists('old')) { + /** + * Retrieve an old input item. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + function old($key = null, $default = null) + { + return app('request')->old($key, $default); + } +} + +if (! function_exists('patch')) { + /** + * Register a new PATCH route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + function patch($uri, $action) + { + return app('router')->patch($uri, $action); + } +} + +if (! function_exists('policy')) { + /** + * Get a policy instance for a given class. + * + * @param object|string $class + * @return mixed + * + * @throws \InvalidArgumentException + */ + function policy($class) + { + return app(Gate::class)->getPolicyFor($class); + } +} + +if (! function_exists('post')) { + /** + * Register a new POST route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + function post($uri, $action) + { + return app('router')->post($uri, $action); + } +} + +if (! function_exists('public_path')) { + /** + * Get the path to the public folder. + * + * @param string $path + * @return string + */ + function public_path($path = '') + { + return app()->make('path.public').($path ? DIRECTORY_SEPARATOR.$path : $path); + } +} + +if (! function_exists('put')) { + /** + * Register a new PUT route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + function put($uri, $action) + { + return app('router')->put($uri, $action); + } +} + +if (! function_exists('redirect')) { + /** + * Get an instance of the redirector. + * + * @param string|null $to + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse + */ + function redirect($to = null, $status = 302, $headers = [], $secure = null) + { + if (is_null($to)) { + return app('redirect'); + } + + return app('redirect')->to($to, $status, $headers, $secure); + } +} + +if (! function_exists('request')) { + /** + * Get an instance of the current request or an input item from the request. + * + * @param string $key + * @param mixed $default + * @return \Illuminate\Http\Request|string|array + */ + function request($key = null, $default = null) + { + if (is_null($key)) { + return app('request'); + } + + return app('request')->input($key, $default); + } +} + +if (! function_exists('resource')) { + /** + * Route a resource to a controller. + * + * @param string $name + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\Route + */ + function resource($name, $controller, array $options = []) + { + return app('router')->resource($name, $controller, $options); + } +} + +if (! function_exists('response')) { + /** + * Return a new response from the application. + * + * @param string $content + * @param int $status + * @param array $headers + * @return \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Routing\ResponseFactory + */ + function response($content = '', $status = 200, array $headers = []) + { + $factory = app(ResponseFactory::class); + + if (func_num_args() === 0) { + return $factory; + } + + return $factory->make($content, $status, $headers); + } +} + +if (! function_exists('route')) { + /** + * Generate a URL to a named route. + * + * @param string $name + * @param array $parameters + * @param bool $absolute + * @param \Illuminate\Routing\Route $route + * @return string + */ + function route($name, $parameters = [], $absolute = true, $route = null) + { + return app('url')->route($name, $parameters, $absolute, $route); + } +} + +if (! function_exists('secure_asset')) { + /** + * Generate an asset path for the application. + * + * @param string $path + * @return string + */ + function secure_asset($path) + { + return asset($path, true); + } +} + +if (! function_exists('secure_url')) { + /** + * Generate a HTTPS url for the application. + * + * @param string $path + * @param mixed $parameters + * @return string + */ + function secure_url($path, $parameters = []) + { + return url($path, $parameters, true); + } +} + +if (! function_exists('session')) { + /** + * Get / set the specified session value. + * + * If an array is passed as the key, we will assume you want to set an array of values. + * + * @param array|string $key + * @param mixed $default + * @return mixed + */ + function session($key = null, $default = null) + { + if (is_null($key)) { + return app('session'); + } + + if (is_array($key)) { + return app('session')->put($key); + } + + return app('session')->get($key, $default); + } +} + +if (! function_exists('storage_path')) { + /** + * Get the path to the storage folder. + * + * @param string $path + * @return string + */ + function storage_path($path = '') + { + return app('path.storage').($path ? DIRECTORY_SEPARATOR.$path : $path); + } +} + +if (! function_exists('trans')) { + /** + * Translate the given message. + * + * @param string $id + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + function trans($id = null, $parameters = [], $domain = 'messages', $locale = null) + { + if (is_null($id)) { + return app('translator'); + } + + return app('translator')->trans($id, $parameters, $domain, $locale); + } +} + +if (! function_exists('trans_choice')) { + /** + * Translates the given message based on a count. + * + * @param string $id + * @param int $number + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + function trans_choice($id, $number, array $parameters = [], $domain = 'messages', $locale = null) + { + return app('translator')->transChoice($id, $number, $parameters, $domain, $locale); + } +} + +if (! function_exists('url')) { + /** + * Generate a url for the application. + * + * @param string $path + * @param mixed $parameters + * @param bool $secure + * @return string + */ + function url($path = null, $parameters = [], $secure = null) + { + return app(UrlGenerator::class)->to($path, $parameters, $secure); + } +} + +if (! function_exists('view')) { + /** + * Get the evaluated view contents for the given view. + * + * @param string $view + * @param array $data + * @param array $mergeData + * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory + */ + function view($view = null, $data = [], $mergeData = []) + { + $factory = app(ViewFactory::class); + + if (func_num_args() === 0) { + return $factory; + } + + return $factory->make($view, $data, $mergeData); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php b/core/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php new file mode 100644 index 0000000..39acc88 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php @@ -0,0 +1,82 @@ +rounds; + + $hash = password_hash($value, PASSWORD_BCRYPT, ['cost' => $cost]); + + if ($hash === false) { + throw new RuntimeException('Bcrypt hashing not supported.'); + } + + return $hash; + } + + /** + * Check the given plain value against a hash. + * + * @param string $value + * @param string $hashedValue + * @param array $options + * @return bool + */ + public function check($value, $hashedValue, array $options = []) + { + if (strlen($hashedValue) === 0) { + return false; + } + + return password_verify($value, $hashedValue); + } + + /** + * Check if the given hash has been hashed using the given options. + * + * @param string $hashedValue + * @param array $options + * @return bool + */ + public function needsRehash($hashedValue, array $options = []) + { + $cost = isset($options['rounds']) ? $options['rounds'] : $this->rounds; + + return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, ['cost' => $cost]); + } + + /** + * Set the default password work factor. + * + * @param int $rounds + * @return $this + */ + public function setRounds($rounds) + { + $this->rounds = (int) $rounds; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php new file mode 100644 index 0000000..85581f4 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php @@ -0,0 +1,37 @@ +app->singleton('hash', function () { + return new BcryptHasher; + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['hash']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Hashing/composer.json b/core/vendor/laravel/framework/src/Illuminate/Hashing/composer.json new file mode 100644 index 0000000..d2ccf86 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Hashing/composer.json @@ -0,0 +1,32 @@ +{ + "name": "illuminate/hashing", + "description": "The Illuminate Hashing package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Hashing\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/Exception/HttpResponseException.php b/core/vendor/laravel/framework/src/Illuminate/Http/Exception/HttpResponseException.php new file mode 100644 index 0000000..0dcf48a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/Exception/HttpResponseException.php @@ -0,0 +1,37 @@ +response = $response; + } + + /** + * Get the underlying response instance. + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/Exception/PostTooLargeException.php b/core/vendor/laravel/framework/src/Illuminate/Http/Exception/PostTooLargeException.php new file mode 100644 index 0000000..f0b7a67 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/Exception/PostTooLargeException.php @@ -0,0 +1,10 @@ +jsonOptions = $options; + + parent::__construct($data, $status, $headers); + } + + /** + * Get the json_decoded data from the response. + * + * @param bool $assoc + * @param int $depth + * @return mixed + */ + public function getData($assoc = false, $depth = 512) + { + return json_decode($this->data, $assoc, $depth); + } + + /** + * {@inheritdoc} + */ + public function setData($data = []) + { + $this->data = $data instanceof Jsonable + ? $data->toJson($this->jsonOptions) + : json_encode($data, $this->jsonOptions); + + return $this->update(); + } + + /** + * Get the JSON encoding options. + * + * @return int + */ + public function getJsonOptions() + { + return $this->jsonOptions; + } + + /** + * Set the JSON encoding options. + * + * @param int $options + * @return mixed + */ + public function setJsonOptions($options) + { + $this->jsonOptions = $options; + + return $this->setData($this->getData()); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/Middleware/CheckResponseForModifications.php b/core/vendor/laravel/framework/src/Illuminate/Http/Middleware/CheckResponseForModifications.php new file mode 100644 index 0000000..2a93e21 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/Middleware/CheckResponseForModifications.php @@ -0,0 +1,27 @@ +isNotModified($request); + } + + return $response; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/Middleware/FrameGuard.php b/core/vendor/laravel/framework/src/Illuminate/Http/Middleware/FrameGuard.php new file mode 100644 index 0000000..b5d3f3c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/Middleware/FrameGuard.php @@ -0,0 +1,24 @@ +headers->set('X-Frame-Options', 'SAMEORIGIN', false); + + return $response; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php b/core/vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php new file mode 100644 index 0000000..419314a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php @@ -0,0 +1,215 @@ + $value]; + + foreach ($key as $k => $v) { + $this->session->flash($k, $v); + } + + return $this; + } + + /** + * Add multiple cookies to the response. + * + * @param array $cookies + * @return $this + */ + public function withCookies(array $cookies) + { + foreach ($cookies as $cookie) { + $this->headers->setCookie($cookie); + } + + return $this; + } + + /** + * Flash an array of input to the session. + * + * @param array $input + * @return $this + */ + public function withInput(array $input = null) + { + $input = $input ?: $this->request->input(); + + $this->session->flashInput($this->removeFilesFromInput($input)); + + return $this; + } + + /** + * Remove all uploaded files form the given input array. + * + * @param array $input + * @return array + */ + protected function removeFilesFromInput(array $input) + { + foreach ($input as $key => $value) { + if (is_array($value)) { + $input[$key] = $this->removeFilesFromInput($value); + } + + if ($value instanceof SymfonyUploadedFile) { + unset($input[$key]); + } + } + + return $input; + } + + /** + * Flash an array of input to the session. + * + * @param mixed string + * @return $this + */ + public function onlyInput() + { + return $this->withInput($this->request->only(func_get_args())); + } + + /** + * Flash an array of input to the session. + * + * @param mixed string + * @return \Illuminate\Http\RedirectResponse + */ + public function exceptInput() + { + return $this->withInput($this->request->except(func_get_args())); + } + + /** + * Flash a container of errors to the session. + * + * @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider + * @param string $key + * @return $this + */ + public function withErrors($provider, $key = 'default') + { + $value = $this->parseErrors($provider); + + $this->session->flash( + 'errors', $this->session->get('errors', new ViewErrorBag)->put($key, $value) + ); + + return $this; + } + + /** + * Parse the given errors into an appropriate value. + * + * @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider + * @return \Illuminate\Support\MessageBag + */ + protected function parseErrors($provider) + { + if ($provider instanceof MessageProvider) { + return $provider->getMessageBag(); + } + + return new MessageBag((array) $provider); + } + + /** + * Get the request instance. + * + * @return \Illuminate\Http\Request|null + */ + public function getRequest() + { + return $this->request; + } + + /** + * Set the request instance. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + } + + /** + * Get the session store implementation. + * + * @return \Illuminate\Session\Store|null + */ + public function getSession() + { + return $this->session; + } + + /** + * Set the session store implementation. + * + * @param \Illuminate\Session\Store $session + * @return void + */ + public function setSession(SessionStore $session) + { + $this->session = $session; + } + + /** + * Dynamically bind flash data in the session. + * + * @param string $method + * @param array $parameters + * @return $this + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (Str::startsWith($method, 'with')) { + return $this->with(Str::snake(substr($method, 4)), $parameters[0]); + } + + throw new BadMethodCallException("Method [$method] does not exist on Redirect."); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/Request.php b/core/vendor/laravel/framework/src/Illuminate/Http/Request.php new file mode 100644 index 0000000..eaa382b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/Request.php @@ -0,0 +1,912 @@ +getMethod(); + } + + /** + * Get the root URL for the application. + * + * @return string + */ + public function root() + { + return rtrim($this->getSchemeAndHttpHost().$this->getBaseUrl(), '/'); + } + + /** + * Get the URL (no query string) for the request. + * + * @return string + */ + public function url() + { + return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/'); + } + + /** + * Get the full URL for the request. + * + * @return string + */ + public function fullUrl() + { + $query = $this->getQueryString(); + + return $query ? $this->url().'?'.$query : $this->url(); + } + + /** + * Get the current path info for the request. + * + * @return string + */ + public function path() + { + $pattern = trim($this->getPathInfo(), '/'); + + return $pattern == '' ? '/' : $pattern; + } + + /** + * Get the current encoded path info for the request. + * + * @return string + */ + public function decodedPath() + { + return rawurldecode($this->path()); + } + + /** + * Get a segment from the URI (1 based index). + * + * @param int $index + * @param string|null $default + * @return string|null + */ + public function segment($index, $default = null) + { + return Arr::get($this->segments(), $index - 1, $default); + } + + /** + * Get all of the segments for the request path. + * + * @return array + */ + public function segments() + { + $segments = explode('/', $this->path()); + + return array_values(array_filter($segments, function ($v) { + return $v != ''; + })); + } + + /** + * Determine if the current request URI matches a pattern. + * + * @param mixed string + * @return bool + */ + public function is() + { + foreach (func_get_args() as $pattern) { + if (Str::is($pattern, urldecode($this->path()))) { + return true; + } + } + + return false; + } + + /** + * Determine if the request is the result of an AJAX call. + * + * @return bool + */ + public function ajax() + { + return $this->isXmlHttpRequest(); + } + + /** + * Determine if the request is the result of an PJAX call. + * + * @return bool + */ + public function pjax() + { + return $this->headers->get('X-PJAX') == true; + } + + /** + * Determine if the request is over HTTPS. + * + * @return bool + */ + public function secure() + { + return $this->isSecure(); + } + + /** + * Returns the client IP address. + * + * @return string + */ + public function ip() + { + return $this->getClientIp(); + } + + /** + * Returns the client IP addresses. + * + * @return array + */ + public function ips() + { + return $this->getClientIps(); + } + + /** + * Determine if the request contains a given input item key. + * + * @param string|array $key + * @return bool + */ + public function exists($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + $input = $this->all(); + + foreach ($keys as $value) { + if (! array_key_exists($value, $input)) { + return false; + } + } + + return true; + } + + /** + * Determine if the request contains a non-empty value for an input item. + * + * @param string|array $key + * @return bool + */ + public function has($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if ($this->isEmptyString($value)) { + return false; + } + } + + return true; + } + + /** + * Determine if the given input key is an empty string for "has". + * + * @param string $key + * @return bool + */ + protected function isEmptyString($key) + { + $value = $this->input($key); + + $boolOrArray = is_bool($value) || is_array($value); + + return ! $boolOrArray && trim((string) $value) === ''; + } + + /** + * Get all of the input and files for the request. + * + * @return array + */ + public function all() + { + return array_replace_recursive($this->input(), $this->files->all()); + } + + /** + * Retrieve an input item from the request. + * + * @param string $key + * @param string|array|null $default + * @return string|array + */ + public function input($key = null, $default = null) + { + $input = $this->getInputSource()->all() + $this->query->all(); + + return Arr::get($input, $key, $default); + } + + /** + * Get a subset of the items from the input data. + * + * @param array $keys + * @return array + */ + public function only($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + $results = []; + + $input = $this->all(); + + foreach ($keys as $key) { + Arr::set($results, $key, Arr::get($input, $key)); + } + + return $results; + } + + /** + * Get all of the input except for a specified array of items. + * + * @param array|mixed $keys + * @return array + */ + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + $results = $this->all(); + + Arr::forget($results, $keys); + + return $results; + } + + /** + * Retrieve a query string item from the request. + * + * @param string $key + * @param string|array|null $default + * @return string|array + */ + public function query($key = null, $default = null) + { + return $this->retrieveItem('query', $key, $default); + } + + /** + * Determine if a cookie is set on the request. + * + * @param string $key + * @return bool + */ + public function hasCookie($key) + { + return ! is_null($this->cookie($key)); + } + + /** + * Retrieve a cookie from the request. + * + * @param string $key + * @param string|array|null $default + * @return string|array + */ + public function cookie($key = null, $default = null) + { + return $this->retrieveItem('cookies', $key, $default); + } + + /** + * Retrieve a file from the request. + * + * @param string $key + * @param mixed $default + * @return \Symfony\Component\HttpFoundation\File\UploadedFile|array|null + */ + public function file($key = null, $default = null) + { + return Arr::get($this->files->all(), $key, $default); + } + + /** + * Determine if the uploaded data contains a file. + * + * @param string $key + * @return bool + */ + public function hasFile($key) + { + if (! is_array($files = $this->file($key))) { + $files = [$files]; + } + + foreach ($files as $file) { + if ($this->isValidFile($file)) { + return true; + } + } + + return false; + } + + /** + * Check that the given file is a valid file instance. + * + * @param mixed $file + * @return bool + */ + protected function isValidFile($file) + { + return $file instanceof SplFileInfo && $file->getPath() != ''; + } + + /** + * Retrieve a header from the request. + * + * @param string $key + * @param string|array|null $default + * @return string|array + */ + public function header($key = null, $default = null) + { + return $this->retrieveItem('headers', $key, $default); + } + + /** + * Retrieve a server variable from the request. + * + * @param string $key + * @param string|array|null $default + * @return string|array + */ + public function server($key = null, $default = null) + { + return $this->retrieveItem('server', $key, $default); + } + + /** + * Retrieve an old input item. + * + * @param string $key + * @param string|array|null $default + * @return string|array + */ + public function old($key = null, $default = null) + { + return $this->session()->getOldInput($key, $default); + } + + /** + * Flash the input for the current request to the session. + * + * @param string $filter + * @param array $keys + * @return void + */ + public function flash($filter = null, $keys = []) + { + $flash = (! is_null($filter)) ? $this->$filter($keys) : $this->input(); + + $this->session()->flashInput($flash); + } + + /** + * Flash only some of the input to the session. + * + * @param array|mixed $keys + * @return void + */ + public function flashOnly($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return $this->flash('only', $keys); + } + + /** + * Flash only some of the input to the session. + * + * @param array|mixed $keys + * @return void + */ + public function flashExcept($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return $this->flash('except', $keys); + } + + /** + * Flush all of the old input from the session. + * + * @return void + */ + public function flush() + { + $this->session()->flashInput([]); + } + + /** + * Retrieve a parameter item from a given source. + * + * @param string $source + * @param string $key + * @param string|array|null $default + * @return string|array + */ + protected function retrieveItem($source, $key, $default) + { + if (is_null($key)) { + return $this->$source->all(); + } + + return $this->$source->get($key, $default, true); + } + + /** + * Merge new input into the current request's input array. + * + * @param array $input + * @return void + */ + public function merge(array $input) + { + $this->getInputSource()->add($input); + } + + /** + * Replace the input for the current request. + * + * @param array $input + * @return void + */ + public function replace(array $input) + { + $this->getInputSource()->replace($input); + } + + /** + * Get the JSON payload for the request. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function json($key = null, $default = null) + { + if (! isset($this->json)) { + $this->json = new ParameterBag((array) json_decode($this->getContent(), true)); + } + + if (is_null($key)) { + return $this->json; + } + + return Arr::get($this->json->all(), $key, $default); + } + + /** + * Get the input source for the request. + * + * @return \Symfony\Component\HttpFoundation\ParameterBag + */ + protected function getInputSource() + { + if ($this->isJson()) { + return $this->json(); + } + + return $this->getRealMethod() == 'GET' ? $this->query : $this->request; + } + + /** + * Determine if the given content types match. + * + * @param string $actual + * @param string $type + * @return bool + */ + public static function matchesType($actual, $type) + { + if ($actual === $type) { + return true; + } + + $split = explode('/', $actual); + + return isset($split[1]) && preg_match('#'.preg_quote($split[0], '#').'/.+\+'.preg_quote($split[1], '#').'#', $type); + } + + /** + * Determine if the request is sending JSON. + * + * @return bool + */ + public function isJson() + { + return Str::contains($this->header('CONTENT_TYPE'), ['/json', '+json']); + } + + /** + * Determine if the current request is asking for JSON in return. + * + * @return bool + */ + public function wantsJson() + { + $acceptable = $this->getAcceptableContentTypes(); + + return isset($acceptable[0]) && Str::contains($acceptable[0], ['/json', '+json']); + } + + /** + * Determines whether the current requests accepts a given content type. + * + * @param string|array $contentTypes + * @return bool + */ + public function accepts($contentTypes) + { + $accepts = $this->getAcceptableContentTypes(); + + if (count($accepts) === 0) { + return true; + } + + $types = (array) $contentTypes; + + foreach ($accepts as $accept) { + if ($accept === '*/*' || $accept === '*') { + return true; + } + + foreach ($types as $type) { + if ($this->matchesType($accept, $type) || $accept === strtok($type, '/').'/*') { + return true; + } + } + } + + return false; + } + + /** + * Return the most suitable content type from the given array based on content negotiation. + * + * @param string|array $contentTypes + * @return string|null + */ + public function prefers($contentTypes) + { + $accepts = $this->getAcceptableContentTypes(); + + $contentTypes = (array) $contentTypes; + + foreach ($accepts as $accept) { + if (in_array($accept, ['*/*', '*'])) { + return $contentTypes[0]; + } + + foreach ($contentTypes as $contentType) { + $type = $contentType; + + if (! is_null($mimeType = $this->getMimeType($contentType))) { + $type = $mimeType; + } + + if ($this->matchesType($type, $accept) || $accept === strtok($type, '/').'/*') { + return $contentType; + } + } + } + } + + /** + * Determines whether a request accepts JSON. + * + * @return bool + */ + public function acceptsJson() + { + return $this->accepts('application/json'); + } + + /** + * Determines whether a request accepts HTML. + * + * @return bool + */ + public function acceptsHtml() + { + return $this->accepts('text/html'); + } + + /** + * Get the data format expected in the response. + * + * @param string $default + * @return string + */ + public function format($default = 'html') + { + foreach ($this->getAcceptableContentTypes() as $type) { + if ($format = $this->getFormat($type)) { + return $format; + } + } + + return $default; + } + + /** + * Create an Illuminate request from a Symfony instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Illuminate\Http\Request + */ + public static function createFromBase(SymfonyRequest $request) + { + if ($request instanceof static) { + return $request; + } + + $content = $request->content; + + $request = (new static)->duplicate( + + $request->query->all(), $request->request->all(), $request->attributes->all(), + + $request->cookies->all(), $request->files->all(), $request->server->all() + ); + + $request->content = $content; + + $request->request = $request->getInputSource(); + + return $request; + } + + /** + * {@inheritdoc} + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + return parent::duplicate($query, $request, $attributes, $cookies, array_filter((array) $files), $server); + } + + /** + * Get the session associated with the request. + * + * @return \Illuminate\Session\Store + * + * @throws \RuntimeException + */ + public function session() + { + if (! $this->hasSession()) { + throw new RuntimeException('Session store not set on request.'); + } + + return $this->getSession(); + } + + /** + * Get the user making the request. + * + * @return mixed + */ + public function user() + { + return call_user_func($this->getUserResolver()); + } + + /** + * Get the route handling the request. + * + * @param string|null $param + * + * @return \Illuminate\Routing\Route|object|string + */ + public function route($param = null) + { + $route = call_user_func($this->getRouteResolver()); + + if (is_null($route) || is_null($param)) { + return $route; + } else { + return $route->parameter($param); + } + } + + /** + * Get the user resolver callback. + * + * @return \Closure + */ + public function getUserResolver() + { + return $this->userResolver ?: function () { + // + }; + } + + /** + * Set the user resolver callback. + * + * @param \Closure $callback + * @return $this + */ + public function setUserResolver(Closure $callback) + { + $this->userResolver = $callback; + + return $this; + } + + /** + * Get the route resolver callback. + * + * @return \Closure + */ + public function getRouteResolver() + { + return $this->routeResolver ?: function () { + // + }; + } + + /** + * Set the route resolver callback. + * + * @param \Closure $callback + * @return $this + */ + public function setRouteResolver(Closure $callback) + { + $this->routeResolver = $callback; + + return $this; + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->all()); + } + + /** + * Get the value at the given offset. + * + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) + { + return Arr::get($this->all(), $offset); + } + + /** + * Set the value at the given offset. + * + * @param string $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + return $this->getInputSource()->set($offset, $value); + } + + /** + * Remove the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + return $this->getInputSource()->remove($offset); + } + + /** + * Check if an input element is set on the request. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return ! is_null($this->__get($key)); + } + + /** + * Get an input element from the request. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + $all = $this->all(); + + if (array_key_exists($key, $all)) { + return $all[$key]; + } else { + return $this->route($key); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/Response.php b/core/vendor/laravel/framework/src/Illuminate/Http/Response.php new file mode 100644 index 0000000..3c9299d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/Response.php @@ -0,0 +1,94 @@ +original = $content; + + // If the content is "JSONable" we will set the appropriate header and convert + // the content to JSON. This is useful when returning something like models + // from routes that will be automatically transformed to their JSON form. + if ($this->shouldBeJson($content)) { + $this->header('Content-Type', 'application/json'); + + $content = $this->morphToJson($content); + } + + // If this content implements the "Renderable" interface then we will call the + // render method on the object so we will avoid any "__toString" exceptions + // that might be thrown and have their errors obscured by PHP's handling. + elseif ($content instanceof Renderable) { + $content = $content->render(); + } + + return parent::setContent($content); + } + + /** + * Morph the given content into JSON. + * + * @param mixed $content + * @return string + */ + protected function morphToJson($content) + { + if ($content instanceof Jsonable) { + return $content->toJson(); + } + + return json_encode($content); + } + + /** + * Determine if the given content should be turned into JSON. + * + * @param mixed $content + * @return bool + */ + protected function shouldBeJson($content) + { + return $content instanceof Jsonable || + $content instanceof ArrayObject || + is_array($content); + } + + /** + * Get the original response content. + * + * @return mixed + */ + public function getOriginalContent() + { + return $this->original; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/ResponseTrait.php b/core/vendor/laravel/framework/src/Illuminate/Http/ResponseTrait.php new file mode 100644 index 0000000..56ddc6a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/ResponseTrait.php @@ -0,0 +1,58 @@ +getStatusCode(); + } + + /** + * Get the content of the response. + * + * @return string + */ + public function content() + { + return $this->getContent(); + } + + /** + * Set a header on the Response. + * + * @param string $key + * @param string $value + * @param bool $replace + * @return $this + */ + public function header($key, $value, $replace = true) + { + $this->headers->set($key, $value, $replace); + + return $this; + } + + /** + * Add a cookie to the response. + * + * @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie + * @return $this + */ + public function withCookie($cookie) + { + if (is_string($cookie) && function_exists('cookie')) { + $cookie = call_user_func_array('cookie', func_get_args()); + } + + $this->headers->setCookie($cookie); + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Http/composer.json b/core/vendor/laravel/framework/src/Illuminate/Http/composer.json new file mode 100644 index 0000000..a7bb6d3 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Http/composer.json @@ -0,0 +1,34 @@ +{ + "name": "illuminate/http", + "description": "The Illuminate Http package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/session": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/http-foundation": "2.7.*", + "symfony/http-kernel": "2.7.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Http\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Log/Writer.php b/core/vendor/laravel/framework/src/Illuminate/Log/Writer.php new file mode 100644 index 0000000..ae386d1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Log/Writer.php @@ -0,0 +1,375 @@ + MonologLogger::DEBUG, + 'info' => MonologLogger::INFO, + 'notice' => MonologLogger::NOTICE, + 'warning' => MonologLogger::WARNING, + 'error' => MonologLogger::ERROR, + 'critical' => MonologLogger::CRITICAL, + 'alert' => MonologLogger::ALERT, + 'emergency' => MonologLogger::EMERGENCY, + ]; + + /** + * Create a new log writer instance. + * + * @param \Monolog\Logger $monolog + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @return void + */ + public function __construct(MonologLogger $monolog, Dispatcher $dispatcher = null) + { + $this->monolog = $monolog; + + if (isset($dispatcher)) { + $this->dispatcher = $dispatcher; + } + } + + /** + * Log an emergency message to the logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function emergency($message, array $context = []) + { + return $this->writeLog(__FUNCTION__, $message, $context); + } + + /** + * Log an alert message to the logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function alert($message, array $context = []) + { + return $this->writeLog(__FUNCTION__, $message, $context); + } + + /** + * Log a critical message to the logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function critical($message, array $context = []) + { + return $this->writeLog(__FUNCTION__, $message, $context); + } + + /** + * Log an error message to the logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function error($message, array $context = []) + { + return $this->writeLog(__FUNCTION__, $message, $context); + } + + /** + * Log a warning message to the logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function warning($message, array $context = []) + { + return $this->writeLog(__FUNCTION__, $message, $context); + } + + /** + * Log a notice to the logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function notice($message, array $context = []) + { + return $this->writeLog(__FUNCTION__, $message, $context); + } + + /** + * Log an informational message to the logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function info($message, array $context = []) + { + return $this->writeLog(__FUNCTION__, $message, $context); + } + + /** + * Log a debug message to the logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function debug($message, array $context = []) + { + return $this->writeLog(__FUNCTION__, $message, $context); + } + + /** + * Log a message to the logs. + * + * @param string $level + * @param string $message + * @param array $context + * @return void + */ + public function log($level, $message, array $context = []) + { + return $this->writeLog($level, $message, $context); + } + + /** + * Dynamically pass log calls into the writer. + * + * @param string $level + * @param string $message + * @param array $context + * @return void + */ + public function write($level, $message, array $context = []) + { + return $this->writeLog($level, $message, $context); + } + + /** + * Write a message to Monolog. + * + * @param string $level + * @param string $message + * @param array $context + * @return void + */ + protected function writeLog($level, $message, $context) + { + $this->fireLogEvent($level, $message = $this->formatMessage($message), $context); + + $this->monolog->{$level}($message, $context); + } + + /** + * Register a file log handler. + * + * @param string $path + * @param string $level + * @return void + */ + public function useFiles($path, $level = 'debug') + { + $this->monolog->pushHandler($handler = new StreamHandler($path, $this->parseLevel($level))); + + $handler->setFormatter($this->getDefaultFormatter()); + } + + /** + * Register a daily file log handler. + * + * @param string $path + * @param int $days + * @param string $level + * @return void + */ + public function useDailyFiles($path, $days = 0, $level = 'debug') + { + $this->monolog->pushHandler( + $handler = new RotatingFileHandler($path, $days, $this->parseLevel($level)) + ); + + $handler->setFormatter($this->getDefaultFormatter()); + } + + /** + * Register a Syslog handler. + * + * @param string $name + * @param string $level + * @return \Psr\Log\LoggerInterface + */ + public function useSyslog($name = 'laravel', $level = 'debug') + { + return $this->monolog->pushHandler(new SyslogHandler($name, LOG_USER, $level)); + } + + /** + * Register an error_log handler. + * + * @param string $level + * @param int $messageType + * @return void + */ + public function useErrorLog($level = 'debug', $messageType = ErrorLogHandler::OPERATING_SYSTEM) + { + $this->monolog->pushHandler( + $handler = new ErrorLogHandler($messageType, $this->parseLevel($level)) + ); + + $handler->setFormatter($this->getDefaultFormatter()); + } + + /** + * Register a new callback handler for when a log event is triggered. + * + * @param \Closure $callback + * @return void + * + * @throws \RuntimeException + */ + public function listen(Closure $callback) + { + if (! isset($this->dispatcher)) { + throw new RuntimeException('Events dispatcher has not been set.'); + } + + $this->dispatcher->listen('illuminate.log', $callback); + } + + /** + * Fires a log event. + * + * @param string $level + * @param string $message + * @param array $context + * @return void + */ + protected function fireLogEvent($level, $message, array $context = []) + { + // If the event dispatcher is set, we will pass along the parameters to the + // log listeners. These are useful for building profilers or other tools + // that aggregate all of the log messages for a given "request" cycle. + if (isset($this->dispatcher)) { + $this->dispatcher->fire('illuminate.log', compact('level', 'message', 'context')); + } + } + + /** + * Format the parameters for the logger. + * + * @param mixed $message + * @return mixed + */ + protected function formatMessage($message) + { + if (is_array($message)) { + return var_export($message, true); + } elseif ($message instanceof Jsonable) { + return $message->toJson(); + } elseif ($message instanceof Arrayable) { + return var_export($message->toArray(), true); + } + + return $message; + } + + /** + * Parse the string level into a Monolog constant. + * + * @param string $level + * @return int + * + * @throws \InvalidArgumentException + */ + protected function parseLevel($level) + { + if (isset($this->levels[$level])) { + return $this->levels[$level]; + } + + throw new InvalidArgumentException('Invalid log level.'); + } + + /** + * Get the underlying Monolog instance. + * + * @return \Monolog\Logger + */ + public function getMonolog() + { + return $this->monolog; + } + + /** + * Get a defaut Monolog formatter instance. + * + * @return \Monolog\Formatter\LineFormatter + */ + protected function getDefaultFormatter() + { + return new LineFormatter(null, null, true, true); + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public function getEventDispatcher() + { + return $this->dispatcher; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @return void + */ + public function setEventDispatcher(Dispatcher $dispatcher) + { + $this->dispatcher = $dispatcher; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Log/composer.json b/core/vendor/laravel/framework/src/Illuminate/Log/composer.json new file mode 100644 index 0000000..f5bfc36 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Log/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/log", + "description": "The Illuminate Log package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "monolog/monolog": "~1.11" + }, + "autoload": { + "psr-4": { + "Illuminate\\Log\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/MailServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Mail/MailServiceProvider.php new file mode 100644 index 0000000..afb5e4b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/MailServiceProvider.php @@ -0,0 +1,120 @@ +registerSwiftMailer(); + + $this->app->singleton('mailer', function ($app) { + // Once we have create the mailer instance, we will set a container instance + // on the mailer. This allows us to resolve mailer classes via containers + // for maximum testability on said classes instead of passing Closures. + $mailer = new Mailer( + $app['view'], $app['swift.mailer'], $app['events'] + ); + + $this->setMailerDependencies($mailer, $app); + + // If a "from" address is set, we will set it on the mailer so that all mail + // messages sent by the applications will utilize the same "from" address + // on each one, which makes the developer's life a lot more convenient. + $from = $app['config']['mail.from']; + + if (is_array($from) && isset($from['address'])) { + $mailer->alwaysFrom($from['address'], $from['name']); + } + + $to = $app['config']['mail.to']; + + if (is_array($to) && isset($to['address'])) { + $mailer->alwaysTo($to['address'], $to['name']); + } + + // Here we will determine if the mailer should be in "pretend" mode for this + // environment, which will simply write out e-mail to the logs instead of + // sending it over the web, which is useful for local dev environments. + $pretend = $app['config']->get('mail.pretend', false); + + $mailer->pretend($pretend); + + return $mailer; + }); + } + + /** + * Set a few dependencies on the mailer instance. + * + * @param \Illuminate\Mail\Mailer $mailer + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function setMailerDependencies($mailer, $app) + { + $mailer->setContainer($app); + + if ($app->bound('Psr\Log\LoggerInterface')) { + $mailer->setLogger($app->make('Psr\Log\LoggerInterface')); + } + + if ($app->bound('queue')) { + $mailer->setQueue($app['queue.connection']); + } + } + + /** + * Register the Swift Mailer instance. + * + * @return void + */ + public function registerSwiftMailer() + { + $this->registerSwiftTransport(); + + // Once we have the transporter registered, we will register the actual Swift + // mailer instance, passing in the transport instances, which allows us to + // override this transporter instances during app start-up if necessary. + $this->app['swift.mailer'] = $this->app->share(function ($app) { + return new Swift_Mailer($app['swift.transport']->driver()); + }); + } + + /** + * Register the Swift Transport instance. + * + * @return void + */ + protected function registerSwiftTransport() + { + $this->app['swift.transport'] = $this->app->share(function ($app) { + return new TransportManager($app); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['mailer', 'swift.mailer', 'swift.transport']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php b/core/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php new file mode 100644 index 0000000..1292a97 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php @@ -0,0 +1,567 @@ +views = $views; + $this->swift = $swift; + $this->events = $events; + } + + /** + * Set the global from address and name. + * + * @param string $address + * @param string|null $name + * @return void + */ + public function alwaysFrom($address, $name = null) + { + $this->from = compact('address', 'name'); + } + + /** + * Set the global to address and name. + * + * @param string $address + * @param string|null $name + * @return void + */ + public function alwaysTo($address, $name = null) + { + $this->to = compact('address', 'name'); + } + + /** + * Send a new message when only a raw text part. + * + * @param string $text + * @param mixed $callback + * @return int + */ + public function raw($text, $callback) + { + return $this->send(['raw' => $text], [], $callback); + } + + /** + * Send a new message when only a plain part. + * + * @param string $view + * @param array $data + * @param mixed $callback + * @return int + */ + public function plain($view, array $data, $callback) + { + return $this->send(['text' => $view], $data, $callback); + } + + /** + * Send a new message using a view. + * + * @param string|array $view + * @param array $data + * @param \Closure|string $callback + * @return void + */ + public function send($view, array $data, $callback) + { + // First we need to parse the view, which could either be a string or an array + // containing both an HTML and plain text versions of the view which should + // be used when sending an e-mail. We will extract both of them out here. + list($view, $plain, $raw) = $this->parseView($view); + + $data['message'] = $message = $this->createMessage(); + + // Once we have retrieved the view content for the e-mail we will set the body + // of this message using the HTML type, which will provide a simple wrapper + // to creating view based emails that are able to receive arrays of data. + $this->addContent($message, $view, $plain, $raw, $data); + + $this->callMessageBuilder($callback, $message); + + if (isset($this->to['address'])) { + $message->to($this->to['address'], $this->to['name'], true); + } + + $message = $message->getSwiftMessage(); + + return $this->sendSwiftMessage($message); + } + + /** + * Queue a new e-mail message for sending. + * + * @param string|array $view + * @param array $data + * @param \Closure|string $callback + * @param string|null $queue + * @return mixed + */ + public function queue($view, array $data, $callback, $queue = null) + { + $callback = $this->buildQueueCallable($callback); + + return $this->queue->push('mailer@handleQueuedMessage', compact('view', 'data', 'callback'), $queue); + } + + /** + * Queue a new e-mail message for sending on the given queue. + * + * @param string $queue + * @param string|array $view + * @param array $data + * @param \Closure|string $callback + * @return mixed + */ + public function onQueue($queue, $view, array $data, $callback) + { + return $this->queue($view, $data, $callback, $queue); + } + + /** + * Queue a new e-mail message for sending on the given queue. + * + * This method didn't match rest of framework's "onQueue" phrasing. Added "onQueue". + * + * @param string $queue + * @param string|array $view + * @param array $data + * @param \Closure|string $callback + * @return mixed + */ + public function queueOn($queue, $view, array $data, $callback) + { + return $this->onQueue($queue, $view, $data, $callback); + } + + /** + * Queue a new e-mail message for sending after (n) seconds. + * + * @param int $delay + * @param string|array $view + * @param array $data + * @param \Closure|string $callback + * @param string|null $queue + * @return mixed + */ + public function later($delay, $view, array $data, $callback, $queue = null) + { + $callback = $this->buildQueueCallable($callback); + + return $this->queue->later($delay, 'mailer@handleQueuedMessage', compact('view', 'data', 'callback'), $queue); + } + + /** + * Queue a new e-mail message for sending after (n) seconds on the given queue. + * + * @param string $queue + * @param int $delay + * @param string|array $view + * @param array $data + * @param \Closure|string $callback + * @return mixed + */ + public function laterOn($queue, $delay, $view, array $data, $callback) + { + return $this->later($delay, $view, $data, $callback, $queue); + } + + /** + * Build the callable for a queued e-mail job. + * + * @param mixed $callback + * @return mixed + */ + protected function buildQueueCallable($callback) + { + if (! $callback instanceof Closure) { + return $callback; + } + + return (new Serializer)->serialize($callback); + } + + /** + * Handle a queued e-mail message job. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param array $data + * @return void + */ + public function handleQueuedMessage($job, $data) + { + $this->send($data['view'], $data['data'], $this->getQueuedCallable($data)); + + $job->delete(); + } + + /** + * Get the true callable for a queued e-mail message. + * + * @param array $data + * @return mixed + */ + protected function getQueuedCallable(array $data) + { + if (Str::contains($data['callback'], 'SerializableClosure')) { + return unserialize($data['callback'])->getClosure(); + } + + return $data['callback']; + } + + /** + * Force the transport to re-connect. + * + * This will prevent errors in daemon queue situations. + * + * @return void + */ + protected function forceReconnection() + { + $this->getSwiftMailer()->getTransport()->stop(); + } + + /** + * Add the content to a given message. + * + * @param \Illuminate\Mail\Message $message + * @param string $view + * @param string $plain + * @param string $raw + * @param array $data + * @return void + */ + protected function addContent($message, $view, $plain, $raw, $data) + { + if (isset($view)) { + $message->setBody($this->getView($view, $data), 'text/html'); + } + + if (isset($plain)) { + $method = isset($view) ? 'addPart' : 'setBody'; + + $message->$method($this->getView($plain, $data), 'text/plain'); + } + + if (isset($raw)) { + $method = (isset($view) || isset($plain)) ? 'addPart' : 'setBody'; + + $message->$method($raw, 'text/plain'); + } + } + + /** + * Parse the given view name or array. + * + * @param string|array $view + * @return array + * + * @throws \InvalidArgumentException + */ + protected function parseView($view) + { + if (is_string($view)) { + return [$view, null, null]; + } + + // If the given view is an array with numeric keys, we will just assume that + // both a "pretty" and "plain" view were provided, so we will return this + // array as is, since must should contain both views with numeric keys. + if (is_array($view) && isset($view[0])) { + return [$view[0], $view[1], null]; + } + + // If the view is an array, but doesn't contain numeric keys, we will assume + // the the views are being explicitly specified and will extract them via + // named keys instead, allowing the developers to use one or the other. + if (is_array($view)) { + return [ + Arr::get($view, 'html'), + Arr::get($view, 'text'), + Arr::get($view, 'raw'), + ]; + } + + throw new InvalidArgumentException('Invalid view.'); + } + + /** + * Send a Swift Message instance. + * + * @param \Swift_Message $message + * @return void + */ + protected function sendSwiftMessage($message) + { + if ($this->events) { + $this->events->fire('mailer.sending', [$message]); + } + + if (! $this->pretending) { + try { + return $this->swift->send($message, $this->failedRecipients); + } finally { + $this->swift->getTransport()->stop(); + } + } elseif (isset($this->logger)) { + $this->logMessage($message); + } + } + + /** + * Log that a message was sent. + * + * @param \Swift_Message $message + * @return void + */ + protected function logMessage($message) + { + $emails = implode(', ', array_keys((array) $message->getTo())); + + $this->logger->info("Pretending to mail message to: {$emails}"); + } + + /** + * Call the provided message builder. + * + * @param \Closure|string $callback + * @param \Illuminate\Mail\Message $message + * @return mixed + * + * @throws \InvalidArgumentException + */ + protected function callMessageBuilder($callback, $message) + { + if ($callback instanceof Closure) { + return call_user_func($callback, $message); + } + + if (is_string($callback)) { + return $this->container->make($callback)->mail($message); + } + + throw new InvalidArgumentException('Callback is not valid.'); + } + + /** + * Create a new message instance. + * + * @return \Illuminate\Mail\Message + */ + protected function createMessage() + { + $message = new Message(new Swift_Message); + + // If a global from address has been specified we will set it on every message + // instances so the developer does not have to repeat themselves every time + // they create a new message. We will just go ahead and push the address. + if (! empty($this->from['address'])) { + $message->from($this->from['address'], $this->from['name']); + } + + return $message; + } + + /** + * Render the given view. + * + * @param string $view + * @param array $data + * @return \Illuminate\View\View + */ + protected function getView($view, $data) + { + return $this->views->make($view, $data)->render(); + } + + /** + * Tell the mailer to not really send messages. + * + * @param bool $value + * @return void + */ + public function pretend($value = true) + { + $this->pretending = $value; + } + + /** + * Check if the mailer is pretending to send messages. + * + * @return bool + */ + public function isPretending() + { + return $this->pretending; + } + + /** + * Get the view factory instance. + * + * @return \Illuminate\Contracts\View\Factory + */ + public function getViewFactory() + { + return $this->views; + } + + /** + * Get the Swift Mailer instance. + * + * @return \Swift_Mailer + */ + public function getSwiftMailer() + { + return $this->swift; + } + + /** + * Get the array of failed recipients. + * + * @return array + */ + public function failures() + { + return $this->failedRecipients; + } + + /** + * Set the Swift Mailer instance. + * + * @param \Swift_Mailer $swift + * @return void + */ + public function setSwiftMailer($swift) + { + $this->swift = $swift; + } + + /** + * Set the log writer instance. + * + * @param \Psr\Log\LoggerInterface $logger + * @return $this + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + + return $this; + } + + /** + * Set the queue manager instance. + * + * @param \Illuminate\Contracts\Queue\Queue $queue + * @return $this + */ + public function setQueue(QueueContract $queue) + { + $this->queue = $queue; + + return $this; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/Message.php b/core/vendor/laravel/framework/src/Illuminate/Mail/Message.php new file mode 100644 index 0000000..5dd1fa2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/Message.php @@ -0,0 +1,296 @@ +swift = $swift; + } + + /** + * Add a "from" address to the message. + * + * @param string $address + * @param string|null $name + * @return $this + */ + public function from($address, $name = null) + { + $this->swift->setFrom($address, $name); + + return $this; + } + + /** + * Set the "sender" of the message. + * + * @param string $address + * @param string|null $name + * @return $this + */ + public function sender($address, $name = null) + { + $this->swift->setSender($address, $name); + + return $this; + } + + /** + * Set the "return path" of the message. + * + * @param string $address + * @return $this + */ + public function returnPath($address) + { + $this->swift->setReturnPath($address); + + return $this; + } + + /** + * Add a recipient to the message. + * + * @param string|array $address + * @param string|null $name + * @param bool $override + * @return $this + */ + public function to($address, $name = null, $override = false) + { + if ($override) { + return $this->swift->setTo($address, $name); + } + + return $this->addAddresses($address, $name, 'To'); + } + + /** + * Add a carbon copy to the message. + * + * @param string|array $address + * @param string|null $name + * @return $this + */ + public function cc($address, $name = null) + { + return $this->addAddresses($address, $name, 'Cc'); + } + + /** + * Add a blind carbon copy to the message. + * + * @param string|array $address + * @param string|null $name + * @return $this + */ + public function bcc($address, $name = null) + { + return $this->addAddresses($address, $name, 'Bcc'); + } + + /** + * Add a reply to address to the message. + * + * @param string $address + * @param string|null $name + * @return $this + */ + public function replyTo($address, $name = null) + { + return $this->addAddresses($address, $name, 'ReplyTo'); + } + + /** + * Add a recipient to the message. + * + * @param string|array $address + * @param string $name + * @param string $type + * @return $this + */ + protected function addAddresses($address, $name, $type) + { + if (is_array($address)) { + $this->swift->{"set{$type}"}($address, $name); + } else { + $this->swift->{"add{$type}"}($address, $name); + } + + return $this; + } + + /** + * Set the subject of the message. + * + * @param string $subject + * @return $this + */ + public function subject($subject) + { + $this->swift->setSubject($subject); + + return $this; + } + + /** + * Set the message priority level. + * + * @param int $level + * @return $this + */ + public function priority($level) + { + $this->swift->setPriority($level); + + return $this; + } + + /** + * Attach a file to the message. + * + * @param string $file + * @param array $options + * @return $this + */ + public function attach($file, array $options = []) + { + $attachment = $this->createAttachmentFromPath($file); + + return $this->prepAttachment($attachment, $options); + } + + /** + * Create a Swift Attachment instance. + * + * @param string $file + * @return \Swift_Attachment + */ + protected function createAttachmentFromPath($file) + { + return Swift_Attachment::fromPath($file); + } + + /** + * Attach in-memory data as an attachment. + * + * @param string $data + * @param string $name + * @param array $options + * @return $this + */ + public function attachData($data, $name, array $options = []) + { + $attachment = $this->createAttachmentFromData($data, $name); + + return $this->prepAttachment($attachment, $options); + } + + /** + * Create a Swift Attachment instance from data. + * + * @param string $data + * @param string $name + * @return \Swift_Attachment + */ + protected function createAttachmentFromData($data, $name) + { + return Swift_Attachment::newInstance($data, $name); + } + + /** + * Embed a file in the message and get the CID. + * + * @param string $file + * @return string + */ + public function embed($file) + { + return $this->swift->embed(Swift_Image::fromPath($file)); + } + + /** + * Embed in-memory data in the message and get the CID. + * + * @param string $data + * @param string $name + * @param string|null $contentType + * @return string + */ + public function embedData($data, $name, $contentType = null) + { + $image = Swift_Image::newInstance($data, $name, $contentType); + + return $this->swift->embed($image); + } + + /** + * Prepare and attach the given attachment. + * + * @param \Swift_Attachment $attachment + * @param array $options + * @return $this + */ + protected function prepAttachment($attachment, $options = []) + { + // First we will check for a MIME type on the message, which instructs the + // mail client on what type of attachment the file is so that it may be + // downloaded correctly by the user. The MIME option is not required. + if (isset($options['mime'])) { + $attachment->setContentType($options['mime']); + } + + // If an alternative name was given as an option, we will set that on this + // attachment so that it will be downloaded with the desired names from + // the developer, otherwise the default file names will get assigned. + if (isset($options['as'])) { + $attachment->setFilename($options['as']); + } + + $this->swift->attach($attachment); + + return $this; + } + + /** + * Get the underlying Swift Message instance. + * + * @return \Swift_Message + */ + public function getSwiftMessage() + { + return $this->swift; + } + + /** + * Dynamically pass missing methods to the Swift instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $callable = [$this->swift, $method]; + + return call_user_func_array($callable, $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/LogTransport.php b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/LogTransport.php new file mode 100644 index 0000000..9396d15 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/LogTransport.php @@ -0,0 +1,55 @@ +logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $this->beforeSendPerformed($message); + + $this->logger->debug($this->getMimeEntityString($message)); + } + + /** + * Get a loggable string out of a Swiftmailer entity. + * + * @param \Swift_Mime_MimeEntity $entity + * @return string + */ + protected function getMimeEntityString(Swift_Mime_MimeEntity $entity) + { + $string = (string) $entity->getHeaders().PHP_EOL.$entity->getBody(); + + foreach ($entity->getChildren() as $children) { + $string .= PHP_EOL.PHP_EOL.$this->getMimeEntityString($children); + } + + return $string; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/MailgunTransport.php b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/MailgunTransport.php new file mode 100644 index 0000000..d9f8704 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/MailgunTransport.php @@ -0,0 +1,146 @@ +key = $key; + $this->client = $client; + $this->setDomain($domain); + } + + /** + * {@inheritdoc} + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $this->beforeSendPerformed($message); + + $options = ['auth' => ['api', $this->key]]; + + $to = $this->getTo($message); + + $message->setBcc([]); + + if (version_compare(ClientInterface::VERSION, '6') === 1) { + $options['multipart'] = [ + ['name' => 'to', 'contents' => $to], + ['name' => 'message', 'contents' => $message->toString(), 'filename' => 'message.mime'], + ]; + } else { + $options['body'] = [ + 'to' => $to, + 'message' => new PostFile('message', $message->toString()), + ]; + } + + return $this->client->post($this->url, $options); + } + + /** + * Get the "to" payload field for the API request. + * + * @param \Swift_Mime_Message $message + * @return array + */ + protected function getTo(Swift_Mime_Message $message) + { + $formatted = []; + + $contacts = array_merge( + (array) $message->getTo(), (array) $message->getCc(), (array) $message->getBcc() + ); + + foreach ($contacts as $address => $display) { + $formatted[] = $display ? $display." <$address>" : $address; + } + + return implode(',', $formatted); + } + + /** + * Get the API key being used by the transport. + * + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * Set the API key being used by the transport. + * + * @param string $key + * @return string + */ + public function setKey($key) + { + return $this->key = $key; + } + + /** + * Get the domain being used by the transport. + * + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Set the domain being used by the transport. + * + * @param string $domain + * @return void + */ + public function setDomain($domain) + { + $this->url = 'https://api.mailgun.net/v3/'.$domain.'/messages.mime'; + + return $this->domain = $domain; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/MandrillTransport.php b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/MandrillTransport.php new file mode 100644 index 0000000..32f4e10 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/MandrillTransport.php @@ -0,0 +1,107 @@ +client = $client; + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $this->beforeSendPerformed($message); + + $data = [ + 'key' => $this->key, + 'to' => $this->getToAddresses($message), + 'raw_message' => $message->toString(), + 'async' => false, + ]; + + if (version_compare(ClientInterface::VERSION, '6') === 1) { + $options = ['form_params' => $data]; + } else { + $options = ['body' => $data]; + } + + return $this->client->post('https://mandrillapp.com/api/1.0/messages/send-raw.json', $options); + } + + /** + * Get all the addresses this message should be sent to. + * + * Note that Mandrill still respects CC, BCC headers in raw message itself. + * + * @param \Swift_Mime_Message $message + * @return array + */ + protected function getToAddresses(Swift_Mime_Message $message) + { + $to = []; + + if ($message->getTo()) { + $to = array_merge($to, array_keys($message->getTo())); + } + + if ($message->getCc()) { + $to = array_merge($to, array_keys($message->getCc())); + } + + if ($message->getBcc()) { + $to = array_merge($to, array_keys($message->getBcc())); + } + + return $to; + } + + /** + * Get the API key being used by the transport. + * + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * Set the API key being used by the transport. + * + * @param string $key + * @return string + */ + public function setKey($key) + { + return $this->key = $key; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/SesTransport.php b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/SesTransport.php new file mode 100644 index 0000000..63457ba --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/SesTransport.php @@ -0,0 +1,42 @@ +ses = $ses; + } + + /** + * {@inheritdoc} + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $this->beforeSendPerformed($message); + + return $this->ses->sendRawEmail([ + 'Source' => key($message->getSender() ?: $message->getFrom()), + 'RawMessage' => [ + 'Data' => $message->toString(), + ], + ]); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/Transport.php b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/Transport.php new file mode 100644 index 0000000..eeca110 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/Transport/Transport.php @@ -0,0 +1,70 @@ +plugins, $plugin); + } + + /** + * Iterate through registered plugins and execute plugins' methods. + * + * @param \Swift_Mime_Message $message + * @return void + */ + protected function beforeSendPerformed(Swift_Mime_Message $message) + { + $event = new Swift_Events_SendEvent($this, $message); + + foreach ($this->plugins as $plugin) { + if (method_exists($plugin, 'beforeSendPerformed')) { + $plugin->beforeSendPerformed($event); + } + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/TransportManager.php b/core/vendor/laravel/framework/src/Illuminate/Mail/TransportManager.php new file mode 100644 index 0000000..d638dcf --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/TransportManager.php @@ -0,0 +1,155 @@ +app['config']['mail']; + + // The Swift SMTP transport instance will allow us to use any SMTP backend + // for delivering mail such as Sendgrid, Amazon SES, or a custom server + // a developer has available. We will just pass this configured host. + $transport = SmtpTransport::newInstance( + $config['host'], $config['port'] + ); + + if (isset($config['encryption'])) { + $transport->setEncryption($config['encryption']); + } + + // Once we have the transport we will check for the presence of a username + // and password. If we have it we will set the credentials on the Swift + // transporter instance so that we'll properly authenticate delivery. + if (isset($config['username'])) { + $transport->setUsername($config['username']); + + $transport->setPassword($config['password']); + } + + if (isset($config['stream'])) { + $transport->setStreamOptions($config['stream']); + } + + return $transport; + } + + /** + * Create an instance of the Sendmail Swift Transport driver. + * + * @return \Swift_SendmailTransport + */ + protected function createSendmailDriver() + { + $command = $this->app['config']['mail']['sendmail']; + + return SendmailTransport::newInstance($command); + } + + /** + * Create an instance of the Amazon SES Swift Transport driver. + * + * @return \Swift_SendmailTransport + */ + protected function createSesDriver() + { + $config = $this->app['config']->get('services.ses', []); + + $config += [ + 'version' => 'latest', 'service' => 'email', + ]; + + if ($config['key'] && $config['secret']) { + $config['credentials'] = Arr::only($config, ['key', 'secret']); + } + + return new SesTransport(new SesClient($config)); + } + + /** + * Create an instance of the Mail Swift Transport driver. + * + * @return \Swift_MailTransport + */ + protected function createMailDriver() + { + return MailTransport::newInstance(); + } + + /** + * Create an instance of the Mailgun Swift Transport driver. + * + * @return \Illuminate\Mail\Transport\MailgunTransport + */ + protected function createMailgunDriver() + { + $config = $this->app['config']->get('services.mailgun', []); + + $client = new HttpClient(Arr::get($config, 'guzzle', [])); + + return new MailgunTransport($client, $config['secret'], $config['domain']); + } + + /** + * Create an instance of the Mandrill Swift Transport driver. + * + * @return \Illuminate\Mail\Transport\MandrillTransport + */ + protected function createMandrillDriver() + { + $config = $this->app['config']->get('services.mandrill', []); + + $client = new HttpClient(Arr::get($config, 'guzzle', [])); + + return new MandrillTransport($client, $config['secret']); + } + + /** + * Create an instance of the Log Swift Transport driver. + * + * @return \Illuminate\Mail\Transport\LogTransport + */ + protected function createLogDriver() + { + return new LogTransport($this->app->make('Psr\Log\LoggerInterface')); + } + + /** + * Get the default cache driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['mail.driver']; + } + + /** + * Set the default cache driver name. + * + * @param string $name + * @return void + */ + public function setDefaultDriver($name) + { + $this->app['config']['mail.driver'] = $name; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Mail/composer.json b/core/vendor/laravel/framework/src/Illuminate/Mail/composer.json new file mode 100644 index 0000000..e59faa3 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Mail/composer.json @@ -0,0 +1,40 @@ +{ + "name": "illuminate/mail", + "description": "The Illuminate Mail package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/container": "5.1.*", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "psr/log": "~1.0", + "swiftmailer/swiftmailer": "~5.1" + }, + "autoload": { + "psr-4": { + "Illuminate\\Mail\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SES mail driver (~3.0).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers (~5.3|~6.0).", + "jeremeamia/superclosure": "Required to be able to serialize closures (~2.0)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php new file mode 100644 index 0000000..40a68ee --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php @@ -0,0 +1,487 @@ += 1 && filter_var($page, FILTER_VALIDATE_INT) !== false; + } + + /** + * Create a range of pagination URLs. + * + * @param int $start + * @param int $end + * @return string + */ + public function getUrlRange($start, $end) + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * Get a URL for a given page number. + * + * @param int $page + * @return string + */ + public function url($page) + { + if ($page <= 0) { + $page = 1; + } + + // If we have any extra query string key / value pairs that need to be added + // onto the URL, we will put them in query string form and then attach it + // to the URL. This allows for extra information like sortings storage. + $parameters = [$this->pageName => $page]; + + if (count($this->query) > 0) { + $parameters = array_merge($this->query, $parameters); + } + + return $this->path.'?' + .http_build_query($parameters, '', '&') + .$this->buildFragment(); + } + + /** + * Get the URL for the previous page. + * + * @return string|null + */ + public function previousPageUrl() + { + if ($this->currentPage() > 1) { + return $this->url($this->currentPage() - 1); + } + } + + /** + * Get / set the URL fragment to be appended to URLs. + * + * @param string|null $fragment + * @return $this|string|null + */ + public function fragment($fragment = null) + { + if (is_null($fragment)) { + return $this->fragment; + } + + $this->fragment = $fragment; + + return $this; + } + + /** + * Add a set of query string values to the paginator. + * + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (is_array($key)) { + return $this->appendArray($key); + } + + return $this->addQuery($key, $value); + } + + /** + * Add an array of query string values. + * + * @param array $keys + * @return $this + */ + protected function appendArray(array $keys) + { + foreach ($keys as $key => $value) { + $this->addQuery($key, $value); + } + + return $this; + } + + /** + * Add a query string value to the paginator. + * + * @param string $key + * @param string $value + * @return $this + */ + public function addQuery($key, $value) + { + if ($key !== $this->pageName) { + $this->query[$key] = $value; + } + + return $this; + } + + /** + * Build the full fragment portion of a URL. + * + * @return string + */ + protected function buildFragment() + { + return $this->fragment ? '#'.$this->fragment : ''; + } + + /** + * Get the slice of items being paginated. + * + * @return array + */ + public function items() + { + return $this->items->all(); + } + + /** + * Get the number of the first item in the slice. + * + * @return int + */ + public function firstItem() + { + return ($this->currentPage - 1) * $this->perPage + 1; + } + + /** + * Get the number of the last item in the slice. + * + * @return int + */ + public function lastItem() + { + return $this->firstItem() + $this->count() - 1; + } + + /** + * Get the number of items shown per page. + * + * @return int + */ + public function perPage() + { + return $this->perPage; + } + + /** + * Get the current page. + * + * @return int + */ + public function currentPage() + { + return $this->currentPage; + } + + /** + * Determine if there are enough items to split into multiple pages. + * + * @return bool + */ + public function hasPages() + { + return ! ($this->currentPage() == 1 && ! $this->hasMorePages()); + } + + /** + * Resolve the current request path or return the default value. + * + * @param string $default + * @return string + */ + public static function resolveCurrentPath($default = '/') + { + if (isset(static::$currentPathResolver)) { + return call_user_func(static::$currentPathResolver); + } + + return $default; + } + + /** + * Set the current request path resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function currentPathResolver(Closure $resolver) + { + static::$currentPathResolver = $resolver; + } + + /** + * Resolve the current page or return the default value. + * + * @param string $pageName + * @param int $default + * @return int + */ + public static function resolveCurrentPage($pageName = 'page', $default = 1) + { + if (isset(static::$currentPageResolver)) { + return call_user_func(static::$currentPageResolver, $pageName); + } + + return $default; + } + + /** + * Set the current page resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function currentPageResolver(Closure $resolver) + { + static::$currentPageResolver = $resolver; + } + + /** + * Set the default Presenter resolver. + * + * @param \Closure $resolver + * @return void + */ + public static function presenter(Closure $resolver) + { + static::$presenterResolver = $resolver; + } + + /** + * Get the query string variable used to store the page. + * + * @return string + */ + public function getPageName() + { + return $this->pageName; + } + + /** + * Set the query string variable used to store the page. + * + * @param string $name + * @return $this + */ + public function setPageName($name) + { + $this->pageName = $name; + + return $this; + } + + /** + * Set the base path to assign to all URLs. + * + * @param string $path + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Determine if the list of items is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * Get the number of items for the current page. + * + * @return int + */ + public function count() + { + return $this->items->count(); + } + + /** + * Get the paginator's underlying collection. + * + * @return \Illuminate\Support\Collection + */ + public function getCollection() + { + return $this->items; + } + + /** + * Determine if the given item exists. + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key) + { + return $this->items->has($key); + } + + /** + * Get the item at the given offset. + * + * @param mixed $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->items->get($key); + } + + /** + * Set the item at the given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->items->put($key, $value); + } + + /** + * Unset the item at the given key. + * + * @param mixed $key + * @return void + */ + public function offsetUnset($key) + { + $this->items->forget($key); + } + + /** + * Make dynamic calls into the collection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->getCollection(), $method], $parameters); + } + + /** + * Render the contents of the paginator when casting to string. + * + * @return string + */ + public function __toString() + { + return $this->render(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapThreeNextPreviousButtonRendererTrait.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapThreeNextPreviousButtonRendererTrait.php new file mode 100644 index 0000000..a8fda50 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapThreeNextPreviousButtonRendererTrait.php @@ -0,0 +1,48 @@ +paginator->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->paginator->url( + $this->paginator->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text, 'prev'); + } + + /** + * Get the next page pagination element. + * + * @param string $text + * @return string + */ + public function getNextButton($text = '»') + { + // If the current page is greater than or equal to the last page, it means we + // can't go any further into the pages, as we're already on this last page + // that is available, so we will make it the "next" link style disabled. + if (! $this->paginator->hasMorePages()) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->paginator->url($this->paginator->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text, 'next'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapThreePresenter.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapThreePresenter.php new file mode 100644 index 0000000..21e647d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapThreePresenter.php @@ -0,0 +1,134 @@ +paginator = $paginator; + $this->window = is_null($window) ? UrlWindow::make($paginator) : $window->get(); + } + + /** + * Determine if the underlying paginator being presented has pages to show. + * + * @return bool + */ + public function hasPages() + { + return $this->paginator->hasPages(); + } + + /** + * Convert the URL window into Bootstrap HTML. + * + * @return string + */ + public function render() + { + if ($this->hasPages()) { + return sprintf( + '
    %s %s %s
', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + + return ''; + } + + /** + * Get HTML wrapper for an available page link. + * + * @param string $url + * @param int $page + * @param string|null $rel + * @return string + */ + protected function getAvailablePageWrapper($url, $page, $rel = null) + { + $rel = is_null($rel) ? '' : ' rel="'.$rel.'"'; + + return '
  • '.$page.'
  • '; + } + + /** + * Get HTML wrapper for disabled text. + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
  • '.$text.'
  • '; + } + + /** + * Get HTML wrapper for active text. + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
  • '.$text.'
  • '; + } + + /** + * Get a pagination "dot" element. + * + * @return string + */ + protected function getDots() + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * Get the current page from the paginator. + * + * @return int + */ + protected function currentPage() + { + return $this->paginator->currentPage(); + } + + /** + * Get the last page from the paginator. + * + * @return int + */ + protected function lastPage() + { + return $this->paginator->lastPage(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php new file mode 100644 index 0000000..8c562e1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php @@ -0,0 +1,164 @@ + $value) { + $this->{$key} = $value; + } + + $this->total = $total; + $this->perPage = $perPage; + $this->lastPage = (int) ceil($total / $perPage); + $this->path = $this->path != '/' ? rtrim($this->path, '/') : $this->path; + $this->currentPage = $this->setCurrentPage($currentPage, $this->lastPage); + $this->items = $items instanceof Collection ? $items : Collection::make($items); + } + + /** + * Get the current page for the request. + * + * @param int $currentPage + * @param int $lastPage + * @return int + */ + protected function setCurrentPage($currentPage, $lastPage) + { + $currentPage = $currentPage ?: static::resolveCurrentPage(); + + // The page number will get validated and adjusted if it either less than one + // or greater than the last page available based on the count of the given + // items array. If it's greater than the last, we'll give back the last. + if (is_numeric($currentPage) && $currentPage > $lastPage) { + return $lastPage > 0 ? $lastPage : 1; + } + + return $this->isValidPageNumber($currentPage) ? (int) $currentPage : 1; + } + + /** + * Get the URL for the next page. + * + * @return string|null + */ + public function nextPageUrl() + { + if ($this->lastPage() > $this->currentPage()) { + return $this->url($this->currentPage() + 1); + } + } + + /** + * Determine if there are more items in the data source. + * + * @return bool + */ + public function hasMorePages() + { + return $this->currentPage() < $this->lastPage(); + } + + /** + * Get the total number of items being paginated. + * + * @return int + */ + public function total() + { + return $this->total; + } + + /** + * Get the last page. + * + * @return int + */ + public function lastPage() + { + return $this->lastPage; + } + + /** + * Render the paginator using the given presenter. + * + * @param \Illuminate\Contracts\Pagination\Presenter|null $presenter + * @return string + */ + public function render(Presenter $presenter = null) + { + if (is_null($presenter) && static::$presenterResolver) { + $presenter = call_user_func(static::$presenterResolver, $this); + } + + $presenter = $presenter ?: new BootstrapThreePresenter($this); + + return $presenter->render(); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'total' => $this->total(), + 'per_page' => $this->perPage(), + 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage(), + 'next_page_url' => $this->nextPageUrl(), + 'prev_page_url' => $this->previousPageUrl(), + 'from' => $this->firstItem(), + 'to' => $this->lastItem(), + 'data' => $this->items->toArray(), + ]; + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php new file mode 100644 index 0000000..48d3e2c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php @@ -0,0 +1,30 @@ +app['request']->url(); + }); + + Paginator::currentPageResolver(function ($pageName = 'page') { + $page = $this->app['request']->input($pageName); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { + return $page; + } + + return 1; + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php new file mode 100644 index 0000000..6229f06 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php @@ -0,0 +1,135 @@ + $value) { + $this->{$key} = $value; + } + + $this->perPage = $perPage; + $this->currentPage = $this->setCurrentPage($currentPage); + $this->path = $this->path != '/' ? rtrim($this->path, '/') : $this->path; + $this->items = $items instanceof Collection ? $items : Collection::make($items); + + $this->checkForMorePages(); + } + + /** + * Get the current page for the request. + * + * @param int $currentPage + * @return int + */ + protected function setCurrentPage($currentPage) + { + $currentPage = $currentPage ?: static::resolveCurrentPage(); + + return $this->isValidPageNumber($currentPage) ? (int) $currentPage : 1; + } + + /** + * Check for more pages. The last item will be sliced off. + * + * @return void + */ + protected function checkForMorePages() + { + $this->hasMore = count($this->items) > ($this->perPage); + + $this->items = $this->items->slice(0, $this->perPage); + } + + /** + * Get the URL for the next page. + * + * @return string|null + */ + public function nextPageUrl() + { + if ($this->hasMore) { + return $this->url($this->currentPage() + 1); + } + } + + /** + * Determine if there are more items in the data source. + * + * @return bool + */ + public function hasMorePages() + { + return $this->hasMore; + } + + /** + * Render the paginator using the given presenter. + * + * @param \Illuminate\Contracts\Pagination\Presenter|null $presenter + * @return string + */ + public function render(Presenter $presenter = null) + { + if (is_null($presenter) && static::$presenterResolver) { + $presenter = call_user_func(static::$presenterResolver, $this); + } + + $presenter = $presenter ?: new SimpleBootstrapThreePresenter($this); + + return $presenter->render(); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'per_page' => $this->perPage(), 'current_page' => $this->currentPage(), + 'next_page_url' => $this->nextPageUrl(), 'prev_page_url' => $this->previousPageUrl(), + 'from' => $this->firstItem(), 'to' => $this->lastItem(), + 'data' => $this->items->toArray(), + ]; + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/SimpleBootstrapThreePresenter.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/SimpleBootstrapThreePresenter.php new file mode 100644 index 0000000..bbb2790 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/SimpleBootstrapThreePresenter.php @@ -0,0 +1,47 @@ +paginator = $paginator; + } + + /** + * Determine if the underlying paginator being presented has pages to show. + * + * @return bool + */ + public function hasPages() + { + return $this->paginator->hasPages() && count($this->paginator->items()) > 0; + } + + /** + * Convert the URL window into Bootstrap HTML. + * + * @return string + */ + public function render() + { + if ($this->hasPages()) { + return sprintf( + '
      %s %s
    ', + $this->getPreviousButton(), + $this->getNextButton() + ); + } + + return ''; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindow.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindow.php new file mode 100644 index 0000000..655f646 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindow.php @@ -0,0 +1,222 @@ +paginator = $paginator; + } + + /** + * Create a new URL window instance. + * + * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator + * @param int $onEachSide + * @return array + */ + public static function make(PaginatorContract $paginator, $onEachSide = 3) + { + return (new static($paginator))->get($onEachSide); + } + + /** + * Get the window of URLs to be shown. + * + * @param int $onEachSide + * @return array + */ + public function get($onEachSide = 3) + { + if ($this->paginator->lastPage() < ($onEachSide * 2) + 6) { + return $this->getSmallSlider(); + } + + return $this->getUrlSlider($onEachSide); + } + + /** + * Get the slider of URLs there are not enough pages to slide. + * + * @return array + */ + protected function getSmallSlider() + { + return [ + 'first' => $this->paginator->getUrlRange(1, $this->lastPage()), + 'slider' => null, + 'last' => null, + ]; + } + + /** + * Create a URL slider links. + * + * @param int $onEachSide + * @return array + */ + protected function getUrlSlider($onEachSide) + { + $window = $onEachSide * 2; + + if (! $this->hasPages()) { + return [ + 'first' => null, + 'slider' => null, + 'last' => null, + ]; + } + + // If the current page is very close to the beginning of the page range, we will + // just render the beginning of the page range, followed by the last 2 of the + // links in this list, since we will not have room to create a full slider. + if ($this->currentPage() <= $window) { + return $this->getSliderTooCloseToBeginning($window); + } + + // If the current page is close to the ending of the page range we will just get + // this first couple pages, followed by a larger window of these ending pages + // since we're too close to the end of the list to create a full on slider. + elseif ($this->currentPage() > ($this->lastPage() - $window)) { + return $this->getSliderTooCloseToEnding($window); + } + + // If we have enough room on both sides of the current page to build a slider we + // will surround it with both the beginning and ending caps, with this window + // of pages in the middle providing a Google style sliding paginator setup. + return $this->getFullSlider($onEachSide); + } + + /** + * Get the slider of URLs when too close to beginning of window. + * + * @param int $window + * @return array + */ + protected function getSliderTooCloseToBeginning($window) + { + return [ + 'first' => $this->paginator->getUrlRange(1, $window + 2), + 'slider' => null, + 'last' => $this->getFinish(), + ]; + } + + /** + * Get the slider of URLs when too close to ending of window. + * + * @param int $window + * @return array + */ + protected function getSliderTooCloseToEnding($window) + { + $last = $this->paginator->getUrlRange( + $this->lastPage() - ($window + 2), + $this->lastPage() + ); + + return [ + 'first' => $this->getStart(), + 'slider' => null, + 'last' => $last, + ]; + } + + /** + * Get the slider of URLs when a full slider can be made. + * + * @param int $onEachSide + * @return array + */ + protected function getFullSlider($onEachSide) + { + return [ + 'first' => $this->getStart(), + 'slider' => $this->getAdjacentUrlRange($onEachSide), + 'last' => $this->getFinish(), + ]; + } + + /** + * Get the page range for the current page window. + * + * @param int $onEachSide + * @return array + */ + public function getAdjacentUrlRange($onEachSide) + { + return $this->paginator->getUrlRange( + $this->currentPage() - $onEachSide, + $this->currentPage() + $onEachSide + ); + } + + /** + * Get the starting URLs of a pagination slider. + * + * @return array + */ + public function getStart() + { + return $this->paginator->getUrlRange(1, 2); + } + + /** + * Get the ending URLs of a pagination slider. + * + * @return array + */ + public function getFinish() + { + return $this->paginator->getUrlRange( + $this->lastPage() - 1, + $this->lastPage() + ); + } + + /** + * Determine if the underlying paginator being presented has pages to show. + * + * @return bool + */ + public function hasPages() + { + return $this->paginator->lastPage() > 1; + } + + /** + * Get the current page from the paginator. + * + * @return int + */ + protected function currentPage() + { + return $this->paginator->currentPage(); + } + + /** + * Get the last page from the paginator. + * + * @return int + */ + protected function lastPage() + { + return $this->paginator->lastPage(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindowPresenterTrait.php b/core/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindowPresenterTrait.php new file mode 100644 index 0000000..64a3397 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindowPresenterTrait.php @@ -0,0 +1,66 @@ +window['first'])) { + $html .= $this->getUrlLinks($this->window['first']); + } + + if (is_array($this->window['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($this->window['slider']); + } + + if (is_array($this->window['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($this->window['last']); + } + + return $html; + } + + /** + * Get the links for the URLs in the given array. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls) + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * Get HTML wrapper for a page link. + * + * @param string $url + * @param int $page + * @param string|null $rel + * @return string + */ + protected function getPageLinkWrapper($url, $page, $rel = null) + { + if ($page == $this->paginator->currentPage()) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page, $rel); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pagination/composer.json b/core/vendor/laravel/framework/src/Illuminate/Pagination/composer.json new file mode 100644 index 0000000..871c66e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pagination/composer.json @@ -0,0 +1,32 @@ +{ + "name": "illuminate/pagination", + "description": "The Illuminate Pagination package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Pagination\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pipeline/Hub.php b/core/vendor/laravel/framework/src/Illuminate/Pipeline/Hub.php new file mode 100644 index 0000000..ed48df4 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pipeline/Hub.php @@ -0,0 +1,74 @@ +container = $container; + } + + /** + * Define the default named pipeline. + * + * @param \Closure $callback + * @return void + */ + public function defaults(Closure $callback) + { + return $this->pipeline('default', $callback); + } + + /** + * Define a new named pipeline. + * + * @param string $name + * @param \Closure $callback + * @return void + */ + public function pipeline($name, Closure $callback) + { + $this->pipelines[$name] = $callback; + } + + /** + * Send an object through one of the available pipelines. + * + * @param mixed $object + * @param string|null $pipeline + * @return mixed + */ + public function pipe($object, $pipeline = null) + { + $pipeline = $pipeline ?: 'default'; + + return call_user_func( + $this->pipelines[$pipeline], new Pipeline($this->container), $object + ); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php b/core/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php new file mode 100644 index 0000000..12aa6d7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php @@ -0,0 +1,159 @@ +container = $container; + } + + /** + * Set the object being sent through the pipeline. + * + * @param mixed $passable + * @return $this + */ + public function send($passable) + { + $this->passable = $passable; + + return $this; + } + + /** + * Set the array of pipes. + * + * @param array|mixed $pipes + * @return $this + */ + public function through($pipes) + { + $this->pipes = is_array($pipes) ? $pipes : func_get_args(); + + return $this; + } + + /** + * Set the method to call on the pipes. + * + * @param string $method + * @return $this + */ + public function via($method) + { + $this->method = $method; + + return $this; + } + + /** + * Run the pipeline with a final destination callback. + * + * @param \Closure $destination + * @return mixed + */ + public function then(Closure $destination) + { + $firstSlice = $this->getInitialSlice($destination); + + $pipes = array_reverse($this->pipes); + + return call_user_func( + array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable + ); + } + + /** + * Get a Closure that represents a slice of the application onion. + * + * @return \Closure + */ + protected function getSlice() + { + return function ($stack, $pipe) { + return function ($passable) use ($stack, $pipe) { + // If the pipe is an instance of a Closure, we will just call it directly but + // otherwise we'll resolve the pipes out of the container and call it with + // the appropriate method and arguments, returning the results back out. + if ($pipe instanceof Closure) { + return call_user_func($pipe, $passable, $stack); + } else { + list($name, $parameters) = $this->parsePipeString($pipe); + + return call_user_func_array([$this->container->make($name), $this->method], + array_merge([$passable, $stack], $parameters)); + } + }; + }; + } + + /** + * Get the initial slice to begin the stack call. + * + * @param \Closure $destination + * @return \Closure + */ + protected function getInitialSlice(Closure $destination) + { + return function ($passable) use ($destination) { + return call_user_func($destination, $passable); + }; + } + + /** + * Parse full pipe string to get name and parameters. + * + * @param string $pipe + * @return array + */ + protected function parsePipeString($pipe) + { + list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []); + + if (is_string($parameters)) { + $parameters = explode(',', $parameters); + } + + return [$name, $parameters]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php new file mode 100644 index 0000000..aceaddf --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php @@ -0,0 +1,39 @@ +app->singleton( + 'Illuminate\Contracts\Pipeline\Hub', 'Illuminate\Pipeline\Hub' + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'Illuminate\Contracts\Pipeline\Hub', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Pipeline/composer.json b/core/vendor/laravel/framework/src/Illuminate/Pipeline/composer.json new file mode 100644 index 0000000..85b0734 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Pipeline/composer.json @@ -0,0 +1,32 @@ +{ + "name": "illuminate/pipeline", + "description": "The Illuminate Pipeline package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php b/core/vendor/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php new file mode 100644 index 0000000..ad090ac --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php @@ -0,0 +1,143 @@ +default = $default; + $this->timeToRun = $timeToRun; + $this->pheanstalk = $pheanstalk; + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + return $this->pushRaw($this->createPayload($job, $data), $queue); + } + + /** + * Push a raw payload onto the queue. + * + * @param string $payload + * @param string $queue + * @param array $options + * @return mixed + */ + public function pushRaw($payload, $queue = null, array $options = []) + { + return $this->pheanstalk->useTube($this->getQueue($queue))->put( + $payload, Pheanstalk::DEFAULT_PRIORITY, Pheanstalk::DEFAULT_DELAY, $this->timeToRun + ); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param \DateTime|int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + $pheanstalk = $this->pheanstalk->useTube($this->getQueue($queue)); + + return $pheanstalk->put($payload, Pheanstalk::DEFAULT_PRIORITY, $this->getSeconds($delay), $this->timeToRun); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Contracts\Queue\Job|null + */ + public function pop($queue = null) + { + $queue = $this->getQueue($queue); + + $job = $this->pheanstalk->watchOnly($queue)->reserve(0); + + if ($job instanceof PheanstalkJob) { + return new BeanstalkdJob($this->container, $this->pheanstalk, $job, $queue); + } + } + + /** + * Delete a message from the Beanstalk queue. + * + * @param string $queue + * @param string $id + * @return void + */ + public function deleteMessage($queue, $id) + { + $this->pheanstalk->useTube($this->getQueue($queue))->delete($id); + } + + /** + * Get the queue or return the default. + * + * @param string|null $queue + * @return string + */ + public function getQueue($queue) + { + return $queue ?: $this->default; + } + + /** + * Get the underlying Pheanstalk instance. + * + * @return \Pheanstalk\Pheanstalk + */ + public function getPheanstalk() + { + return $this->pheanstalk; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php b/core/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php new file mode 100644 index 0000000..dc16c88 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php @@ -0,0 +1,80 @@ +dispatcher = $dispatcher; + } + + /** + * Handle the queued job. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param array $data + * @return void + */ + public function call(Job $job, array $data) + { + $command = $this->setJobInstanceIfNecessary( + $job, unserialize($data['command']) + ); + + $this->dispatcher->dispatchNow($command, function ($handler) use ($job) { + $this->setJobInstanceIfNecessary($job, $handler); + }); + + if (! $job->isDeletedOrReleased()) { + $job->delete(); + } + } + + /** + * Set the job instance of the given class if necessary. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param mixed $instance + * @return mixed + */ + protected function setJobInstanceIfNecessary(Job $job, $instance) + { + if (in_array('Illuminate\Queue\InteractsWithQueue', class_uses_recursive(get_class($instance)))) { + $instance->setJob($job); + } + + return $instance; + } + + /** + * Call the failed method on the job instance. + * + * @param array $data + * @return void + */ + public function failed(array $data) + { + $handler = $this->dispatcher->resolveHandler($command = unserialize($data['command'])); + + if (method_exists($handler, 'failed')) { + call_user_func([$handler, 'failed'], $command); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Capsule/Manager.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Capsule/Manager.php new file mode 100644 index 0000000..179be60 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Capsule/Manager.php @@ -0,0 +1,183 @@ +setupContainer($container ?: new Container); + + // Once we have the container setup, we will setup the default configuration + // options in the container "config" bindings. This just makes this queue + // manager behave correctly since all the correct binding are in place. + $this->setupDefaultConfiguration(); + + $this->setupManager(); + + $this->registerConnectors(); + } + + /** + * Setup the default queue configuration options. + * + * @return void + */ + protected function setupDefaultConfiguration() + { + $this->container['config']['queue.default'] = 'default'; + } + + /** + * Build the queue manager instance. + * + * @return void + */ + protected function setupManager() + { + $this->manager = new QueueManager($this->container); + } + + /** + * Register the default connectors that the component ships with. + * + * @return void + */ + protected function registerConnectors() + { + $provider = new QueueServiceProvider($this->container); + + $provider->registerConnectors($this->manager); + } + + /** + * Get a connection instance from the global manager. + * + * @param string $connection + * @return \Illuminate\Contracts\Queue\Queue + */ + public static function connection($connection = null) + { + return static::$instance->getConnection($connection); + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @param string $connection + * @return mixed + */ + public static function push($job, $data = '', $queue = null, $connection = null) + { + return static::$instance->connection($connection)->push($job, $data, $queue); + } + + /** + * Push a new an array of jobs onto the queue. + * + * @param array $jobs + * @param mixed $data + * @param string $queue + * @param string $connection + * @return mixed + */ + public static function bulk($jobs, $data = '', $queue = null, $connection = null) + { + return static::$instance->connection($connection)->bulk($jobs, $data, $queue); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param \DateTime|int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @param string $connection + * @return mixed + */ + public static function later($delay, $job, $data = '', $queue = null, $connection = null) + { + return static::$instance->connection($connection)->later($delay, $job, $data, $queue); + } + + /** + * Get a registered connection instance. + * + * @param string $name + * @return \Illuminate\Contracts\Queue\Queue + */ + public function getConnection($name = null) + { + return $this->manager->connection($name); + } + + /** + * Register a connection with the manager. + * + * @param array $config + * @param string $name + * @return void + */ + public function addConnection(array $config, $name = 'default') + { + $this->container['config']["queue.connections.{$name}"] = $config; + } + + /** + * Get the queue manager instance. + * + * @return \Illuminate\Queue\QueueManager + */ + public function getQueueManager() + { + return $this->manager; + } + + /** + * Pass dynamic instance methods to the manager. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->manager, $method], $parameters); + } + + /** + * Dynamically pass methods to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return call_user_func_array([static::connection(), $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php new file mode 100644 index 0000000..1087415 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php @@ -0,0 +1,26 @@ +connections = $connections; + } + + /** + * Establish a queue connection. + * + * @param array $config + * @return \Illuminate\Contracts\Queue\Queue + */ + public function connect(array $config) + { + return new DatabaseQueue( + $this->connections->connection(Arr::get($config, 'connection')), + $config['table'], + $config['queue'], + Arr::get($config, 'expire', 60) + ); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/IronConnector.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/IronConnector.php new file mode 100644 index 0000000..d103c19 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/IronConnector.php @@ -0,0 +1,61 @@ +crypt = $crypt; + $this->request = $request; + } + + /** + * Establish a queue connection. + * + * @param array $config + * @return \Illuminate\Contracts\Queue\Queue + */ + public function connect(array $config) + { + $ironConfig = ['token' => $config['token'], 'project_id' => $config['project']]; + + if (isset($config['host'])) { + $ironConfig['host'] = $config['host']; + } + + $iron = new IronMQ($ironConfig); + + if (isset($config['ssl_verifypeer'])) { + $iron->ssl_verifypeer = $config['ssl_verifypeer']; + } + + return new IronQueue($iron, $this->request, $config['queue'], $config['encrypt']); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/NullConnector.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/NullConnector.php new file mode 100644 index 0000000..39de480 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/NullConnector.php @@ -0,0 +1,19 @@ +redis = $redis; + $this->connection = $connection; + } + + /** + * Establish a queue connection. + * + * @param array $config + * @return \Illuminate\Contracts\Queue\Queue + */ + public function connect(array $config) + { + $queue = new RedisQueue( + $this->redis, $config['queue'], Arr::get($config, 'connection', $this->connection) + ); + + $queue->setExpire(Arr::get($config, 'expire', 60)); + + return $queue; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SqsConnector.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SqsConnector.php new file mode 100644 index 0000000..4e8e5e6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SqsConnector.php @@ -0,0 +1,46 @@ +getDefaultConfiguration($config); + + if ($config['key'] && $config['secret']) { + $config['credentials'] = Arr::only($config, ['key', 'secret']); + } + + return new SqsQueue( + new SqsClient($config), $config['queue'], Arr::get($config, 'prefix', '') + ); + } + + /** + * Get the default configuration for SQS. + * + * @param array $config + * @return array + */ + protected function getDefaultConfiguration(array $config) + { + return array_merge([ + 'version' => 'latest', + 'http' => [ + 'timeout' => 60, + 'connect_timeout' => 60, + ], + ], $config); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SyncConnector.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SyncConnector.php new file mode 100644 index 0000000..4269b80 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SyncConnector.php @@ -0,0 +1,19 @@ +files = $files; + $this->composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $table = $this->laravel['config']['queue.failed.table']; + + $tableClassName = Str::studly($table); + + $fullPath = $this->createBaseMigration($table); + + $stub = str_replace( + ['{{table}}', '{{tableClassName}}'], [$table, $tableClassName], $this->files->get(__DIR__.'/stubs/failed_jobs.stub') + ); + + $this->files->put($fullPath, $stub); + + $this->info('Migration created successfully!'); + + $this->composer->dumpAutoloads(); + } + + /** + * Create a base migration file for the table. + * + * @param string $table + * @return string + */ + protected function createBaseMigration($table = 'failed_jobs') + { + $name = 'create_'.$table.'_table'; + + $path = $this->laravel->databasePath().'/migrations'; + + return $this->laravel['migration.creator']->create($name, $path); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/FlushFailedCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/FlushFailedCommand.php new file mode 100644 index 0000000..fc21093 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/FlushFailedCommand.php @@ -0,0 +1,34 @@ +laravel['queue.failer']->flush(); + + $this->info('All failed jobs deleted successfully!'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ForgetFailedCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ForgetFailedCommand.php new file mode 100644 index 0000000..2a016cf --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ForgetFailedCommand.php @@ -0,0 +1,49 @@ +laravel['queue.failer']->forget($this->argument('id'))) { + $this->info('Failed job deleted successfully!'); + } else { + $this->error('No failed job matches the given ID.'); + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['id', InputArgument::REQUIRED, 'The ID of the failed job'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ListFailedCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ListFailedCommand.php new file mode 100644 index 0000000..27b4df7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ListFailedCommand.php @@ -0,0 +1,113 @@ +getFailedJobs(); + + if (count($jobs) == 0) { + return $this->info('No failed jobs!'); + } + + $this->displayFailedJobs($jobs); + } + + /** + * Compile the failed jobs into a displayable format. + * + * @return array + */ + protected function getFailedJobs() + { + $results = []; + + foreach ($this->laravel['queue.failer']->all() as $failed) { + $results[] = $this->parseFailedJob((array) $failed); + } + + return array_filter($results); + } + + /** + * Parse the failed job row. + * + * @param array $failed + * @return array + */ + protected function parseFailedJob(array $failed) + { + $row = array_values(Arr::except($failed, ['payload'])); + + array_splice($row, 3, 0, $this->extractJobName($failed['payload'])); + + return $row; + } + + /** + * Extract the failed job name from payload. + * + * @param string $payload + * @return string|null + */ + private function extractJobName($payload) + { + $payload = json_decode($payload, true); + + if ($payload && (! isset($payload['data']['command']))) { + return Arr::get($payload, 'job'); + } + + if ($payload && isset($payload['data']['command'])) { + preg_match('/"([^"]+)"/', $payload['data']['command'], $matches); + + if (isset($matches[1])) { + return $matches[1]; + } else { + return Arr::get($payload, 'job'); + } + } + } + + /** + * Display the failed jobs in the console. + * + * @param array $jobs + * @return void + */ + protected function displayFailedJobs(array $jobs) + { + $this->table($this->headers, $jobs); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ListenCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ListenCommand.php new file mode 100644 index 0000000..67de1d1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/ListenCommand.php @@ -0,0 +1,144 @@ +listener = $listener; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->setListenerOptions(); + + $delay = $this->input->getOption('delay'); + + // The memory limit is the amount of memory we will allow the script to occupy + // before killing it and letting a process manager restart it for us, which + // is to protect us against any memory leaks that will be in the scripts. + $memory = $this->input->getOption('memory'); + + $connection = $this->input->getArgument('connection'); + + $timeout = $this->input->getOption('timeout'); + + // We need to get the right queue for the connection which is set in the queue + // configuration file for the application. We will pull it based on the set + // connection being run for the queue operation currently being executed. + $queue = $this->getQueue($connection); + + $this->listener->listen( + $connection, $queue, $delay, $memory, $timeout + ); + } + + /** + * Get the name of the queue connection to listen on. + * + * @param string $connection + * @return string + */ + protected function getQueue($connection) + { + if (is_null($connection)) { + $connection = $this->laravel['config']['queue.default']; + } + + $queue = $this->laravel['config']->get("queue.connections.{$connection}.queue", 'default'); + + return $this->input->getOption('queue') ?: $queue; + } + + /** + * Set the options on the queue listener. + * + * @return void + */ + protected function setListenerOptions() + { + $this->listener->setEnvironment($this->laravel->environment()); + + $this->listener->setSleep($this->option('sleep')); + + $this->listener->setMaxTries($this->option('tries')); + + $this->listener->setOutputHandler(function ($type, $line) { + $this->output->write($line); + }); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['connection', InputArgument::OPTIONAL, 'The name of connection'], + ]; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['queue', null, InputOption::VALUE_OPTIONAL, 'The queue to listen on', null], + + ['delay', null, InputOption::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0], + + ['memory', null, InputOption::VALUE_OPTIONAL, 'The memory limit in megabytes', 128], + + ['timeout', null, InputOption::VALUE_OPTIONAL, 'Seconds a job may run before timing out', 60], + + ['sleep', null, InputOption::VALUE_OPTIONAL, 'Seconds to wait before checking queue for jobs', 3], + + ['tries', null, InputOption::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 0], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/RestartCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/RestartCommand.php new file mode 100644 index 0000000..72ad27e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/RestartCommand.php @@ -0,0 +1,34 @@ +laravel['cache']->forever('illuminate:queue:restart', time()); + + $this->info('Broadcasting queue restart signal.'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/RetryCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/RetryCommand.php new file mode 100644 index 0000000..703c8d5 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/RetryCommand.php @@ -0,0 +1,97 @@ +argument('id'); + + if (count($ids) === 1 && $ids[0] === 'all') { + $ids = Arr::pluck($this->laravel['queue.failer']->all(), 'id'); + } + + foreach ($ids as $id) { + $this->retryJob($id); + } + } + + /** + * Retry the queue job with the given ID. + * + * @param string $id + * @return void + */ + protected function retryJob($id) + { + $failed = $this->laravel['queue.failer']->find($id); + + if (! is_null($failed)) { + $failed = (object) $failed; + + $failed->payload = $this->resetAttempts($failed->payload); + + $this->laravel['queue']->connection($failed->connection) + ->pushRaw($failed->payload, $failed->queue); + + $this->laravel['queue.failer']->forget($failed->id); + + $this->info("The failed job [{$id}] has been pushed back onto the queue!"); + } else { + $this->error("No failed job matches the given ID [{$id}]."); + } + } + + /** + * Reset the payload attempts. + * + * @param string $payload + * @return string + */ + protected function resetAttempts($payload) + { + $payload = json_decode($payload, true); + + if (isset($payload['attempts'])) { + $payload['attempts'] = 1; + } + + return json_encode($payload); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['id', InputArgument::IS_ARRAY, 'The ID of the failed job'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/SubscribeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/SubscribeCommand.php new file mode 100644 index 0000000..a19be46 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/SubscribeCommand.php @@ -0,0 +1,162 @@ +laravel['queue']->connection(); + + if (! $iron instanceof IronQueue) { + throw new RuntimeException('Iron.io based queue must be default.'); + } + + $iron->getIron()->updateQueue($this->argument('queue'), $this->getQueueOptions()); + + $this->line('Queue subscriber added: '.$this->argument('url').''); + } + + /** + * Get the queue options. + * + * @return array + */ + protected function getQueueOptions() + { + return [ + 'push_type' => $this->getPushType(), 'subscribers' => $this->getSubscriberList(), + ]; + } + + /** + * Get the push type for the queue. + * + * @return string + */ + protected function getPushType() + { + if ($this->option('type')) { + return $this->option('type'); + } + + try { + return $this->getQueue()->push_type; + } catch (Exception $e) { + return 'multicast'; + } + } + + /** + * Get the current subscribers for the queue. + * + * @return array + */ + protected function getSubscriberList() + { + $subscribers = $this->getCurrentSubscribers(); + + $url = $this->argument('url'); + + if (! Str::startsWith($url, ['http://', 'https://'])) { + $url = $this->laravel['url']->to($url); + } + + $subscribers[] = ['url' => $url]; + + return $subscribers; + } + + /** + * Get the current subscriber list. + * + * @return array + */ + protected function getCurrentSubscribers() + { + try { + return $this->getQueue()->subscribers; + } catch (Exception $e) { + return []; + } + } + + /** + * Get the queue information from Iron.io. + * + * @return object + */ + protected function getQueue() + { + if (isset($this->meta)) { + return $this->meta; + } + + return $this->meta = $this->laravel['queue']->getIron()->getQueue($this->argument('queue')); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['queue', InputArgument::REQUIRED, 'The name of Iron.io queue.'], + + ['url', InputArgument::REQUIRED, 'The URL to be subscribed.'], + ]; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['type', null, InputOption::VALUE_OPTIONAL, 'The push type for the queue.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/TableCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/TableCommand.php new file mode 100644 index 0000000..f41d15c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/TableCommand.php @@ -0,0 +1,91 @@ +files = $files; + $this->composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $table = $this->laravel['config']['queue.connections.database.table']; + + $tableClassName = Str::studly($table); + + $fullPath = $this->createBaseMigration($table); + + $stub = str_replace( + ['{{table}}', '{{tableClassName}}'], [$table, $tableClassName], $this->files->get(__DIR__.'/stubs/jobs.stub') + ); + + $this->files->put($fullPath, $stub); + + $this->info('Migration created successfully!'); + + $this->composer->dumpAutoloads(); + } + + /** + * Create a base migration file for the table. + * + * @param string $table + * @return string + */ + protected function createBaseMigration($table = 'jobs') + { + $name = 'create_'.$table.'_table'; + + $path = $this->laravel->databasePath().'/migrations'; + + return $this->laravel['migration.creator']->create($name, $path); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php new file mode 100644 index 0000000..586fa6e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php @@ -0,0 +1,177 @@ +worker = $worker; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + if ($this->downForMaintenance() && ! $this->option('daemon')) { + return $this->worker->sleep($this->option('sleep')); + } + + $queue = $this->option('queue'); + + $delay = $this->option('delay'); + + // The memory limit is the amount of memory we will allow the script to occupy + // before killing it and letting a process manager restart it for us, which + // is to protect us against any memory leaks that will be in the scripts. + $memory = $this->option('memory'); + + $connection = $this->argument('connection'); + + $response = $this->runWorker( + $connection, $queue, $delay, $memory, $this->option('daemon') + ); + + // If a job was fired by the worker, we'll write the output out to the console + // so that the developer can watch live while the queue runs in the console + // window, which will also of get logged if stdout is logged out to disk. + if (! is_null($response['job'])) { + $this->writeOutput($response['job'], $response['failed']); + } + } + + /** + * Run the worker instance. + * + * @param string $connection + * @param string $queue + * @param int $delay + * @param int $memory + * @param bool $daemon + * @return array + */ + protected function runWorker($connection, $queue, $delay, $memory, $daemon = false) + { + $this->worker->setDaemonExceptionHandler( + $this->laravel['Illuminate\Contracts\Debug\ExceptionHandler'] + ); + + if ($daemon) { + $this->worker->setCache($this->laravel['cache']->driver()); + + return $this->worker->daemon( + $connection, $queue, $delay, $memory, + $this->option('sleep'), $this->option('tries') + ); + } + + return $this->worker->pop( + $connection, $queue, $delay, + $this->option('sleep'), $this->option('tries') + ); + } + + /** + * Write the status output for the queue worker. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param bool $failed + * @return void + */ + protected function writeOutput(Job $job, $failed) + { + if ($failed) { + $this->output->writeln('Failed: '.$job->getName()); + } else { + $this->output->writeln('Processed: '.$job->getName()); + } + } + + /** + * Determine if the worker should run in maintenance mode. + * + * @return bool + */ + protected function downForMaintenance() + { + if ($this->option('force')) { + return false; + } + + return $this->laravel->isDownForMaintenance(); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['connection', InputArgument::OPTIONAL, 'The name of connection', null], + ]; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['queue', null, InputOption::VALUE_OPTIONAL, 'The queue to listen on'], + + ['daemon', null, InputOption::VALUE_NONE, 'Run the worker in daemon mode'], + + ['delay', null, InputOption::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0], + + ['force', null, InputOption::VALUE_NONE, 'Force the worker to run even in maintenance mode'], + + ['memory', null, InputOption::VALUE_OPTIONAL, 'The memory limit in megabytes', 128], + + ['sleep', null, InputOption::VALUE_OPTIONAL, 'Number of seconds to sleep when no job is available', 3], + + ['tries', null, InputOption::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 0], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/stubs/failed_jobs.stub b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/stubs/failed_jobs.stub new file mode 100644 index 0000000..06d00bd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/stubs/failed_jobs.stub @@ -0,0 +1,33 @@ +increments('id'); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('{{table}}'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Console/stubs/jobs.stub b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/stubs/jobs.stub new file mode 100644 index 0000000..1866669 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Console/stubs/jobs.stub @@ -0,0 +1,37 @@ +bigIncrements('id'); + $table->string('queue'); + $table->longText('payload'); + $table->tinyInteger('attempts')->unsigned(); + $table->tinyInteger('reserved')->unsigned(); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + $table->index(['queue', 'reserved', 'reserved_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('{{table}}'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/ConsoleServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Queue/ConsoleServiceProvider.php new file mode 100644 index 0000000..395e686 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/ConsoleServiceProvider.php @@ -0,0 +1,71 @@ +app->singleton('command.queue.table', function ($app) { + return new TableCommand($app['files'], $app['composer']); + }); + + $this->app->singleton('command.queue.failed', function () { + return new ListFailedCommand; + }); + + $this->app->singleton('command.queue.retry', function () { + return new RetryCommand; + }); + + $this->app->singleton('command.queue.forget', function () { + return new ForgetFailedCommand; + }); + + $this->app->singleton('command.queue.flush', function () { + return new FlushFailedCommand; + }); + + $this->app->singleton('command.queue.failed-table', function ($app) { + return new FailedTableCommand($app['files'], $app['composer']); + }); + + $this->commands( + 'command.queue.table', 'command.queue.failed', 'command.queue.retry', + 'command.queue.forget', 'command.queue.flush', 'command.queue.failed-table' + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'command.queue.table', 'command.queue.failed', 'command.queue.retry', + 'command.queue.forget', 'command.queue.flush', 'command.queue.failed-table', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/DatabaseQueue.php b/core/vendor/laravel/framework/src/Illuminate/Queue/DatabaseQueue.php new file mode 100644 index 0000000..7aa6db8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/DatabaseQueue.php @@ -0,0 +1,323 @@ +table = $table; + $this->expire = $expire; + $this->default = $default; + $this->database = $database; + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + return $this->pushToDatabase(0, $queue, $this->createPayload($job, $data)); + } + + /** + * Push a raw payload onto the queue. + * + * @param string $payload + * @param string $queue + * @param array $options + * @return mixed + */ + public function pushRaw($payload, $queue = null, array $options = []) + { + return $this->pushToDatabase(0, $queue, $payload); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param \DateTime|int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return void + */ + public function later($delay, $job, $data = '', $queue = null) + { + return $this->pushToDatabase($delay, $queue, $this->createPayload($job, $data)); + } + + /** + * Push an array of jobs onto the queue. + * + * @param array $jobs + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function bulk($jobs, $data = '', $queue = null) + { + $queue = $this->getQueue($queue); + + $availableAt = $this->getAvailableAt(0); + + $records = array_map(function ($job) use ($queue, $data, $availableAt) { + return $this->buildDatabaseRecord( + $queue, $this->createPayload($job, $data), $availableAt + ); + }, (array) $jobs); + + return $this->database->table($this->table)->insert($records); + } + + /** + * Release a reserved job back onto the queue. + * + * @param string $queue + * @param \StdClass $job + * @param int $delay + * @return mixed + */ + public function release($queue, $job, $delay) + { + return $this->pushToDatabase($delay, $queue, $job->payload, $job->attempts); + } + + /** + * Push a raw payload to the database with a given delay. + * + * @param \DateTime|int $delay + * @param string|null $queue + * @param string $payload + * @param int $attempts + * @return mixed + */ + protected function pushToDatabase($delay, $queue, $payload, $attempts = 0) + { + $attributes = $this->buildDatabaseRecord( + $this->getQueue($queue), $payload, $this->getAvailableAt($delay), $attempts + ); + + return $this->database->table($this->table)->insertGetId($attributes); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Contracts\Queue\Job|null + */ + public function pop($queue = null) + { + $queue = $this->getQueue($queue); + + if (! is_null($this->expire)) { + $this->releaseJobsThatHaveBeenReservedTooLong($queue); + } + + if ($job = $this->getNextAvailableJob($queue)) { + $this->markJobAsReserved($job->id); + + $this->database->commit(); + + return new DatabaseJob( + $this->container, $this, $job, $queue + ); + } + + $this->database->commit(); + } + + /** + * Release the jobs that have been reserved for too long. + * + * @param string $queue + * @return void + */ + protected function releaseJobsThatHaveBeenReservedTooLong($queue) + { + $expired = Carbon::now()->subSeconds($this->expire)->getTimestamp(); + + $this->database->table($this->table) + ->where('queue', $this->getQueue($queue)) + ->where('reserved', 1) + ->where('reserved_at', '<=', $expired) + ->update([ + 'reserved' => 0, + 'reserved_at' => null, + 'attempts' => new Expression('attempts + 1'), + ]); + } + + /** + * Get the next available job for the queue. + * + * @param string|null $queue + * @return \StdClass|null + */ + protected function getNextAvailableJob($queue) + { + $this->database->beginTransaction(); + + $job = $this->database->table($this->table) + ->lockForUpdate() + ->where('queue', $this->getQueue($queue)) + ->where('reserved', 0) + ->where('available_at', '<=', $this->getTime()) + ->orderBy('id', 'asc') + ->first(); + + return $job ? (object) $job : null; + } + + /** + * Mark the given job ID as reserved. + * + * @param string $id + * @return void + */ + protected function markJobAsReserved($id) + { + $this->database->table($this->table)->where('id', $id)->update([ + 'reserved' => 1, 'reserved_at' => $this->getTime(), + ]); + } + + /** + * Delete a reserved job from the queue. + * + * @param string $queue + * @param string $id + * @return void + */ + public function deleteReserved($queue, $id) + { + $this->database->table($this->table)->where('id', $id)->delete(); + } + + /** + * Get the "available at" UNIX timestamp. + * + * @param \DateTime|int $delay + * @return int + */ + protected function getAvailableAt($delay) + { + $availableAt = $delay instanceof DateTime ? $delay : Carbon::now()->addSeconds($delay); + + return $availableAt->getTimestamp(); + } + + /** + * Create an array to insert for the given job. + * + * @param string|null $queue + * @param string $payload + * @param int $availableAt + * @param int $attempts + * @return array + */ + protected function buildDatabaseRecord($queue, $payload, $availableAt, $attempts = 0) + { + return [ + 'queue' => $queue, + 'payload' => $payload, + 'attempts' => $attempts, + 'reserved' => 0, + 'reserved_at' => null, + 'available_at' => $availableAt, + 'created_at' => $this->getTime(), + ]; + } + + /** + * Get the queue or return the default. + * + * @param string|null $queue + * @return string + */ + protected function getQueue($queue) + { + return $queue ?: $this->default; + } + + /** + * Get the underlying database instance. + * + * @return \Illuminate\Database\Connection + */ + public function getDatabase() + { + return $this->database; + } + + /** + * Get the expiration time in seconds. + * + * @return int|null + */ + public function getExpire() + { + return $this->expire; + } + + /** + * Set the expiration time in seconds. + * + * @param int|null $seconds + * @return void + */ + public function setExpire($seconds) + { + $this->expire = $seconds; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Failed/DatabaseFailedJobProvider.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Failed/DatabaseFailedJobProvider.php new file mode 100644 index 0000000..5758627 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Failed/DatabaseFailedJobProvider.php @@ -0,0 +1,112 @@ +table = $table; + $this->resolver = $resolver; + $this->database = $database; + } + + /** + * Log a failed job into storage. + * + * @param string $connection + * @param string $queue + * @param string $payload + * @return void + */ + public function log($connection, $queue, $payload) + { + $failed_at = Carbon::now(); + + $this->getTable()->insert(compact('connection', 'queue', 'payload', 'failed_at')); + } + + /** + * Get a list of all of the failed jobs. + * + * @return array + */ + public function all() + { + return $this->getTable()->orderBy('id', 'desc')->get(); + } + + /** + * Get a single failed job. + * + * @param mixed $id + * @return array + */ + public function find($id) + { + return $this->getTable()->find($id); + } + + /** + * Delete a single failed job from storage. + * + * @param mixed $id + * @return bool + */ + public function forget($id) + { + return $this->getTable()->where('id', $id)->delete() > 0; + } + + /** + * Flush all of the failed jobs from storage. + * + * @return void + */ + public function flush() + { + $this->getTable()->delete(); + } + + /** + * Get a new query builder instance for the table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function getTable() + { + return $this->resolver->connection($this->database)->table($this->table); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Failed/FailedJobProviderInterface.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Failed/FailedJobProviderInterface.php new file mode 100644 index 0000000..1b207c0 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Failed/FailedJobProviderInterface.php @@ -0,0 +1,46 @@ +crypt = $crypt; + } + + /** + * Fire the Closure based queue job. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param array $data + * @return void + */ + public function fire($job, $data) + { + $closure = unserialize($this->crypt->decrypt($data['closure'])); + + $closure($job); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/InteractsWithQueue.php b/core/vendor/laravel/framework/src/Illuminate/Queue/InteractsWithQueue.php new file mode 100644 index 0000000..bf1e021 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/InteractsWithQueue.php @@ -0,0 +1,63 @@ +job) { + return $this->job->delete(); + } + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + if ($this->job) { + return $this->job->release($delay); + } + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + return $this->job ? $this->job->attempts() : 1; + } + + /** + * Set the base queue job instance. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @return $this + */ + public function setJob(JobContract $job) + { + $this->job = $job; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/IronQueue.php b/core/vendor/laravel/framework/src/Illuminate/Queue/IronQueue.php new file mode 100644 index 0000000..6831889 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/IronQueue.php @@ -0,0 +1,263 @@ +iron = $iron; + $this->request = $request; + $this->default = $default; + $this->shouldEncrypt = $shouldEncrypt; + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + return $this->pushRaw($this->createPayload($job, $data, $queue), $queue); + } + + /** + * Push a raw payload onto the queue. + * + * @param string $payload + * @param string $queue + * @param array $options + * @return mixed + */ + public function pushRaw($payload, $queue = null, array $options = []) + { + if ($this->shouldEncrypt) { + $payload = $this->crypt->encrypt($payload); + } + + return $this->iron->postMessage($this->getQueue($queue), $payload, $options)->id; + } + + /** + * Push a raw payload onto the queue after encrypting the payload. + * + * @param string $payload + * @param string $queue + * @param int $delay + * @return mixed + */ + public function recreate($payload, $queue, $delay) + { + $options = ['delay' => $this->getSeconds($delay)]; + + return $this->pushRaw($payload, $queue, $options); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param \DateTime|int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + $delay = $this->getSeconds($delay); + + $payload = $this->createPayload($job, $data, $queue); + + return $this->pushRaw($payload, $queue, compact('delay')); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Contracts\Queue\Job|null + */ + public function pop($queue = null) + { + $queue = $this->getQueue($queue); + + $job = $this->iron->getMessage($queue); + + // If we were able to pop a message off of the queue, we will need to decrypt + // the message body, as all Iron.io messages are encrypted, since the push + // queues will be a security hazard to unsuspecting developers using it. + if (! is_null($job)) { + $job->body = $this->parseJobBody($job->body); + + return new IronJob($this->container, $this, $job); + } + } + + /** + * Delete a message from the Iron queue. + * + * @param string $queue + * @param string $id + * @return void + */ + public function deleteMessage($queue, $id) + { + $this->iron->deleteMessage($queue, $id); + } + + /** + * Marshal a push queue request and fire the job. + * + * @return \Illuminate\Http\Response + * + * @deprecated since version 5.1. + */ + public function marshal() + { + $this->createPushedIronJob($this->marshalPushedJob())->fire(); + + return new Response('OK'); + } + + /** + * Marshal out the pushed job and payload. + * + * @return object + */ + protected function marshalPushedJob() + { + $r = $this->request; + + $body = $this->parseJobBody($r->getContent()); + + return (object) [ + 'id' => $r->header('iron-message-id'), 'body' => $body, 'pushed' => true, + ]; + } + + /** + * Create a new IronJob for a pushed job. + * + * @param object $job + * @return \Illuminate\Queue\Jobs\IronJob + */ + protected function createPushedIronJob($job) + { + return new IronJob($this->container, $this, $job, true); + } + + /** + * Create a payload string from the given job and data. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return string + */ + protected function createPayload($job, $data = '', $queue = null) + { + $payload = $this->setMeta(parent::createPayload($job, $data), 'attempts', 1); + + return $this->setMeta($payload, 'queue', $this->getQueue($queue)); + } + + /** + * Parse the job body for firing. + * + * @param string $body + * @return string + */ + protected function parseJobBody($body) + { + return $this->shouldEncrypt ? $this->crypt->decrypt($body) : $body; + } + + /** + * Get the queue or return the default. + * + * @param string|null $queue + * @return string + */ + public function getQueue($queue) + { + return $queue ?: $this->default; + } + + /** + * Get the underlying IronMQ instance. + * + * @return \IronMQ\IronMQ + */ + public function getIron() + { + return $this->iron; + } + + /** + * Get the request instance. + * + * @return \Illuminate\Http\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Set the request instance. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php new file mode 100644 index 0000000..be245b7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php @@ -0,0 +1,156 @@ +job = $job; + $this->queue = $queue; + $this->container = $container; + $this->pheanstalk = $pheanstalk; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->getRawBody(), true)); + } + + /** + * Get the raw body string for the job. + * + * @return string + */ + public function getRawBody() + { + return $this->job->getData(); + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + parent::delete(); + + $this->pheanstalk->delete($this->job); + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + parent::release($delay); + + $priority = Pheanstalk::DEFAULT_PRIORITY; + + $this->pheanstalk->release($this->job, $priority, $delay); + } + + /** + * Bury the job in the queue. + * + * @return void + */ + public function bury() + { + parent::release(); + + $this->pheanstalk->bury($this->job); + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + $stats = $this->pheanstalk->statsJob($this->job); + + return (int) $stats->reserves; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->job->getId(); + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Get the underlying Pheanstalk instance. + * + * @return \Pheanstalk\Pheanstalk + */ + public function getPheanstalk() + { + return $this->pheanstalk; + } + + /** + * Get the underlying Pheanstalk job. + * + * @return \Pheanstalk\Job + */ + public function getPheanstalkJob() + { + return $this->job; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/DatabaseJob.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/DatabaseJob.php new file mode 100644 index 0000000..4bcc8eb --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/DatabaseJob.php @@ -0,0 +1,139 @@ +job = $job; + $this->queue = $queue; + $this->database = $database; + $this->container = $container; + $this->job->attempts = $this->job->attempts + 1; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->job->payload, true)); + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + parent::delete(); + + $this->database->deleteReserved($this->queue, $this->job->id); + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + parent::release($delay); + + $this->delete(); + + $this->database->release($this->queue, $this->job, $delay); + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + return (int) $this->job->attempts; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->job->id; + } + + /** + * Get the raw body string for the job. + * + * @return string + */ + public function getRawBody() + { + return $this->job->payload; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Get the underlying queue driver instance. + * + * @return \Illuminate\Queue\DatabaseQueue + */ + public function getDatabaseQueue() + { + return $this->database; + } + + /** + * Get the underlying database job. + * + * @return \StdClass + */ + public function getDatabaseJob() + { + return $this->job; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/IronJob.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/IronJob.php new file mode 100644 index 0000000..6793bcd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/IronJob.php @@ -0,0 +1,180 @@ +job = $job; + $this->iron = $iron; + $this->pushed = $pushed; + $this->container = $container; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->getRawBody(), true)); + } + + /** + * Get the raw body string for the job. + * + * @return string + */ + public function getRawBody() + { + return $this->job->body; + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + parent::delete(); + + if (isset($this->job->pushed)) { + return; + } + + $this->iron->deleteMessage($this->getQueue(), $this->job->id); + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + parent::release($delay); + + if (! $this->pushed) { + $this->delete(); + } + + $this->recreateJob($delay); + } + + /** + * Release a pushed job back onto the queue. + * + * @param int $delay + * @return void + */ + protected function recreateJob($delay) + { + $payload = json_decode($this->job->body, true); + + Arr::set($payload, 'attempts', Arr::get($payload, 'attempts', 1) + 1); + + $this->iron->recreate(json_encode($payload), $this->getQueue(), $delay); + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + return Arr::get(json_decode($this->job->body, true), 'attempts', 1); + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->job->id; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Get the underlying Iron queue instance. + * + * @return \Illuminate\Queue\IronQueue + */ + public function getIron() + { + return $this->iron; + } + + /** + * Get the underlying IronMQ job. + * + * @return array + */ + public function getIronJob() + { + return $this->job; + } + + /** + * Get the name of the queue the job belongs to. + * + * @return string + */ + public function getQueue() + { + return Arr::get(json_decode($this->job->body, true), 'queue'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php new file mode 100644 index 0000000..304bc9d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php @@ -0,0 +1,270 @@ +deleted = true; + } + + /** + * Determine if the job has been deleted. + * + * @return bool + */ + public function isDeleted() + { + return $this->deleted; + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + $this->released = true; + } + + /** + * Determine if the job was released back into the queue. + * + * @return bool + */ + public function isReleased() + { + return $this->released; + } + + /** + * Determine if the job has been deleted or released. + * + * @return bool + */ + public function isDeletedOrReleased() + { + return $this->isDeleted() || $this->isReleased(); + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + abstract public function attempts(); + + /** + * Get the raw body string for the job. + * + * @return string + */ + abstract public function getRawBody(); + + /** + * Resolve and fire the job handler method. + * + * @param array $payload + * @return void + */ + protected function resolveAndFire(array $payload) + { + list($class, $method) = $this->parseJob($payload['job']); + + $this->instance = $this->resolve($class); + + $this->instance->{$method}($this, $this->resolveQueueableEntities($payload['data'])); + } + + /** + * Parse the job declaration into class and method. + * + * @param string $job + * @return array + */ + protected function parseJob($job) + { + $segments = explode('@', $job); + + return count($segments) > 1 ? $segments : [$segments[0], 'fire']; + } + + /** + * Resolve the given job handler. + * + * @param string $class + * @return mixed + */ + protected function resolve($class) + { + return $this->container->make($class); + } + + /** + * Resolve all of the queueable entities in the given payload. + * + * @param mixed $data + * @return mixed + */ + protected function resolveQueueableEntities($data) + { + if (is_string($data)) { + return $this->resolveQueueableEntity($data); + } + + if (is_array($data)) { + $data = array_map(function ($d) { + if (is_array($d)) { + return $this->resolveQueueableEntities($d); + } + + return $this->resolveQueueableEntity($d); + }, $data); + } + + return $data; + } + + /** + * Resolve a single queueable entity from the resolver. + * + * @param mixed $value + * @return \Illuminate\Contracts\Queue\QueueableEntity + */ + protected function resolveQueueableEntity($value) + { + if (is_string($value) && Str::startsWith($value, '::entity::')) { + list($marker, $type, $id) = explode('|', $value, 3); + + return $this->getEntityResolver()->resolve($type, $id); + } + + return $value; + } + + /** + * Call the failed method on the job instance. + * + * @return void + */ + public function failed() + { + $payload = json_decode($this->getRawBody(), true); + + list($class, $method) = $this->parseJob($payload['job']); + + $this->instance = $this->resolve($class); + + if (method_exists($this->instance, 'failed')) { + $this->instance->failed($this->resolveQueueableEntities($payload['data'])); + } + } + + /** + * Get an entity resolver instance. + * + * @return \Illuminate\Contracts\Queue\EntityResolver + */ + protected function getEntityResolver() + { + return $this->container->make('Illuminate\Contracts\Queue\EntityResolver'); + } + + /** + * Calculate the number of seconds with the given delay. + * + * @param \DateTime|int $delay + * @return int + */ + protected function getSeconds($delay) + { + if ($delay instanceof DateTime) { + return max(0, $delay->getTimestamp() - $this->getTime()); + } + + return (int) $delay; + } + + /** + * Get the current system time. + * + * @return int + */ + protected function getTime() + { + return time(); + } + + /** + * Get the name of the queued job class. + * + * @return string + */ + public function getName() + { + return json_decode($this->getRawBody(), true)['job']; + } + + /** + * Get the name of the queue the job belongs to. + * + * @return string + */ + public function getQueue() + { + return $this->queue; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/RedisJob.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/RedisJob.php new file mode 100644 index 0000000..af9ca97 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/RedisJob.php @@ -0,0 +1,139 @@ +job = $job; + $this->redis = $redis; + $this->queue = $queue; + $this->container = $container; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->getRawBody(), true)); + } + + /** + * Get the raw body string for the job. + * + * @return string + */ + public function getRawBody() + { + return $this->job; + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + parent::delete(); + + $this->redis->deleteReserved($this->queue, $this->job); + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + parent::release($delay); + + $this->delete(); + + $this->redis->release($this->queue, $this->job, $delay, $this->attempts() + 1); + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + return Arr::get(json_decode($this->job, true), 'attempts'); + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return Arr::get(json_decode($this->job, true), 'id'); + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Get the underlying queue driver instance. + * + * @return \Illuminate\Redis\Database + */ + public function getRedisQueue() + { + return $this->redis; + } + + /** + * Get the underlying Redis job. + * + * @return string + */ + public function getRedisJob() + { + return $this->job; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SqsJob.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SqsJob.php new file mode 100644 index 0000000..b676a52 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SqsJob.php @@ -0,0 +1,147 @@ +sqs = $sqs; + $this->job = $job; + $this->queue = $queue; + $this->container = $container; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->getRawBody(), true)); + } + + /** + * Get the raw body string for the job. + * + * @return string + */ + public function getRawBody() + { + return $this->job['Body']; + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + parent::delete(); + + $this->sqs->deleteMessage([ + + 'QueueUrl' => $this->queue, 'ReceiptHandle' => $this->job['ReceiptHandle'], + + ]); + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + parent::release($delay); + + $this->sqs->changeMessageVisibility([ + 'QueueUrl' => $this->queue, + 'ReceiptHandle' => $this->job['ReceiptHandle'], + 'VisibilityTimeout' => $delay, + ]); + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + return (int) $this->job['Attributes']['ApproximateReceiveCount']; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->job['MessageId']; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Get the underlying SQS client instance. + * + * @return \Aws\Sqs\SqsClient + */ + public function getSqs() + { + return $this->sqs; + } + + /** + * Get the underlying raw SQS job. + * + * @return array + */ + public function getSqsJob() + { + return $this->job; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php new file mode 100644 index 0000000..c1f2236 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php @@ -0,0 +1,89 @@ +queue = $queue; + $this->payload = $payload; + $this->container = $container; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->payload, true)); + } + + /** + * Get the raw body string for the job. + * + * @return string + */ + public function getRawBody() + { + return $this->payload; + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + parent::release($delay); + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + return 1; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return ''; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Listener.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Listener.php new file mode 100644 index 0000000..b19d46c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Listener.php @@ -0,0 +1,265 @@ +commandPath = $commandPath; + $this->workerCommand = $this->buildWorkerCommand(); + } + + /** + * Build the environment specific worker command. + * + * @return string + */ + protected function buildWorkerCommand() + { + $binary = ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)); + + if (defined('HHVM_VERSION')) { + $binary .= ' --php'; + } + + if (defined('ARTISAN_BINARY')) { + $artisan = ProcessUtils::escapeArgument(ARTISAN_BINARY); + } else { + $artisan = 'artisan'; + } + + $command = 'queue:work %s --queue=%s --delay=%s --memory=%s --sleep=%s --tries=%s'; + + return "{$binary} {$artisan} {$command}"; + } + + /** + * Listen to the given queue connection. + * + * @param string $connection + * @param string $queue + * @param string $delay + * @param string $memory + * @param int $timeout + * @return void + */ + public function listen($connection, $queue, $delay, $memory, $timeout = 60) + { + $process = $this->makeProcess($connection, $queue, $delay, $memory, $timeout); + + while (true) { + $this->runProcess($process, $memory); + } + } + + /** + * Run the given process. + * + * @param \Symfony\Component\Process\Process $process + * @param int $memory + * @return void + */ + public function runProcess(Process $process, $memory) + { + $process->run(function ($type, $line) { + $this->handleWorkerOutput($type, $line); + }); + + // Once we have run the job we'll go check if the memory limit has been + // exceeded for the script. If it has, we will kill this script so a + // process manager will restart this with a clean slate of memory. + if ($this->memoryExceeded($memory)) { + $this->stop(); + } + } + + /** + * Create a new Symfony process for the worker. + * + * @param string $connection + * @param string $queue + * @param int $delay + * @param int $memory + * @param int $timeout + * @return \Symfony\Component\Process\Process + */ + public function makeProcess($connection, $queue, $delay, $memory, $timeout) + { + $string = $this->workerCommand; + + // If the environment is set, we will append it to the command string so the + // workers will run under the specified environment. Otherwise, they will + // just run under the production environment which is not always right. + if (isset($this->environment)) { + $string .= ' --env='.ProcessUtils::escapeArgument($this->environment); + } + + // Next, we will just format out the worker commands with all of the various + // options available for the command. This will produce the final command + // line that we will pass into a Symfony process object for processing. + $command = sprintf( + $string, + ProcessUtils::escapeArgument($connection), + ProcessUtils::escapeArgument($queue), + $delay, + $memory, + $this->sleep, + $this->maxTries + ); + + return new Process($command, $this->commandPath, null, null, $timeout); + } + + /** + * Handle output from the worker process. + * + * @param int $type + * @param string $line + * @return void + */ + protected function handleWorkerOutput($type, $line) + { + if (isset($this->outputHandler)) { + call_user_func($this->outputHandler, $type, $line); + } + } + + /** + * Determine if the memory limit has been exceeded. + * + * @param int $memoryLimit + * @return bool + */ + public function memoryExceeded($memoryLimit) + { + return (memory_get_usage() / 1024 / 1024) >= $memoryLimit; + } + + /** + * Stop listening and bail out of the script. + * + * @return void + */ + public function stop() + { + die; + } + + /** + * Set the output handler callback. + * + * @param \Closure $outputHandler + * @return void + */ + public function setOutputHandler(Closure $outputHandler) + { + $this->outputHandler = $outputHandler; + } + + /** + * Get the current listener environment. + * + * @return string + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * Set the current environment. + * + * @param string $environment + * @return void + */ + public function setEnvironment($environment) + { + $this->environment = $environment; + } + + /** + * Get the amount of seconds to wait before polling the queue. + * + * @return int + */ + public function getSleep() + { + return $this->sleep; + } + + /** + * Set the amount of seconds to wait before polling the queue. + * + * @param int $sleep + * @return void + */ + public function setSleep($sleep) + { + $this->sleep = $sleep; + } + + /** + * Set the amount of times to try a job before logging it failed. + * + * @param int $tries + * @return void + */ + public function setMaxTries($tries) + { + $this->maxTries = $tries; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/NullQueue.php b/core/vendor/laravel/framework/src/Illuminate/Queue/NullQueue.php new file mode 100644 index 0000000..bdef6cc --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/NullQueue.php @@ -0,0 +1,59 @@ +push($job, $data, $queue); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param string $queue + * @param \DateTime|int $delay + * @param string $job + * @param mixed $data + * @return mixed + */ + public function laterOn($queue, $delay, $job, $data = '') + { + return $this->later($delay, $job, $data, $queue); + } + + /** + * Marshal a push queue request and fire the job. + * + * @throws \RuntimeException + * + * @deprecated since version 5.1. + */ + public function marshal() + { + throw new RuntimeException('Push queues only supported by Iron.'); + } + + /** + * Push an array of jobs onto the queue. + * + * @param array $jobs + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function bulk($jobs, $data = '', $queue = null) + { + foreach ((array) $jobs as $job) { + $this->push($job, $data, $queue); + } + } + + /** + * Create a payload string from the given job and data. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return string + */ + protected function createPayload($job, $data = '', $queue = null) + { + if ($job instanceof Closure) { + return json_encode($this->createClosurePayload($job, $data)); + } elseif (is_object($job)) { + return json_encode([ + 'job' => 'Illuminate\Queue\CallQueuedHandler@call', + 'data' => ['command' => serialize(clone $job)], + ]); + } + + return json_encode($this->createPlainPayload($job, $data)); + } + + /** + * Create a typical, "plain" queue payload array. + * + * @param string $job + * @param mixed $data + * @return array + */ + protected function createPlainPayload($job, $data) + { + return ['job' => $job, 'data' => $this->prepareQueueableEntities($data)]; + } + + /** + * Prepare any queueable entities for storage in the queue. + * + * @param mixed $data + * @return mixed + */ + protected function prepareQueueableEntities($data) + { + if ($data instanceof QueueableEntity) { + return $this->prepareQueueableEntity($data); + } + + if (is_array($data)) { + $data = array_map(function ($d) { + if (is_array($d)) { + return $this->prepareQueueableEntities($d); + } + + return $this->prepareQueueableEntity($d); + }, $data); + } + + return $data; + } + + /** + * Prepare a single queueable entity for storage on the queue. + * + * @param mixed $value + * @return mixed + */ + protected function prepareQueueableEntity($value) + { + if ($value instanceof QueueableEntity) { + return '::entity::|'.get_class($value).'|'.$value->getQueueableId(); + } + + return $value; + } + + /** + * Create a payload string for the given Closure job. + * + * @param \Closure $job + * @param mixed $data + * @return string + */ + protected function createClosurePayload($job, $data) + { + $closure = $this->crypt->encrypt((new Serializer)->serialize($job)); + + return ['job' => 'IlluminateQueueClosure', 'data' => compact('closure')]; + } + + /** + * Set additional meta on a payload string. + * + * @param string $payload + * @param string $key + * @param string $value + * @return string + */ + protected function setMeta($payload, $key, $value) + { + $payload = json_decode($payload, true); + + return json_encode(Arr::set($payload, $key, $value)); + } + + /** + * Calculate the number of seconds with the given delay. + * + * @param \DateTime|int $delay + * @return int + */ + protected function getSeconds($delay) + { + if ($delay instanceof DateTime) { + return max(0, $delay->getTimestamp() - $this->getTime()); + } + + return (int) $delay; + } + + /** + * Get the current UNIX timestamp. + * + * @return int + */ + protected function getTime() + { + return time(); + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + + /** + * Set the encrypter instance. + * + * @param \Illuminate\Contracts\Encryption\Encrypter $crypt + * @return void + */ + public function setEncrypter(EncrypterContract $crypt) + { + $this->crypt = $crypt; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/QueueManager.php b/core/vendor/laravel/framework/src/Illuminate/Queue/QueueManager.php new file mode 100644 index 0000000..cf91bbd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/QueueManager.php @@ -0,0 +1,243 @@ +app = $app; + } + + /** + * Register an event listener for the after job event. + * + * @param mixed $callback + * @return void + */ + public function after($callback) + { + $this->app['events']->listen('illuminate.queue.after', $callback); + } + + /** + * Register an event listener for the daemon queue loop. + * + * @param mixed $callback + * @return void + */ + public function looping($callback) + { + $this->app['events']->listen('illuminate.queue.looping', $callback); + } + + /** + * Register an event listener for the failed job event. + * + * @param mixed $callback + * @return void + */ + public function failing($callback) + { + $this->app['events']->listen('illuminate.queue.failed', $callback); + } + + /** + * Register an event listener for the daemon queue stopping. + * + * @param mixed $callback + * @return void + */ + public function stopping($callback) + { + $this->app['events']->listen('illuminate.queue.stopping', $callback); + } + + /** + * Determine if the driver is connected. + * + * @param string $name + * @return bool + */ + public function connected($name = null) + { + return isset($this->connections[$name ?: $this->getDefaultDriver()]); + } + + /** + * Resolve a queue connection instance. + * + * @param string $name + * @return \Illuminate\Contracts\Queue\Queue + */ + public function connection($name = null) + { + $name = $name ?: $this->getDefaultDriver(); + + // If the connection has not been resolved yet we will resolve it now as all + // of the connections are resolved when they are actually needed so we do + // not make any unnecessary connection to the various queue end-points. + if (! isset($this->connections[$name])) { + $this->connections[$name] = $this->resolve($name); + + $this->connections[$name]->setContainer($this->app); + + $this->connections[$name]->setEncrypter($this->app['encrypter']); + } + + return $this->connections[$name]; + } + + /** + * Resolve a queue connection. + * + * @param string $name + * @return \Illuminate\Contracts\Queue\Queue + */ + protected function resolve($name) + { + $config = $this->getConfig($name); + + return $this->getConnector($config['driver'])->connect($config); + } + + /** + * Get the connector for a given driver. + * + * @param string $driver + * @return \Illuminate\Queue\Connectors\ConnectorInterface + * + * @throws \InvalidArgumentException + */ + protected function getConnector($driver) + { + if (isset($this->connectors[$driver])) { + return call_user_func($this->connectors[$driver]); + } + + throw new InvalidArgumentException("No connector for [$driver]"); + } + + /** + * Add a queue connection resolver. + * + * @param string $driver + * @param \Closure $resolver + * @return void + */ + public function extend($driver, Closure $resolver) + { + return $this->addConnector($driver, $resolver); + } + + /** + * Add a queue connection resolver. + * + * @param string $driver + * @param \Closure $resolver + * @return void + */ + public function addConnector($driver, Closure $resolver) + { + $this->connectors[$driver] = $resolver; + } + + /** + * Get the queue connection configuration. + * + * @param string $name + * @return array + */ + protected function getConfig($name) + { + return $this->app['config']["queue.connections.{$name}"]; + } + + /** + * Get the name of the default queue connection. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['queue.default']; + } + + /** + * Set the name of the default queue connection. + * + * @param string $name + * @return void + */ + public function setDefaultDriver($name) + { + $this->app['config']['queue.default'] = $name; + } + + /** + * Get the full name for the given connection. + * + * @param string $connection + * @return string + */ + public function getName($connection = null) + { + return $connection ?: $this->getDefaultDriver(); + } + + /** + * Determine if the application is in maintenance mode. + * + * @return bool + */ + public function isDownForMaintenance() + { + return $this->app->isDownForMaintenance(); + } + + /** + * Dynamically pass calls to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $callable = [$this->connection(), $method]; + + return call_user_func_array($callable, $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php new file mode 100644 index 0000000..5957a89 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php @@ -0,0 +1,326 @@ +registerManager(); + + $this->registerWorker(); + + $this->registerListener(); + + $this->registerSubscriber(); + + $this->registerFailedJobServices(); + + $this->registerQueueClosure(); + } + + /** + * Register the queue manager. + * + * @return void + */ + protected function registerManager() + { + $this->app->singleton('queue', function ($app) { + // Once we have an instance of the queue manager, we will register the various + // resolvers for the queue connectors. These connectors are responsible for + // creating the classes that accept queue configs and instantiate queues. + $manager = new QueueManager($app); + + $this->registerConnectors($manager); + + return $manager; + }); + + $this->app->singleton('queue.connection', function ($app) { + return $app['queue']->connection(); + }); + } + + /** + * Register the queue worker. + * + * @return void + */ + protected function registerWorker() + { + $this->registerWorkCommand(); + + $this->registerRestartCommand(); + + $this->app->singleton('queue.worker', function ($app) { + return new Worker($app['queue'], $app['queue.failer'], $app['events']); + }); + } + + /** + * Register the queue worker console command. + * + * @return void + */ + protected function registerWorkCommand() + { + $this->app->singleton('command.queue.work', function ($app) { + return new WorkCommand($app['queue.worker']); + }); + + $this->commands('command.queue.work'); + } + + /** + * Register the queue listener. + * + * @return void + */ + protected function registerListener() + { + $this->registerListenCommand(); + + $this->app->singleton('queue.listener', function ($app) { + return new Listener($app->basePath()); + }); + } + + /** + * Register the queue listener console command. + * + * @return void + */ + protected function registerListenCommand() + { + $this->app->singleton('command.queue.listen', function ($app) { + return new ListenCommand($app['queue.listener']); + }); + + $this->commands('command.queue.listen'); + } + + /** + * Register the queue restart console command. + * + * @return void + */ + public function registerRestartCommand() + { + $this->app->singleton('command.queue.restart', function () { + return new RestartCommand; + }); + + $this->commands('command.queue.restart'); + } + + /** + * Register the push queue subscribe command. + * + * @return void + */ + protected function registerSubscriber() + { + $this->app->singleton('command.queue.subscribe', function () { + return new SubscribeCommand; + }); + + $this->commands('command.queue.subscribe'); + } + + /** + * Register the connectors on the queue manager. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + public function registerConnectors($manager) + { + foreach (['Null', 'Sync', 'Database', 'Beanstalkd', 'Redis', 'Sqs', 'Iron'] as $connector) { + $this->{"register{$connector}Connector"}($manager); + } + } + + /** + * Register the Null queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerNullConnector($manager) + { + $manager->addConnector('null', function () { + return new NullConnector; + }); + } + + /** + * Register the Sync queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerSyncConnector($manager) + { + $manager->addConnector('sync', function () { + return new SyncConnector; + }); + } + + /** + * Register the Beanstalkd queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerBeanstalkdConnector($manager) + { + $manager->addConnector('beanstalkd', function () { + return new BeanstalkdConnector; + }); + } + + /** + * Register the database queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerDatabaseConnector($manager) + { + $manager->addConnector('database', function () { + return new DatabaseConnector($this->app['db']); + }); + } + + /** + * Register the Redis queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerRedisConnector($manager) + { + $app = $this->app; + + $manager->addConnector('redis', function () use ($app) { + return new RedisConnector($app['redis']); + }); + } + + /** + * Register the Amazon SQS queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerSqsConnector($manager) + { + $manager->addConnector('sqs', function () { + return new SqsConnector; + }); + } + + /** + * Register the IronMQ queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerIronConnector($manager) + { + $app = $this->app; + + $manager->addConnector('iron', function () use ($app) { + return new IronConnector($app['encrypter'], $app['request']); + }); + + $this->registerIronRequestBinder(); + } + + /** + * Register the request rebinding event for the Iron queue. + * + * @return void + */ + protected function registerIronRequestBinder() + { + $this->app->rebinding('request', function ($app, $request) { + if ($app['queue']->connected('iron')) { + $app['queue']->connection('iron')->setRequest($request); + } + }); + } + + /** + * Register the failed job services. + * + * @return void + */ + protected function registerFailedJobServices() + { + $this->app->singleton('queue.failer', function ($app) { + $config = $app['config']['queue.failed']; + + if (isset($config['table'])) { + return new DatabaseFailedJobProvider($app['db'], $config['database'], $config['table']); + } else { + return new NullFailedJobProvider; + } + }); + } + + /** + * Register the Illuminate queued closure job. + * + * @return void + */ + protected function registerQueueClosure() + { + $this->app->singleton('IlluminateQueueClosure', function ($app) { + return new IlluminateQueueClosure($app['encrypter']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'queue', 'queue.worker', 'queue.listener', 'queue.failer', + 'command.queue.work', 'command.queue.listen', 'command.queue.restart', + 'command.queue.subscribe', 'queue.connection', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/README.md b/core/vendor/laravel/framework/src/Illuminate/Queue/README.md new file mode 100644 index 0000000..e6a715b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/README.md @@ -0,0 +1,34 @@ +## Illuminate Queue + +The Laravel Queue component provides a unified API across a variety of different queue services. Queues allow you to defer the processing of a time consuming task, such as sending an e-mail, until a later time, thus drastically speeding up the web requests to your application. + +### Usage Instructions + +First, create a new Queue `Capsule` manager instance. Similar to the "Capsule" provided for the Eloquent ORM, the queue Capsule aims to make configuring the library for usage outside of the Laravel framework as easy as possible. + +```PHP +use Illuminate\Queue\Capsule\Manager as Queue; + +$queue = new Queue; + +$queue->addConnection([ + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', +]); + +// Make this Capsule instance available globally via static methods... (optional) +$queue->setAsGlobal(); +``` + +Once the Capsule instance has been registered. You may use it like so: + +```PHP +// As an instance... +$queue->push('SendEmail', array('message' => $message)); + +// If setAsGlobal has been called... +Queue::push('SendEmail', array('message' => $message)); +``` + +For further documentation on using the queue, consult the [Laravel framework documentation](http://laravel.com/docs). diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php b/core/vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php new file mode 100644 index 0000000..acd36ab --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php @@ -0,0 +1,319 @@ +redis = $redis; + $this->default = $default; + $this->connection = $connection; + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + return $this->pushRaw($this->createPayload($job, $data), $queue); + } + + /** + * Push a raw payload onto the queue. + * + * @param string $payload + * @param string $queue + * @param array $options + * @return mixed + */ + public function pushRaw($payload, $queue = null, array $options = []) + { + $this->getConnection()->rpush($this->getQueue($queue), $payload); + + return Arr::get(json_decode($payload, true), 'id'); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param \DateTime|int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + $delay = $this->getSeconds($delay); + + $this->getConnection()->zadd($this->getQueue($queue).':delayed', $this->getTime() + $delay, $payload); + + return Arr::get(json_decode($payload, true), 'id'); + } + + /** + * Release a reserved job back onto the queue. + * + * @param string $queue + * @param string $payload + * @param int $delay + * @param int $attempts + * @return void + */ + public function release($queue, $payload, $delay, $attempts) + { + $payload = $this->setMeta($payload, 'attempts', $attempts); + + $this->getConnection()->zadd($this->getQueue($queue).':delayed', $this->getTime() + $delay, $payload); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Contracts\Queue\Job|null + */ + public function pop($queue = null) + { + $original = $queue ?: $this->default; + + $queue = $this->getQueue($queue); + + if (! is_null($this->expire)) { + $this->migrateAllExpiredJobs($queue); + } + + $job = $this->getConnection()->lpop($queue); + + if (! is_null($job)) { + $this->getConnection()->zadd($queue.':reserved', $this->getTime() + $this->expire, $job); + + return new RedisJob($this->container, $this, $job, $original); + } + } + + /** + * Delete a reserved job from the queue. + * + * @param string $queue + * @param string $job + * @return void + */ + public function deleteReserved($queue, $job) + { + $this->getConnection()->zrem($this->getQueue($queue).':reserved', $job); + } + + /** + * Migrate all of the waiting jobs in the queue. + * + * @param string $queue + * @return void + */ + protected function migrateAllExpiredJobs($queue) + { + $this->migrateExpiredJobs($queue.':delayed', $queue); + + $this->migrateExpiredJobs($queue.':reserved', $queue); + } + + /** + * Migrate the delayed jobs that are ready to the regular queue. + * + * @param string $from + * @param string $to + * @return void + */ + public function migrateExpiredJobs($from, $to) + { + $options = ['cas' => true, 'watch' => $from, 'retry' => 10]; + + $this->getConnection()->transaction($options, function ($transaction) use ($from, $to) { + // First we need to get all of jobs that have expired based on the current time + // so that we can push them onto the main queue. After we get them we simply + // remove them from this "delay" queues. All of this within a transaction. + $jobs = $this->getExpiredJobs( + $transaction, $from, $time = $this->getTime() + ); + + // If we actually found any jobs, we will remove them from the old queue and we + // will insert them onto the new (ready) "queue". This means they will stand + // ready to be processed by the queue worker whenever their turn comes up. + if (count($jobs) > 0) { + $this->removeExpiredJobs($transaction, $from, $time); + + $this->pushExpiredJobsOntoNewQueue($transaction, $to, $jobs); + } + }); + } + + /** + * Get the expired jobs from a given queue. + * + * @param \Predis\Transaction\MultiExec $transaction + * @param string $from + * @param int $time + * @return array + */ + protected function getExpiredJobs($transaction, $from, $time) + { + return $transaction->zrangebyscore($from, '-inf', $time); + } + + /** + * Remove the expired jobs from a given queue. + * + * @param \Predis\Transaction\MultiExec $transaction + * @param string $from + * @param int $time + * @return void + */ + protected function removeExpiredJobs($transaction, $from, $time) + { + $transaction->multi(); + + $transaction->zremrangebyscore($from, '-inf', $time); + } + + /** + * Push all of the given jobs onto another queue. + * + * @param \Predis\Transaction\MultiExec $transaction + * @param string $to + * @param array $jobs + * @return void + */ + protected function pushExpiredJobsOntoNewQueue($transaction, $to, $jobs) + { + call_user_func_array([$transaction, 'rpush'], array_merge([$to], $jobs)); + } + + /** + * Create a payload string from the given job and data. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return string + */ + protected function createPayload($job, $data = '', $queue = null) + { + $payload = parent::createPayload($job, $data); + + $payload = $this->setMeta($payload, 'id', $this->getRandomId()); + + return $this->setMeta($payload, 'attempts', 1); + } + + /** + * Get a random ID string. + * + * @return string + */ + protected function getRandomId() + { + return Str::random(32); + } + + /** + * Get the queue or return the default. + * + * @param string|null $queue + * @return string + */ + protected function getQueue($queue) + { + return 'queues:'.($queue ?: $this->default); + } + + /** + * Get the connection for the queue. + * + * @return \Predis\ClientInterface + */ + protected function getConnection() + { + return $this->redis->connection($this->connection); + } + + /** + * Get the underlying Redis instance. + * + * @return \Illuminate\Redis\Database + */ + public function getRedis() + { + return $this->redis; + } + + /** + * Get the expiration time in seconds. + * + * @return int|null + */ + public function getExpire() + { + return $this->expire; + } + + /** + * Set the expiration time in seconds. + * + * @param int|null $seconds + * @return void + */ + public function setExpire($seconds) + { + $this->expire = $seconds; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/SerializesModels.php b/core/vendor/laravel/framework/src/Illuminate/Queue/SerializesModels.php new file mode 100644 index 0000000..9ad9cf7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/SerializesModels.php @@ -0,0 +1,83 @@ +getProperties(); + + foreach ($properties as $property) { + $property->setValue($this, $this->getSerializedPropertyValue( + $this->getPropertyValue($property) + )); + } + + return array_map(function ($p) { + return $p->getName(); + }, $properties); + } + + /** + * Restore the model after serialization. + * + * @return void + */ + public function __wakeup() + { + foreach ((new ReflectionClass($this))->getProperties() as $property) { + $property->setValue($this, $this->getRestoredPropertyValue( + $this->getPropertyValue($property) + )); + } + } + + /** + * Get the property value prepared for serialization. + * + * @param mixed $value + * @return mixed + */ + protected function getSerializedPropertyValue($value) + { + return $value instanceof QueueableEntity + ? new ModelIdentifier(get_class($value), $value->getQueueableId()) : $value; + } + + /** + * Get the restored property value after deserialization. + * + * @param mixed $value + * @return mixed + */ + protected function getRestoredPropertyValue($value) + { + return $value instanceof ModelIdentifier + ? (new $value->class)->newQuery()->useWritePdo()->findOrFail($value->id) + : $value; + } + + /** + * Get the property value for the given property. + * + * @param \ReflectionProperty $property + * @return mixed + */ + protected function getPropertyValue(ReflectionProperty $property) + { + $property->setAccessible(true); + + return $property->getValue($this); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/SqsQueue.php b/core/vendor/laravel/framework/src/Illuminate/Queue/SqsQueue.php new file mode 100644 index 0000000..77609d1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/SqsQueue.php @@ -0,0 +1,165 @@ +sqs = $sqs; + $this->prefix = $prefix; + $this->default = $default; + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + return $this->pushRaw($this->createPayload($job, $data), $queue); + } + + /** + * Push a raw payload onto the queue. + * + * @param string $payload + * @param string $queue + * @param array $options + * @return mixed + */ + public function pushRaw($payload, $queue = null, array $options = []) + { + $response = $this->sqs->sendMessage(['QueueUrl' => $this->getQueue($queue), 'MessageBody' => $payload]); + + return $response->get('MessageId'); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param \DateTime|int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + $delay = $this->getSeconds($delay); + + return $this->sqs->sendMessage([ + 'QueueUrl' => $this->getQueue($queue), 'MessageBody' => $payload, 'DelaySeconds' => $delay, + + ])->get('MessageId'); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Contracts\Queue\Job|null + */ + public function pop($queue = null) + { + $queue = $this->getQueue($queue); + + $response = $this->sqs->receiveMessage( + ['QueueUrl' => $queue, 'AttributeNames' => ['ApproximateReceiveCount']] + ); + + if (count($response['Messages']) > 0) { + if ($this->jobCreator) { + return call_user_func($this->jobCreator, $this->container, $this->sqs, $queue, $response); + } else { + return new SqsJob($this->container, $this->sqs, $queue, $response['Messages'][0]); + } + } + } + + /** + * Define the job creator callback for the connection. + * + * @param callable $callback + * @return $this + */ + public function createJobsUsing(callable $callback) + { + $this->jobCreator = $callback; + + return $this; + } + + /** + * Get the queue or return the default. + * + * @param string|null $queue + * @return string + */ + public function getQueue($queue) + { + $queue = $queue ?: $this->default; + + if (filter_var($queue, FILTER_VALIDATE_URL) !== false) { + return $queue; + } + + return rtrim($this->prefix, '/').'/'.($queue); + } + + /** + * Get the underlying SQS instance. + * + * @return \Aws\Sqs\SqsClient + */ + public function getSqs() + { + return $this->sqs; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php b/core/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php new file mode 100644 index 0000000..36516db --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php @@ -0,0 +1,135 @@ +resolveJob($this->createPayload($job, $data, $queue), $queue); + + try { + $queueJob->fire(); + + $this->raiseAfterJobEvent($queueJob); + } catch (Exception $e) { + $this->handleFailedJob($queueJob); + + throw $e; + } catch (Throwable $e) { + $this->handleFailedJob($queueJob); + + throw $e; + } + + return 0; + } + + /** + * Push a raw payload onto the queue. + * + * @param string $payload + * @param string $queue + * @param array $options + * @return mixed + */ + public function pushRaw($payload, $queue = null, array $options = []) + { + // + } + + /** + * Push a new job onto the queue after a delay. + * + * @param \DateTime|int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + return $this->push($job, $data, $queue); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Contracts\Queue\Job|null + */ + public function pop($queue = null) + { + // + } + + /** + * Resolve a Sync job instance. + * + * @param string $payload + * @param string $queue + * @return \Illuminate\Queue\Jobs\SyncJob + */ + protected function resolveJob($payload, $queue) + { + return new SyncJob($this->container, $payload, $queue); + } + + /** + * Raise the after queue job event. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @return void + */ + protected function raiseAfterJobEvent(Job $job) + { + $data = json_decode($job->getRawBody(), true); + + if ($this->container->bound('events')) { + $this->container['events']->fire('illuminate.queue.after', ['sync', $job, $data]); + } + } + + /** + * Handle the failed job. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @return array + */ + protected function handleFailedJob(Job $job) + { + $job->failed(); + + $this->raiseFailedJobEvent($job); + } + + /** + * Raise the failed queue job event. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @return void + */ + protected function raiseFailedJobEvent(Job $job) + { + $data = json_decode($job->getRawBody(), true); + + if ($this->container->bound('events')) { + $this->container['events']->fire('illuminate.queue.failed', ['sync', $job, $data]); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/Worker.php b/core/vendor/laravel/framework/src/Illuminate/Queue/Worker.php new file mode 100644 index 0000000..47bfc09 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/Worker.php @@ -0,0 +1,394 @@ +failer = $failer; + $this->events = $events; + $this->manager = $manager; + } + + /** + * Listen to the given queue in a loop. + * + * @param string $connectionName + * @param string $queue + * @param int $delay + * @param int $memory + * @param int $sleep + * @param int $maxTries + * @return array + */ + public function daemon($connectionName, $queue = null, $delay = 0, $memory = 128, $sleep = 3, $maxTries = 0) + { + $lastRestart = $this->getTimestampOfLastQueueRestart(); + + while (true) { + if ($this->daemonShouldRun()) { + $this->runNextJobForDaemon( + $connectionName, $queue, $delay, $sleep, $maxTries + ); + } else { + $this->sleep($sleep); + } + + if ($this->memoryExceeded($memory) || $this->queueShouldRestart($lastRestart)) { + $this->stop(); + } + } + } + + /** + * Run the next job for the daemon worker. + * + * @param string $connectionName + * @param string $queue + * @param int $delay + * @param int $sleep + * @param int $maxTries + * @return void + */ + protected function runNextJobForDaemon($connectionName, $queue, $delay, $sleep, $maxTries) + { + try { + $this->pop($connectionName, $queue, $delay, $sleep, $maxTries); + } catch (Exception $e) { + if ($this->exceptions) { + $this->exceptions->report($e); + } + } catch (Throwable $e) { + if ($this->exceptions) { + $this->exceptions->report(new FatalThrowableError($e)); + } + } + } + + /** + * Determine if the daemon should process on this iteration. + * + * @return bool + */ + protected function daemonShouldRun() + { + if ($this->manager->isDownForMaintenance()) { + return false; + } + + return $this->events->until('illuminate.queue.looping') !== false; + } + + /** + * Listen to the given queue. + * + * @param string $connectionName + * @param string $queue + * @param int $delay + * @param int $sleep + * @param int $maxTries + * @return array + */ + public function pop($connectionName, $queue = null, $delay = 0, $sleep = 3, $maxTries = 0) + { + try { + $connection = $this->manager->connection($connectionName); + + $job = $this->getNextJob($connection, $queue); + + // If we're able to pull a job off of the stack, we will process it and + // then immediately return back out. If there is no job on the queue + // we will "sleep" the worker for the specified number of seconds. + if (! is_null($job)) { + return $this->process( + $this->manager->getName($connectionName), $job, $maxTries, $delay + ); + } + } catch (Exception $e) { + if ($this->exceptions) { + $this->exceptions->report($e); + } + } catch (Throwable $e) { + if ($this->exceptions) { + $this->exceptions->report(new FatalThrowableError($e)); + } + } + + $this->sleep($sleep); + + return ['job' => null, 'failed' => false]; + } + + /** + * Get the next job from the queue connection. + * + * @param \Illuminate\Contracts\Queue\Queue $connection + * @param string $queue + * @return \Illuminate\Contracts\Queue\Job|null + */ + protected function getNextJob($connection, $queue) + { + if (is_null($queue)) { + return $connection->pop(); + } + + foreach (explode(',', $queue) as $queue) { + if (! is_null($job = $connection->pop($queue))) { + return $job; + } + } + } + + /** + * Process a given job from the queue. + * + * @param string $connection + * @param \Illuminate\Contracts\Queue\Job $job + * @param int $maxTries + * @param int $delay + * @return array|null + * + * @throws \Throwable + */ + public function process($connection, Job $job, $maxTries = 0, $delay = 0) + { + if ($maxTries > 0 && $job->attempts() > $maxTries) { + return $this->logFailedJob($connection, $job); + } + + try { + // First we will fire off the job. Once it is done we will see if it will + // be auto-deleted after processing and if so we will go ahead and run + // the delete method on the job. Otherwise we will just keep moving. + $job->fire(); + + $this->raiseAfterJobEvent($connection, $job); + + return ['job' => $job, 'failed' => false]; + } catch (Exception $e) { + // If we catch an exception, we will attempt to release the job back onto + // the queue so it is not lost. This will let is be retried at a later + // time by another listener (or the same one). We will do that here. + if (! $job->isDeleted()) { + $job->release($delay); + } + + throw $e; + } catch (Throwable $e) { + if (! $job->isDeleted()) { + $job->release($delay); + } + + throw $e; + } + } + + /** + * Raise the after queue job event. + * + * @param string $connection + * @param \Illuminate\Contracts\Queue\Job $job + * @return void + */ + protected function raiseAfterJobEvent($connection, Job $job) + { + if ($this->events) { + $data = json_decode($job->getRawBody(), true); + + $this->events->fire('illuminate.queue.after', [$connection, $job, $data]); + } + } + + /** + * Log a failed job into storage. + * + * @param string $connection + * @param \Illuminate\Contracts\Queue\Job $job + * @return array + */ + protected function logFailedJob($connection, Job $job) + { + if ($this->failer) { + $this->failer->log($connection, $job->getQueue(), $job->getRawBody()); + + $job->delete(); + + $job->failed(); + + $this->raiseFailedJobEvent($connection, $job); + } + + return ['job' => $job, 'failed' => true]; + } + + /** + * Raise the failed queue job event. + * + * @param string $connection + * @param \Illuminate\Contracts\Queue\Job $job + * @return void + */ + protected function raiseFailedJobEvent($connection, Job $job) + { + if ($this->events) { + $data = json_decode($job->getRawBody(), true); + + $this->events->fire('illuminate.queue.failed', [$connection, $job, $data]); + } + } + + /** + * Determine if the memory limit has been exceeded. + * + * @param int $memoryLimit + * @return bool + */ + public function memoryExceeded($memoryLimit) + { + return (memory_get_usage() / 1024 / 1024) >= $memoryLimit; + } + + /** + * Stop listening and bail out of the script. + * + * @return void + */ + public function stop() + { + $this->events->fire('illuminate.queue.stopping'); + + die; + } + + /** + * Sleep the script for a given number of seconds. + * + * @param int $seconds + * @return void + */ + public function sleep($seconds) + { + sleep($seconds); + } + + /** + * Get the last queue restart timestamp, or null. + * + * @return int|null + */ + protected function getTimestampOfLastQueueRestart() + { + if ($this->cache) { + return $this->cache->get('illuminate:queue:restart'); + } + } + + /** + * Determine if the queue worker should restart. + * + * @param int|null $lastRestart + * @return bool + */ + protected function queueShouldRestart($lastRestart) + { + return $this->getTimestampOfLastQueueRestart() != $lastRestart; + } + + /** + * Set the exception handler to use in Daemon mode. + * + * @param \Illuminate\Contracts\Debug\ExceptionHandler $handler + * @return void + */ + public function setDaemonExceptionHandler(ExceptionHandler $handler) + { + $this->exceptions = $handler; + } + + /** + * Set the cache repository implementation. + * + * @param \Illuminate\Contracts\Cache\Repository $cache + * @return void + */ + public function setCache(CacheContract $cache) + { + $this->cache = $cache; + } + + /** + * Get the queue manager instance. + * + * @return \Illuminate\Queue\QueueManager + */ + public function getManager() + { + return $this->manager; + } + + /** + * Set the queue manager instance. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + public function setManager(QueueManager $manager) + { + $this->manager = $manager; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Queue/composer.json b/core/vendor/laravel/framework/src/Illuminate/Queue/composer.json new file mode 100644 index 0000000..84f2edd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Queue/composer.json @@ -0,0 +1,47 @@ +{ + "name": "illuminate/queue", + "description": "The Illuminate Queue package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/console": "5.1.*", + "illuminate/contracts": "5.1.*", + "illuminate/container": "5.1.*", + "illuminate/http": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/process": "2.7.*", + "symfony/debug": "2.7.*", + "nesbot/carbon": "~1.19" + }, + "autoload": { + "psr-4": { + "Illuminate\\Queue\\": "" + }, + "classmap": [ + "IlluminateQueueClosure.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver (~3.0).", + "illuminate/redis": "Required to use the redis queue driver (5.1.*).", + "iron-io/iron_mq": "Required to use the iron queue driver (~2.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Redis/Database.php b/core/vendor/laravel/framework/src/Illuminate/Redis/Database.php new file mode 100644 index 0000000..437427b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Redis/Database.php @@ -0,0 +1,139 @@ +clients = $this->createAggregateClient($servers, $options); + } else { + $this->clients = $this->createSingleClients($servers, $options); + } + } + + /** + * Create a new aggregate client supporting sharding. + * + * @param array $servers + * @param array $options + * @return array + */ + protected function createAggregateClient(array $servers, array $options = []) + { + return ['default' => new Client(array_values($servers), $options)]; + } + + /** + * Create an array of single connection clients. + * + * @param array $servers + * @param array $options + * @return array + */ + protected function createSingleClients(array $servers, array $options = []) + { + $clients = []; + + foreach ($servers as $key => $server) { + $clients[$key] = new Client($server, $options); + } + + return $clients; + } + + /** + * Get a specific Redis connection instance. + * + * @param string $name + * @return \Predis\ClientInterface|null + */ + public function connection($name = 'default') + { + return Arr::get($this->clients, $name ?: 'default'); + } + + /** + * Run a command against the Redis database. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function command($method, array $parameters = []) + { + return call_user_func_array([$this->clients['default'], $method], $parameters); + } + + /** + * Subscribe to a set of given channels for messages. + * + * @param array|string $channels + * @param \Closure $callback + * @param string $connection + * @param string $method + * @return void + */ + public function subscribe($channels, Closure $callback, $connection = null, $method = 'subscribe') + { + $loop = $this->connection($connection)->pubSubLoop(); + + call_user_func_array([$loop, $method], (array) $channels); + + foreach ($loop as $message) { + if ($message->kind === 'message' || $message->kind === 'pmessage') { + call_user_func($callback, $message->payload, $message->channel); + } + } + + unset($loop); + } + + /** + * Subscribe to a set of given channels with wildcards. + * + * @param array|string $channels + * @param \Closure $callback + * @param string $connection + * @return void + */ + public function psubscribe($channels, Closure $callback, $connection = null) + { + return $this->subscribe($channels, $callback, $connection, __FUNCTION__); + } + + /** + * Dynamically make a Redis command. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->command($method, $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php new file mode 100644 index 0000000..78e1cc1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php @@ -0,0 +1,37 @@ +app->singleton('redis', function ($app) { + return new Database($app['config']['database.redis']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['redis']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Redis/composer.json b/core/vendor/laravel/framework/src/Illuminate/Redis/composer.json new file mode 100644 index 0000000..b78240b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Redis/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/redis", + "description": "The Illuminate Redis package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "predis/predis": "~1.0" + }, + "autoload": { + "psr-4": { + "Illuminate\\Redis\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Console/ControllerMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Console/ControllerMakeCommand.php new file mode 100644 index 0000000..8ff71e8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Console/ControllerMakeCommand.php @@ -0,0 +1,67 @@ +option('plain')) { + return __DIR__.'/stubs/controller.plain.stub'; + } + + return __DIR__.'/stubs/controller.stub'; + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Http\Controllers'; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['plain', null, InputOption::VALUE_NONE, 'Generate an empty controller class.'], + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Console/MiddlewareMakeCommand.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Console/MiddlewareMakeCommand.php new file mode 100644 index 0000000..e41813d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Console/MiddlewareMakeCommand.php @@ -0,0 +1,50 @@ +middleware[$middleware] = $options; + } + + /** + * Register a "before" filter on the controller. + * + * @param \Closure|string $filter + * @param array $options + * @return void + * + * @deprecated since version 5.1. + */ + public function beforeFilter($filter, array $options = []) + { + $this->beforeFilters[] = $this->parseFilter($filter, $options); + } + + /** + * Register an "after" filter on the controller. + * + * @param \Closure|string $filter + * @param array $options + * @return void + * + * @deprecated since version 5.1. + */ + public function afterFilter($filter, array $options = []) + { + $this->afterFilters[] = $this->parseFilter($filter, $options); + } + + /** + * Parse the given filter and options. + * + * @param \Closure|string $filter + * @param array $options + * @return array + */ + protected function parseFilter($filter, array $options) + { + $parameters = []; + + $original = $filter; + + if ($filter instanceof Closure) { + $filter = $this->registerClosureFilter($filter); + } elseif ($this->isInstanceFilter($filter)) { + $filter = $this->registerInstanceFilter($filter); + } else { + list($filter, $parameters) = Route::parseFilter($filter); + } + + return compact('original', 'filter', 'parameters', 'options'); + } + + /** + * Register an anonymous controller filter Closure. + * + * @param \Closure $filter + * @return string + */ + protected function registerClosureFilter(Closure $filter) + { + $this->getRouter()->filter($name = spl_object_hash($filter), $filter); + + return $name; + } + + /** + * Register a controller instance method as a filter. + * + * @param string $filter + * @return string + */ + protected function registerInstanceFilter($filter) + { + $this->getRouter()->filter($filter, [$this, substr($filter, 1)]); + + return $filter; + } + + /** + * Determine if a filter is a local method on the controller. + * + * @param mixed $filter + * @return bool + * + * @throws \InvalidArgumentException + */ + protected function isInstanceFilter($filter) + { + if (is_string($filter) && Str::startsWith($filter, '@')) { + if (method_exists($this, substr($filter, 1))) { + return true; + } + + throw new InvalidArgumentException("Filter method [$filter] does not exist."); + } + + return false; + } + + /** + * Remove the given before filter. + * + * @param string $filter + * @return void + * + * @deprecated since version 5.1. + */ + public function forgetBeforeFilter($filter) + { + $this->beforeFilters = $this->removeFilter($filter, $this->getBeforeFilters()); + } + + /** + * Remove the given after filter. + * + * @param string $filter + * @return void + * + * @deprecated since version 5.1. + */ + public function forgetAfterFilter($filter) + { + $this->afterFilters = $this->removeFilter($filter, $this->getAfterFilters()); + } + + /** + * Remove the given controller filter from the provided filter array. + * + * @param string $removing + * @param array $current + * @return array + */ + protected function removeFilter($removing, $current) + { + return array_filter($current, function ($filter) use ($removing) { + return $filter['original'] != $removing; + }); + } + + /** + * Get the middleware assigned to the controller. + * + * @return array + */ + public function getMiddleware() + { + return $this->middleware; + } + + /** + * Get the registered "before" filters. + * + * @return array + * + * @deprecated since version 5.1. + */ + public function getBeforeFilters() + { + return $this->beforeFilters; + } + + /** + * Get the registered "after" filters. + * + * @return array + * + * @deprecated since version 5.1. + */ + public function getAfterFilters() + { + return $this->afterFilters; + } + + /** + * Get the router instance. + * + * @return \Illuminate\Routing\Router + */ + public static function getRouter() + { + return static::$router; + } + + /** + * Set the router instance. + * + * @param \Illuminate\Routing\Router $router + * @return void + */ + public static function setRouter(Router $router) + { + static::$router = $router; + } + + /** + * Execute an action on the controller. + * + * @param string $method + * @param array $parameters + * @return \Symfony\Component\HttpFoundation\Response + */ + public function callAction($method, $parameters) + { + return call_user_func_array([$this, $method], $parameters); + } + + /** + * Handle calls to missing methods on the controller. + * + * @param array $parameters + * @return mixed + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function missingMethod($parameters = []) + { + throw new NotFoundHttpException('Controller method not found.'); + } + + /** + * Handle calls to missing methods on the controller. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + throw new BadMethodCallException("Method [$method] does not exist."); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php b/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php new file mode 100644 index 0000000..6482868 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php @@ -0,0 +1,300 @@ +router = $router; + $this->container = $container; + } + + /** + * Dispatch a request to a given controller and method. + * + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @param string $controller + * @param string $method + * @return mixed + */ + public function dispatch(Route $route, Request $request, $controller, $method) + { + // First we will make an instance of this controller via the IoC container instance + // so that we can call the methods on it. We will also apply any "after" filters + // to the route so that they will be run by the routers after this processing. + $instance = $this->makeController($controller); + + $this->assignAfter($instance, $route, $request, $method); + + $response = $this->before($instance, $route, $request, $method); + + // If no before filters returned a response we'll call the method on the controller + // to get the response to be returned to the router. We will then return it back + // out for processing by this router and the after filters can be called then. + if (is_null($response)) { + $response = $this->callWithinStack( + $instance, $route, $request, $method + ); + } + + return $response; + } + + /** + * Make a controller instance via the IoC container. + * + * @param string $controller + * @return mixed + */ + protected function makeController($controller) + { + Controller::setRouter($this->router); + + return $this->container->make($controller); + } + + /** + * Call the given controller instance method. + * + * @param \Illuminate\Routing\Controller $instance + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @param string $method + * @return mixed + */ + protected function callWithinStack($instance, $route, $request, $method) + { + $shouldSkipMiddleware = $this->container->bound('middleware.disable') && + $this->container->make('middleware.disable') === true; + + $middleware = $shouldSkipMiddleware ? [] : $this->getMiddleware($instance, $method); + + // Here we will make a stack onion instance to execute this request in, which gives + // us the ability to define middlewares on controllers. We will return the given + // response back out so that "after" filters can be run after the middlewares. + return (new Pipeline($this->container)) + ->send($request) + ->through($middleware) + ->then(function ($request) use ($instance, $route, $method) { + return $this->router->prepareResponse( + $request, $this->call($instance, $route, $method) + ); + }); + } + + /** + * Get the middleware for the controller instance. + * + * @param \Illuminate\Routing\Controller $instance + * @param string $method + * @return array + */ + protected function getMiddleware($instance, $method) + { + $results = []; + + foreach ($instance->getMiddleware() as $name => $options) { + if (! $this->methodExcludedByOptions($method, $options)) { + $results[] = $this->router->resolveMiddlewareClassName($name); + } + } + + return $results; + } + + /** + * Determine if the given options exclude a particular method. + * + * @param string $method + * @param array $options + * @return bool + */ + public function methodExcludedByOptions($method, array $options) + { + return (isset($options['only']) && ! in_array($method, (array) $options['only'])) || + (! empty($options['except']) && in_array($method, (array) $options['except'])); + } + + /** + * Call the given controller instance method. + * + * @param \Illuminate\Routing\Controller $instance + * @param \Illuminate\Routing\Route $route + * @param string $method + * @return mixed + */ + protected function call($instance, $route, $method) + { + $parameters = $this->resolveClassMethodDependencies( + $route->parametersWithoutNulls(), $instance, $method + ); + + return $instance->callAction($method, $parameters); + } + + /** + * Call the "before" filters for the controller. + * + * @param \Illuminate\Routing\Controller $instance + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @param string $method + * @return mixed + */ + protected function before($instance, $route, $request, $method) + { + foreach ($instance->getBeforeFilters() as $filter) { + if ($this->filterApplies($filter, $request, $method)) { + // Here we will just check if the filter applies. If it does we will call the filter + // and return the responses if it isn't null. If it is null, we will keep hitting + // them until we get a response or are finished iterating through this filters. + $response = $this->callFilter($filter, $route, $request); + + if (! is_null($response)) { + return $response; + } + } + } + } + + /** + * Apply the applicable after filters to the route. + * + * @param \Illuminate\Routing\Controller $instance + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @param string $method + * @return mixed + */ + protected function assignAfter($instance, $route, $request, $method) + { + foreach ($instance->getAfterFilters() as $filter) { + // If the filter applies, we will add it to the route, since it has already been + // registered with the router by the controller, and will just let the normal + // router take care of calling these filters so we do not duplicate logics. + if ($this->filterApplies($filter, $request, $method)) { + $route->after($this->getAssignableAfter($filter)); + } + } + } + + /** + * Get the assignable after filter for the route. + * + * @param \Closure|string $filter + * @return string + */ + protected function getAssignableAfter($filter) + { + if ($filter['original'] instanceof Closure) { + return $filter['filter']; + } + + return $filter['original']; + } + + /** + * Determine if the given filter applies to the request. + * + * @param array $filter + * @param \Illuminate\Http\Request $request + * @param string $method + * @return bool + */ + protected function filterApplies($filter, $request, $method) + { + foreach (['Method', 'On'] as $type) { + if ($this->{"filterFails{$type}"}($filter, $request, $method)) { + return false; + } + } + + return true; + } + + /** + * Determine if the filter fails the method constraints. + * + * @param array $filter + * @param \Illuminate\Http\Request $request + * @param string $method + * @return bool + */ + protected function filterFailsMethod($filter, $request, $method) + { + return $this->methodExcludedByOptions($method, $filter['options']); + } + + /** + * Determine if the filter fails the "on" constraint. + * + * @param array $filter + * @param \Illuminate\Http\Request $request + * @param string $method + * @return bool + */ + protected function filterFailsOn($filter, $request, $method) + { + $on = Arr::get($filter, 'options.on'); + + if (is_null($on)) { + return false; + } + + // If the "on" is a string, we will explode it on the pipe so you can set any + // amount of methods on the filter constraints and it will still work like + // you specified an array. Then we will check if the method is in array. + if (is_string($on)) { + $on = explode('|', $on); + } + + return ! in_array(strtolower($request->getMethod()), $on); + } + + /** + * Call the given controller filter method. + * + * @param array $filter + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @return mixed + */ + protected function callFilter($filter, $route, $request) + { + return $this->router->callRouteFilter( + $filter['filter'], $filter['parameters'], $route, $request + ); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerInspector.php b/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerInspector.php new file mode 100644 index 0000000..bf6ff39 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerInspector.php @@ -0,0 +1,133 @@ +getMethods(ReflectionMethod::IS_PUBLIC); + + // To get the routable methods, we will simply spin through all methods on the + // controller instance checking to see if it belongs to the given class and + // is a publicly routable method. If so, we will add it to this listings. + foreach ($methods as $method) { + if ($this->isRoutable($method)) { + $data = $this->getMethodData($method, $prefix); + + $routable[$method->name][] = $data; + + // If the routable method is an index method, we will create a special index + // route which is simply the prefix and the verb and does not contain any + // the wildcard place-holders that each "typical" routes would contain. + if ($data['plain'] == $prefix.'/index') { + $routable[$method->name][] = $this->getIndexData($data, $prefix); + } + } + } + + return $routable; + } + + /** + * Determine if the given controller method is routable. + * + * @param \ReflectionMethod $method + * @return bool + */ + public function isRoutable(ReflectionMethod $method) + { + if ($method->class == 'Illuminate\Routing\Controller') { + return false; + } + + return Str::startsWith($method->name, $this->verbs); + } + + /** + * Get the method data for a given method. + * + * @param \ReflectionMethod $method + * @param string $prefix + * @return array + */ + public function getMethodData(ReflectionMethod $method, $prefix) + { + $verb = $this->getVerb($name = $method->name); + + $uri = $this->addUriWildcards($plain = $this->getPlainUri($name, $prefix)); + + return compact('verb', 'plain', 'uri'); + } + + /** + * Get the routable data for an index method. + * + * @param array $data + * @param string $prefix + * @return array + */ + protected function getIndexData($data, $prefix) + { + return ['verb' => $data['verb'], 'plain' => $prefix, 'uri' => $prefix]; + } + + /** + * Extract the verb from a controller action. + * + * @param string $name + * @return string + */ + public function getVerb($name) + { + return head(explode('_', Str::snake($name))); + } + + /** + * Determine the URI from the given method name. + * + * @param string $name + * @param string $prefix + * @return string + */ + public function getPlainUri($name, $prefix) + { + return $prefix.'/'.implode('-', array_slice(explode('_', Str::snake($name)), 1)); + } + + /** + * Add wildcards to the given URI. + * + * @param string $uri + * @return string + */ + public function addUriWildcards($uri) + { + return $uri.'/{one?}/{two?}/{three?}/{four?}/{five?}'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerServiceProvider.php new file mode 100644 index 0000000..ead0992 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/ControllerServiceProvider.php @@ -0,0 +1,20 @@ +app->singleton('illuminate.route.dispatcher', function ($app) { + return new ControllerDispatcher($app['router'], $app); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/GeneratorServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Routing/GeneratorServiceProvider.php new file mode 100644 index 0000000..1b5bfea --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/GeneratorServiceProvider.php @@ -0,0 +1,67 @@ +registerControllerGenerator(); + + $this->registerMiddlewareGenerator(); + + $this->commands('command.controller.make', 'command.middleware.make'); + } + + /** + * Register the controller generator command. + * + * @return void + */ + protected function registerControllerGenerator() + { + $this->app->singleton('command.controller.make', function ($app) { + return new ControllerMakeCommand($app['files']); + }); + } + + /** + * Register the middleware generator command. + * + * @return void + */ + protected function registerMiddlewareGenerator() + { + $this->app->singleton('command.middleware.make', function ($app) { + return new MiddlewareMakeCommand($app['files']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'command.controller.make', 'command.middleware.make', + ]; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/HostValidator.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/HostValidator.php new file mode 100644 index 0000000..76f9d87 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/HostValidator.php @@ -0,0 +1,25 @@ +getCompiled()->getHostRegex())) { + return true; + } + + return preg_match($route->getCompiled()->getHostRegex(), $request->getHost()); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/MethodValidator.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/MethodValidator.php new file mode 100644 index 0000000..f9cf155 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/MethodValidator.php @@ -0,0 +1,21 @@ +getMethod(), $route->methods()); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/SchemeValidator.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/SchemeValidator.php new file mode 100644 index 0000000..fd5d5af --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/SchemeValidator.php @@ -0,0 +1,27 @@ +httpOnly()) { + return ! $request->secure(); + } elseif ($route->secure()) { + return $request->secure(); + } + + return true; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/UriValidator.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/UriValidator.php new file mode 100644 index 0000000..6a54d12 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/UriValidator.php @@ -0,0 +1,23 @@ +path() == '/' ? '/' : '/'.$request->path(); + + return preg_match($route->getCompiled()->getRegex(), rawurldecode($path)); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/ValidatorInterface.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/ValidatorInterface.php new file mode 100644 index 0000000..0f178f1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Matching/ValidatorInterface.php @@ -0,0 +1,18 @@ +generator = $generator; + } + + /** + * Create a new redirect response to the "home" route. + * + * @param int $status + * @return \Illuminate\Http\RedirectResponse + */ + public function home($status = 302) + { + return $this->to($this->generator->route('home'), $status); + } + + /** + * Create a new redirect response to the previous location. + * + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function back($status = 302, $headers = []) + { + $back = $this->generator->previous(); + + return $this->createRedirect($back, $status, $headers); + } + + /** + * Create a new redirect response to the current URI. + * + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function refresh($status = 302, $headers = []) + { + return $this->to($this->generator->getRequest()->path(), $status, $headers); + } + + /** + * Create a new redirect response, while putting the current URL in the session. + * + * @param string $path + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function guest($path, $status = 302, $headers = [], $secure = null) + { + $this->session->put('url.intended', $this->generator->full()); + + return $this->to($path, $status, $headers, $secure); + } + + /** + * Create a new redirect response to the previously intended location. + * + * @param string $default + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function intended($default = '/', $status = 302, $headers = [], $secure = null) + { + $path = $this->session->pull('url.intended', $default); + + return $this->to($path, $status, $headers, $secure); + } + + /** + * Create a new redirect response to the given path. + * + * @param string $path + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function to($path, $status = 302, $headers = [], $secure = null) + { + $path = $this->generator->to($path, [], $secure); + + return $this->createRedirect($path, $status, $headers); + } + + /** + * Create a new redirect response to an external URL (no validation). + * + * @param string $path + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function away($path, $status = 302, $headers = []) + { + return $this->createRedirect($path, $status, $headers); + } + + /** + * Create a new redirect response to the given HTTPS path. + * + * @param string $path + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function secure($path, $status = 302, $headers = []) + { + return $this->to($path, $status, $headers, true); + } + + /** + * Create a new redirect response to a named route. + * + * @param string $route + * @param array $parameters + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function route($route, $parameters = [], $status = 302, $headers = []) + { + $path = $this->generator->route($route, $parameters); + + return $this->to($path, $status, $headers); + } + + /** + * Create a new redirect response to a controller action. + * + * @param string $action + * @param array $parameters + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function action($action, $parameters = [], $status = 302, $headers = []) + { + $path = $this->generator->action($action, $parameters); + + return $this->to($path, $status, $headers); + } + + /** + * Create a new redirect response. + * + * @param string $path + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + protected function createRedirect($path, $status, $headers) + { + $redirect = new RedirectResponse($path, $status, $headers); + + if (isset($this->session)) { + $redirect->setSession($this->session); + } + + $redirect->setRequest($this->generator->getRequest()); + + return $redirect; + } + + /** + * Get the URL generator instance. + * + * @return \Illuminate\Routing\UrlGenerator + */ + public function getUrlGenerator() + { + return $this->generator; + } + + /** + * Set the active session store. + * + * @param \Illuminate\Session\Store $session + * @return void + */ + public function setSession(SessionStore $session) + { + $this->session = $session; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/ResourceRegistrar.php b/core/vendor/laravel/framework/src/Illuminate/Routing/ResourceRegistrar.php new file mode 100644 index 0000000..bd46c9a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/ResourceRegistrar.php @@ -0,0 +1,389 @@ +router = $router; + } + + /** + * Route a resource to a controller. + * + * @param string $name + * @param string $controller + * @param array $options + * @return void + */ + public function register($name, $controller, array $options = []) + { + // If the resource name contains a slash, we will assume the developer wishes to + // register these resource routes with a prefix so we will set that up out of + // the box so they don't have to mess with it. Otherwise, we will continue. + if (Str::contains($name, '/')) { + $this->prefixedResource($name, $controller, $options); + + return; + } + + // We need to extract the base resource from the resource name. Nested resources + // are supported in the framework, but we need to know what name to use for a + // place-holder on the route wildcards, which should be the base resources. + $base = $this->getResourceWildcard(last(explode('.', $name))); + + $defaults = $this->resourceDefaults; + + foreach ($this->getResourceMethods($defaults, $options) as $m) { + $this->{'addResource'.ucfirst($m)}($name, $base, $controller, $options); + } + } + + /** + * Build a set of prefixed resource routes. + * + * @param string $name + * @param string $controller + * @param array $options + * @return void + */ + protected function prefixedResource($name, $controller, array $options) + { + list($name, $prefix) = $this->getResourcePrefix($name); + + // We need to extract the base resource from the resource name. Nested resources + // are supported in the framework, but we need to know what name to use for a + // place-holder on the route wildcards, which should be the base resources. + $callback = function ($me) use ($name, $controller, $options) { + $me->resource($name, $controller, $options); + }; + + return $this->router->group(compact('prefix'), $callback); + } + + /** + * Extract the resource and prefix from a resource name. + * + * @param string $name + * @return array + */ + protected function getResourcePrefix($name) + { + $segments = explode('/', $name); + + // To get the prefix, we will take all of the name segments and implode them on + // a slash. This will generate a proper URI prefix for us. Then we take this + // last segment, which will be considered the final resources name we use. + $prefix = implode('/', array_slice($segments, 0, -1)); + + return [end($segments), $prefix]; + } + + /** + * Get the applicable resource methods. + * + * @param array $defaults + * @param array $options + * @return array + */ + protected function getResourceMethods($defaults, $options) + { + if (isset($options['only'])) { + return array_intersect($defaults, (array) $options['only']); + } elseif (isset($options['except'])) { + return array_diff($defaults, (array) $options['except']); + } + + return $defaults; + } + + /** + * Get the base resource URI for a given resource. + * + * @param string $resource + * @return string + */ + public function getResourceUri($resource) + { + if (! Str::contains($resource, '.')) { + return $resource; + } + + // Once we have built the base URI, we'll remove the wildcard holder for this + // base resource name so that the individual route adders can suffix these + // paths however they need to, as some do not have any wildcards at all. + $segments = explode('.', $resource); + + $uri = $this->getNestedResourceUri($segments); + + return str_replace('/{'.$this->getResourceWildcard(end($segments)).'}', '', $uri); + } + + /** + * Get the URI for a nested resource segment array. + * + * @param array $segments + * @return string + */ + protected function getNestedResourceUri(array $segments) + { + // We will spin through the segments and create a place-holder for each of the + // resource segments, as well as the resource itself. Then we should get an + // entire string for the resource URI that contains all nested resources. + return implode('/', array_map(function ($s) { + return $s.'/{'.$this->getResourceWildcard($s).'}'; + }, $segments)); + } + + /** + * Get the action array for a resource route. + * + * @param string $resource + * @param string $controller + * @param string $method + * @param array $options + * @return array + */ + protected function getResourceAction($resource, $controller, $method, $options) + { + $name = $this->getResourceName($resource, $method, $options); + + return ['as' => $name, 'uses' => $controller.'@'.$method]; + } + + /** + * Get the name for a given resource. + * + * @param string $resource + * @param string $method + * @param array $options + * @return string + */ + protected function getResourceName($resource, $method, $options) + { + if (isset($options['names'][$method])) { + return $options['names'][$method]; + } + + // If a global prefix has been assigned to all names for this resource, we will + // grab that so we can prepend it onto the name when we create this name for + // the resource action. Otherwise we'll just use an empty string for here. + $prefix = isset($options['as']) ? $options['as'].'.' : ''; + + if (! $this->router->hasGroupStack()) { + return $prefix.$resource.'.'.$method; + } + + return $this->getGroupResourceName($prefix, $resource, $method); + } + + /** + * Get the resource name for a grouped resource. + * + * @param string $prefix + * @param string $resource + * @param string $method + * @return string + */ + protected function getGroupResourceName($prefix, $resource, $method) + { + $group = trim(str_replace('/', '.', $this->router->getLastGroupPrefix()), '.'); + + if (empty($group)) { + return trim("{$prefix}{$resource}.{$method}", '.'); + } + + return trim("{$prefix}{$group}.{$resource}.{$method}", '.'); + } + + /** + * Format a resource wildcard for usage. + * + * @param string $value + * @return string + */ + public function getResourceWildcard($value) + { + return str_replace('-', '_', $value); + } + + /** + * Add the index method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\Route + */ + protected function addResourceIndex($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name); + + $action = $this->getResourceAction($name, $controller, 'index', $options); + + return $this->router->get($uri, $action); + } + + /** + * Add the create method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\Route + */ + protected function addResourceCreate($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name).'/create'; + + $action = $this->getResourceAction($name, $controller, 'create', $options); + + return $this->router->get($uri, $action); + } + + /** + * Add the store method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\Route + */ + protected function addResourceStore($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name); + + $action = $this->getResourceAction($name, $controller, 'store', $options); + + return $this->router->post($uri, $action); + } + + /** + * Add the show method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\Route + */ + protected function addResourceShow($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}'; + + $action = $this->getResourceAction($name, $controller, 'show', $options); + + return $this->router->get($uri, $action); + } + + /** + * Add the edit method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\Route + */ + protected function addResourceEdit($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}/edit'; + + $action = $this->getResourceAction($name, $controller, 'edit', $options); + + return $this->router->get($uri, $action); + } + + /** + * Add the update method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @param array $options + * @return void + */ + protected function addResourceUpdate($name, $base, $controller, $options) + { + $this->addPutResourceUpdate($name, $base, $controller, $options); + + return $this->addPatchResourceUpdate($name, $base, $controller); + } + + /** + * Add the update method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\Route + */ + protected function addPutResourceUpdate($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}'; + + $action = $this->getResourceAction($name, $controller, 'update', $options); + + return $this->router->put($uri, $action); + } + + /** + * Add the update method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addPatchResourceUpdate($name, $base, $controller) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}'; + + $this->router->patch($uri, $controller.'@update'); + } + + /** + * Add the destroy method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\Route + */ + protected function addResourceDestroy($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}'; + + $action = $this->getResourceAction($name, $controller, 'destroy', $options); + + return $this->router->delete($uri, $action); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/ResponseFactory.php b/core/vendor/laravel/framework/src/Illuminate/Routing/ResponseFactory.php new file mode 100644 index 0000000..02e77cb --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/ResponseFactory.php @@ -0,0 +1,209 @@ +view = $view; + $this->redirector = $redirector; + } + + /** + * Return a new response from the application. + * + * @param string $content + * @param int $status + * @param array $headers + * @return \Illuminate\Http\Response + */ + public function make($content = '', $status = 200, array $headers = []) + { + return new Response($content, $status, $headers); + } + + /** + * Return a new view response from the application. + * + * @param string $view + * @param array $data + * @param int $status + * @param array $headers + * @return \Illuminate\Http\Response + */ + public function view($view, $data = [], $status = 200, array $headers = []) + { + return static::make($this->view->make($view, $data), $status, $headers); + } + + /** + * Return a new JSON response from the application. + * + * @param string|array $data + * @param int $status + * @param array $headers + * @param int $options + * @return \Illuminate\Http\JsonResponse + */ + public function json($data = [], $status = 200, array $headers = [], $options = 0) + { + if ($data instanceof Arrayable && ! $data instanceof JsonSerializable) { + $data = $data->toArray(); + } + + return new JsonResponse($data, $status, $headers, $options); + } + + /** + * Return a new JSONP response from the application. + * + * @param string $callback + * @param string|array $data + * @param int $status + * @param array $headers + * @param int $options + * @return \Illuminate\Http\JsonResponse + */ + public function jsonp($callback, $data = [], $status = 200, array $headers = [], $options = 0) + { + return $this->json($data, $status, $headers, $options)->setCallback($callback); + } + + /** + * Return a new streamed response from the application. + * + * @param \Closure $callback + * @param int $status + * @param array $headers + * @return \Symfony\Component\HttpFoundation\StreamedResponse + */ + public function stream($callback, $status = 200, array $headers = []) + { + return new StreamedResponse($callback, $status, $headers); + } + + /** + * Create a new file download response. + * + * @param \SplFileInfo|string $file + * @param string $name + * @param array $headers + * @param string|null $disposition + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public function download($file, $name = null, array $headers = [], $disposition = 'attachment') + { + $response = new BinaryFileResponse($file, 200, $headers, true, $disposition); + + if (! is_null($name)) { + return $response->setContentDisposition($disposition, $name, str_replace('%', '', Str::ascii($name))); + } + + return $response; + } + + /** + * Create a new redirect response to the given path. + * + * @param string $path + * @param int $status + * @param array $headers + * @param bool|null $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function redirectTo($path, $status = 302, $headers = [], $secure = null) + { + return $this->redirector->to($path, $status, $headers, $secure); + } + + /** + * Create a new redirect response to a named route. + * + * @param string $route + * @param array $parameters + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function redirectToRoute($route, $parameters = [], $status = 302, $headers = []) + { + return $this->redirector->route($route, $parameters, $status, $headers); + } + + /** + * Create a new redirect response to a controller action. + * + * @param string $action + * @param array $parameters + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function redirectToAction($action, $parameters = [], $status = 302, $headers = []) + { + return $this->redirector->action($action, $parameters, $status, $headers); + } + + /** + * Create a new redirect response, while putting the current URL in the session. + * + * @param string $path + * @param int $status + * @param array $headers + * @param bool|null $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function redirectGuest($path, $status = 302, $headers = [], $secure = null) + { + return $this->redirector->guest($path, $status, $headers, $secure); + } + + /** + * Create a new redirect response to the previously intended location. + * + * @param string $default + * @param int $status + * @param array $headers + * @param bool|null $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function redirectToIntended($default = '/', $status = 302, $headers = [], $secure = null) + { + return $this->redirector->intended($default, $status, $headers, $secure); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Route.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Route.php new file mode 100644 index 0000000..cd4627a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Route.php @@ -0,0 +1,1027 @@ +uri = $uri; + $this->methods = (array) $methods; + $this->action = $this->parseAction($action); + + if (in_array('GET', $this->methods) && ! in_array('HEAD', $this->methods)) { + $this->methods[] = 'HEAD'; + } + + if (isset($this->action['prefix'])) { + $this->prefix($this->action['prefix']); + } + } + + /** + * Run the route action and return the response. + * + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function run(Request $request) + { + $this->container = $this->container ?: new Container; + + try { + if (! is_string($this->action['uses'])) { + return $this->runCallable($request); + } + + if ($this->customDispatcherIsBound()) { + return $this->runWithCustomDispatcher($request); + } + + return $this->runController($request); + } catch (HttpResponseException $e) { + return $e->getResponse(); + } + } + + /** + * Run the route action and return the response. + * + * @param \Illuminate\Http\Request $request + * @return mixed + */ + protected function runCallable(Request $request) + { + $parameters = $this->resolveMethodDependencies( + $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses']) + ); + + return call_user_func_array($this->action['uses'], $parameters); + } + + /** + * Run the route action and return the response. + * + * @param \Illuminate\Http\Request $request + * @return mixed + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + protected function runController(Request $request) + { + list($class, $method) = explode('@', $this->action['uses']); + + $parameters = $this->resolveClassMethodDependencies( + $this->parametersWithoutNulls(), $class, $method + ); + + if (! method_exists($instance = $this->container->make($class), $method)) { + throw new NotFoundHttpException; + } + + return call_user_func_array([$instance, $method], $parameters); + } + + /** + * Determine if a custom route dispatcher is bound in the container. + * + * @return bool + */ + protected function customDispatcherIsBound() + { + return $this->container->bound('illuminate.route.dispatcher'); + } + + /** + * Send the request and route to a custom dispatcher for handling. + * + * @param \Illuminate\Http\Request $request + * @return mixed + */ + protected function runWithCustomDispatcher(Request $request) + { + list($class, $method) = explode('@', $this->action['uses']); + + $dispatcher = $this->container->make('illuminate.route.dispatcher'); + + return $dispatcher->dispatch($this, $request, $class, $method); + } + + /** + * Determine if the route matches given request. + * + * @param \Illuminate\Http\Request $request + * @param bool $includingMethod + * @return bool + */ + public function matches(Request $request, $includingMethod = true) + { + $this->compileRoute(); + + foreach ($this->getValidators() as $validator) { + if (! $includingMethod && $validator instanceof MethodValidator) { + continue; + } + + if (! $validator->matches($this, $request)) { + return false; + } + } + + return true; + } + + /** + * Compile the route into a Symfony CompiledRoute instance. + * + * @return void + */ + protected function compileRoute() + { + $optionals = $this->extractOptionalParameters(); + + $uri = preg_replace('/\{(\w+?)\?\}/', '{$1}', $this->uri); + + $this->compiled = with( + + new SymfonyRoute($uri, $optionals, $this->wheres, [], $this->domain() ?: '') + + )->compile(); + } + + /** + * Get the optional parameters for the route. + * + * @return array + */ + protected function extractOptionalParameters() + { + preg_match_all('/\{(\w+?)\?\}/', $this->uri, $matches); + + return isset($matches[1]) ? array_fill_keys($matches[1], null) : []; + } + + /** + * Get or set the middlewares attached to the route. + * + * @param array|string|null $middleware + * @return $this|array + */ + public function middleware($middleware = null) + { + if (is_null($middleware)) { + return (array) Arr::get($this->action, 'middleware', []); + } + + if (is_string($middleware)) { + $middleware = [$middleware]; + } + + $this->action['middleware'] = array_merge( + (array) Arr::get($this->action, 'middleware', []), $middleware + ); + + return $this; + } + + /** + * Get the "before" filters for the route. + * + * @return array + * + * @deprecated since version 5.1. + */ + public function beforeFilters() + { + if (! isset($this->action['before'])) { + return []; + } + + return $this->parseFilters($this->action['before']); + } + + /** + * Get the "after" filters for the route. + * + * @return array + * + * @deprecated since version 5.1. + */ + public function afterFilters() + { + if (! isset($this->action['after'])) { + return []; + } + + return $this->parseFilters($this->action['after']); + } + + /** + * Parse the given filter string. + * + * @param string $filters + * @return array + * + * @deprecated since version 5.1. + */ + public static function parseFilters($filters) + { + return Arr::build(static::explodeFilters($filters), function ($key, $value) { + return Route::parseFilter($value); + }); + } + + /** + * Turn the filters into an array if they aren't already. + * + * @param array|string $filters + * @return array + */ + protected static function explodeFilters($filters) + { + if (is_array($filters)) { + return static::explodeArrayFilters($filters); + } + + return array_map('trim', explode('|', $filters)); + } + + /** + * Flatten out an array of filter declarations. + * + * @param array $filters + * @return array + */ + protected static function explodeArrayFilters(array $filters) + { + $results = []; + + foreach ($filters as $filter) { + $results = array_merge($results, array_map('trim', explode('|', $filter))); + } + + return $results; + } + + /** + * Parse the given filter into name and parameters. + * + * @param string $filter + * @return array + * + * @deprecated since version 5.1. + */ + public static function parseFilter($filter) + { + if (! Str::contains($filter, ':')) { + return [$filter, []]; + } + + return static::parseParameterFilter($filter); + } + + /** + * Parse a filter with parameters. + * + * @param string $filter + * @return array + */ + protected static function parseParameterFilter($filter) + { + list($name, $parameters) = explode(':', $filter, 2); + + return [$name, explode(',', $parameters)]; + } + + /** + * Determine a given parameter exists from the route. + * + * @param string $name + * @return bool + */ + public function hasParameter($name) + { + return array_key_exists($name, $this->parameters()); + } + + /** + * Get a given parameter from the route. + * + * @param string $name + * @param mixed $default + * @return string|object + */ + public function getParameter($name, $default = null) + { + return $this->parameter($name, $default); + } + + /** + * Get a given parameter from the route. + * + * @param string $name + * @param mixed $default + * @return string|object + */ + public function parameter($name, $default = null) + { + return Arr::get($this->parameters(), $name, $default); + } + + /** + * Set a parameter to the given value. + * + * @param string $name + * @param mixed $value + * @return void + */ + public function setParameter($name, $value) + { + $this->parameters(); + + $this->parameters[$name] = $value; + } + + /** + * Unset a parameter on the route if it is set. + * + * @param string $name + * @return void + */ + public function forgetParameter($name) + { + $this->parameters(); + + unset($this->parameters[$name]); + } + + /** + * Get the key / value list of parameters for the route. + * + * @return array + * + * @throws \LogicException + */ + public function parameters() + { + if (isset($this->parameters)) { + return array_map(function ($value) { + return is_string($value) ? rawurldecode($value) : $value; + }, $this->parameters); + } + + throw new LogicException('Route is not bound.'); + } + + /** + * Get the key / value list of parameters without null values. + * + * @return array + */ + public function parametersWithoutNulls() + { + return array_filter($this->parameters(), function ($p) { + return ! is_null($p); + }); + } + + /** + * Get all of the parameter names for the route. + * + * @return array + */ + public function parameterNames() + { + if (isset($this->parameterNames)) { + return $this->parameterNames; + } + + return $this->parameterNames = $this->compileParameterNames(); + } + + /** + * Get the parameter names for the route. + * + * @return array + */ + protected function compileParameterNames() + { + preg_match_all('/\{(.*?)\}/', $this->domain().$this->uri, $matches); + + return array_map(function ($m) { + return trim($m, '?'); + }, $matches[1]); + } + + /** + * Bind the route to a given request for execution. + * + * @param \Illuminate\Http\Request $request + * @return $this + */ + public function bind(Request $request) + { + $this->compileRoute(); + + $this->bindParameters($request); + + return $this; + } + + /** + * Extract the parameter list from the request. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function bindParameters(Request $request) + { + // If the route has a regular expression for the host part of the URI, we will + // compile that and get the parameter matches for this domain. We will then + // merge them into this parameters array so that this array is completed. + $params = $this->matchToKeys( + + array_slice($this->bindPathParameters($request), 1) + + ); + + // If the route has a regular expression for the host part of the URI, we will + // compile that and get the parameter matches for this domain. We will then + // merge them into this parameters array so that this array is completed. + if (! is_null($this->compiled->getHostRegex())) { + $params = $this->bindHostParameters( + $request, $params + ); + } + + return $this->parameters = $this->replaceDefaults($params); + } + + /** + * Get the parameter matches for the path portion of the URI. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function bindPathParameters(Request $request) + { + preg_match($this->compiled->getRegex(), '/'.$request->decodedPath(), $matches); + + return $matches; + } + + /** + * Extract the parameter list from the host part of the request. + * + * @param \Illuminate\Http\Request $request + * @param array $parameters + * @return array + */ + protected function bindHostParameters(Request $request, $parameters) + { + preg_match($this->compiled->getHostRegex(), $request->getHost(), $matches); + + return array_merge($this->matchToKeys(array_slice($matches, 1)), $parameters); + } + + /** + * Combine a set of parameter matches with the route's keys. + * + * @param array $matches + * @return array + */ + protected function matchToKeys(array $matches) + { + if (count($this->parameterNames()) == 0) { + return []; + } + + $parameters = array_intersect_key($matches, array_flip($this->parameterNames())); + + return array_filter($parameters, function ($value) { + return is_string($value) && strlen($value) > 0; + }); + } + + /** + * Replace null parameters with their defaults. + * + * @param array $parameters + * @return array + */ + protected function replaceDefaults(array $parameters) + { + foreach ($parameters as $key => &$value) { + $value = isset($value) ? $value : Arr::get($this->defaults, $key); + } + + foreach ($this->defaults as $key => $value) { + if (! isset($parameters[$key])) { + $parameters[$key] = $value; + } + } + + return $parameters; + } + + /** + * Parse the route action into a standard array. + * + * @param callable|array $action + * @return array + * + * @throws \UnexpectedValueException + */ + protected function parseAction($action) + { + // If the action is already a Closure instance, we will just set that instance + // as the "uses" property, because there is nothing else we need to do when + // it is available. Otherwise we will need to find it in the action list. + if (is_callable($action)) { + return ['uses' => $action]; + } + + // If no "uses" property has been set, we will dig through the array to find a + // Closure instance within this list. We will set the first Closure we come + // across into the "uses" property that will get fired off by this route. + elseif (! isset($action['uses'])) { + $action['uses'] = $this->findCallable($action); + } + + if (is_string($action['uses']) && ! Str::contains($action['uses'], '@')) { + throw new UnexpectedValueException(sprintf( + 'Invalid route action: [%s]', $action['uses'] + )); + } + + return $action; + } + + /** + * Find the callable in an action array. + * + * @param array $action + * @return callable + */ + protected function findCallable(array $action) + { + return Arr::first($action, function ($key, $value) { + return is_callable($value) && is_numeric($key); + }); + } + + /** + * Get the route validators for the instance. + * + * @return array + */ + public static function getValidators() + { + if (isset(static::$validators)) { + return static::$validators; + } + + // To match the route, we will use a chain of responsibility pattern with the + // validator implementations. We will spin through each one making sure it + // passes and then we will know if the route as a whole matches request. + return static::$validators = [ + new MethodValidator, new SchemeValidator, + new HostValidator, new UriValidator, + ]; + } + + /** + * Add before filters to the route. + * + * @param string $filters + * @return $this + * + * @deprecated since version 5.1. + */ + public function before($filters) + { + return $this->addFilters('before', $filters); + } + + /** + * Add after filters to the route. + * + * @param string $filters + * @return $this + * + * @deprecated since version 5.1. + */ + public function after($filters) + { + return $this->addFilters('after', $filters); + } + + /** + * Add the given filters to the route by type. + * + * @param string $type + * @param string $filters + * @return $this + */ + protected function addFilters($type, $filters) + { + $filters = static::explodeFilters($filters); + + if (isset($this->action[$type])) { + $existing = static::explodeFilters($this->action[$type]); + + $this->action[$type] = array_merge($existing, $filters); + } else { + $this->action[$type] = $filters; + } + + return $this; + } + + /** + * Set a default value for the route. + * + * @param string $key + * @param mixed $value + * @return $this + */ + public function defaults($key, $value) + { + $this->defaults[$key] = $value; + + return $this; + } + + /** + * Set a regular expression requirement on the route. + * + * @param array|string $name + * @param string $expression + * @return $this + */ + public function where($name, $expression = null) + { + foreach ($this->parseWhere($name, $expression) as $name => $expression) { + $this->wheres[$name] = $expression; + } + + return $this; + } + + /** + * Parse arguments to the where method into an array. + * + * @param array|string $name + * @param string $expression + * @return array + */ + protected function parseWhere($name, $expression) + { + return is_array($name) ? $name : [$name => $expression]; + } + + /** + * Set a list of regular expression requirements on the route. + * + * @param array $wheres + * @return $this + */ + protected function whereArray(array $wheres) + { + foreach ($wheres as $name => $expression) { + $this->where($name, $expression); + } + + return $this; + } + + /** + * Add a prefix to the route URI. + * + * @param string $prefix + * @return $this + */ + public function prefix($prefix) + { + $uri = rtrim($prefix, '/').'/'.ltrim($this->uri, '/'); + + $this->uri = trim($uri, '/'); + + return $this; + } + + /** + * Get the URI associated with the route. + * + * @return string + */ + public function getPath() + { + return $this->uri(); + } + + /** + * Get the URI associated with the route. + * + * @return string + */ + public function uri() + { + return $this->uri; + } + + /** + * Get the HTTP verbs the route responds to. + * + * @return array + */ + public function getMethods() + { + return $this->methods(); + } + + /** + * Get the HTTP verbs the route responds to. + * + * @return array + */ + public function methods() + { + return $this->methods; + } + + /** + * Determine if the route only responds to HTTP requests. + * + * @return bool + */ + public function httpOnly() + { + return in_array('http', $this->action, true); + } + + /** + * Determine if the route only responds to HTTPS requests. + * + * @return bool + */ + public function httpsOnly() + { + return $this->secure(); + } + + /** + * Determine if the route only responds to HTTPS requests. + * + * @return bool + */ + public function secure() + { + return in_array('https', $this->action, true); + } + + /** + * Get the domain defined for the route. + * + * @return string|null + */ + public function domain() + { + return isset($this->action['domain']) ? $this->action['domain'] : null; + } + + /** + * Get the URI that the route responds to. + * + * @return string + */ + public function getUri() + { + return $this->uri; + } + + /** + * Set the URI that the route responds to. + * + * @param string $uri + * @return \Illuminate\Routing\Route + */ + public function setUri($uri) + { + $this->uri = $uri; + + return $this; + } + + /** + * Get the prefix of the route instance. + * + * @return string + */ + public function getPrefix() + { + return isset($this->action['prefix']) ? $this->action['prefix'] : null; + } + + /** + * Get the name of the route instance. + * + * @return string + */ + public function getName() + { + return isset($this->action['as']) ? $this->action['as'] : null; + } + + /** + * Add or change the route name. + * + * @param string $name + * @return $this + */ + public function name($name) + { + $this->action['as'] = isset($this->action['as']) ? $this->action['as'].$name : $name; + + return $this; + } + + /** + * Get the action name for the route. + * + * @return string + */ + public function getActionName() + { + return isset($this->action['controller']) ? $this->action['controller'] : 'Closure'; + } + + /** + * Get the action array for the route. + * + * @return array + */ + public function getAction() + { + return $this->action; + } + + /** + * Set the action array for the route. + * + * @param array $action + * @return $this + */ + public function setAction(array $action) + { + $this->action = $action; + + return $this; + } + + /** + * Get the compiled version of the route. + * + * @return \Symfony\Component\Routing\CompiledRoute + */ + public function getCompiled() + { + return $this->compiled; + } + + /** + * Set the container instance on the route. + * + * @param \Illuminate\Container\Container $container + * @return $this + */ + public function setContainer(Container $container) + { + $this->container = $container; + + return $this; + } + + /** + * Prepare the route instance for serialization. + * + * @return void + * + * @throws \LogicException + */ + public function prepareForSerialization() + { + if ($this->action['uses'] instanceof Closure) { + throw new LogicException("Unable to prepare route [{$this->uri}] for serialization. Uses Closure."); + } + + unset($this->container, $this->compiled); + } + + /** + * Dynamically access route parameters. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->parameter($key); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php b/core/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php new file mode 100644 index 0000000..4821a1f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php @@ -0,0 +1,313 @@ +addToCollections($route); + + $this->addLookups($route); + + return $route; + } + + /** + * Add the given route to the arrays of routes. + * + * @param \Illuminate\Routing\Route $route + * @return void + */ + protected function addToCollections($route) + { + $domainAndUri = $route->domain().$route->getUri(); + + foreach ($route->methods() as $method) { + $this->routes[$method][$domainAndUri] = $route; + } + + $this->allRoutes[$method.$domainAndUri] = $route; + } + + /** + * Add the route to any look-up tables if necessary. + * + * @param \Illuminate\Routing\Route $route + * @return void + */ + protected function addLookups($route) + { + // If the route has a name, we will add it to the name look-up table so that we + // will quickly be able to find any route associate with a name and not have + // to iterate through every route every time we need to perform a look-up. + $action = $route->getAction(); + + if (isset($action['as'])) { + $this->nameList[$action['as']] = $route; + } + + // When the route is routing to a controller we will also store the action that + // is used by the route. This will let us reverse route to controllers while + // processing a request and easily generate URLs to the given controllers. + if (isset($action['controller'])) { + $this->addToActionList($action, $route); + } + } + + /** + * Refresh the name look-up table. + * + * This is done in case any names are fluently defined. + * + * @return void + */ + public function refreshNameLookups() + { + $this->nameList = []; + + foreach ($this->allRoutes as $route) { + if ($route->getName()) { + $this->nameList[$route->getName()] = $route; + } + } + } + + /** + * Add a route to the controller action dictionary. + * + * @param array $action + * @param \Illuminate\Routing\Route $route + * @return void + */ + protected function addToActionList($action, $route) + { + $this->actionList[trim($action['controller'], '\\')] = $route; + } + + /** + * Find the first route matching a given request. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Routing\Route + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function match(Request $request) + { + $routes = $this->get($request->getMethod()); + + // First, we will see if we can find a matching route for this current request + // method. If we can, great, we can just return it so that it can be called + // by the consumer. Otherwise we will check for routes with another verb. + $route = $this->check($routes, $request); + + if (! is_null($route)) { + return $route->bind($request); + } + + // If no route was found we will now check if a matching route is specified by + // another HTTP verb. If it is we will need to throw a MethodNotAllowed and + // inform the user agent of which HTTP verb it should use for this route. + $others = $this->checkForAlternateVerbs($request); + + if (count($others) > 0) { + return $this->getRouteForMethods($request, $others); + } + + throw new NotFoundHttpException; + } + + /** + * Determine if any routes match on another HTTP verb. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function checkForAlternateVerbs($request) + { + $methods = array_diff(Router::$verbs, [$request->getMethod()]); + + // Here we will spin through all verbs except for the current request verb and + // check to see if any routes respond to them. If they do, we will return a + // proper error response with the correct headers on the response string. + $others = []; + + foreach ($methods as $method) { + if (! is_null($this->check($this->get($method), $request, false))) { + $others[] = $method; + } + } + + return $others; + } + + /** + * Get a route (if necessary) that responds when other available methods are present. + * + * @param \Illuminate\Http\Request $request + * @param array $methods + * @return \Illuminate\Routing\Route + * + * @throws \Symfony\Component\Routing\Exception\MethodNotAllowedHttpException + */ + protected function getRouteForMethods($request, array $methods) + { + if ($request->method() == 'OPTIONS') { + return (new Route('OPTIONS', $request->path(), function () use ($methods) { + return new Response('', 200, ['Allow' => implode(',', $methods)]); + }))->bind($request); + } + + $this->methodNotAllowed($methods); + } + + /** + * Throw a method not allowed HTTP exception. + * + * @param array $others + * @return void + * + * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException + */ + protected function methodNotAllowed(array $others) + { + throw new MethodNotAllowedHttpException($others); + } + + /** + * Determine if a route in the array matches the request. + * + * @param array $routes + * @param \Illuminate\http\Request $request + * @param bool $includingMethod + * @return \Illuminate\Routing\Route|null + */ + protected function check(array $routes, $request, $includingMethod = true) + { + return Arr::first($routes, function ($key, $value) use ($request, $includingMethod) { + return $value->matches($request, $includingMethod); + }); + } + + /** + * Get all of the routes in the collection. + * + * @param string|null $method + * @return array + */ + protected function get($method = null) + { + if (is_null($method)) { + return $this->getRoutes(); + } + + return Arr::get($this->routes, $method, []); + } + + /** + * Determine if the route collection contains a given named route. + * + * @param string $name + * @return bool + */ + public function hasNamedRoute($name) + { + return ! is_null($this->getByName($name)); + } + + /** + * Get a route instance by its name. + * + * @param string $name + * @return \Illuminate\Routing\Route|null + */ + public function getByName($name) + { + return isset($this->nameList[$name]) ? $this->nameList[$name] : null; + } + + /** + * Get a route instance by its controller action. + * + * @param string $action + * @return \Illuminate\Routing\Route|null + */ + public function getByAction($action) + { + return isset($this->actionList[$action]) ? $this->actionList[$action] : null; + } + + /** + * Get all of the routes in the collection. + * + * @return array + */ + public function getRoutes() + { + return array_values($this->allRoutes); + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->getRoutes()); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count() + { + return count($this->getRoutes()); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php b/core/vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php new file mode 100644 index 0000000..9ab742a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php @@ -0,0 +1,149 @@ +resolveClassMethodDependencies([], $instance, $method) + ); + } + + /** + * Resolve the object method's type-hinted dependencies. + * + * @param array $parameters + * @param object $instance + * @param string $method + * @return array + */ + protected function resolveClassMethodDependencies(array $parameters, $instance, $method) + { + if (! method_exists($instance, $method)) { + return $parameters; + } + + return $this->resolveMethodDependencies( + $parameters, new ReflectionMethod($instance, $method) + ); + } + + /** + * Resolve the given method's type-hinted dependencies. + * + * @param array $parameters + * @param \ReflectionFunctionAbstract $reflector + * @return array + */ + public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector) + { + $originalParameters = $parameters; + + foreach ($reflector->getParameters() as $key => $parameter) { + $instance = $this->transformDependency( + $parameter, $parameters, $originalParameters + ); + + if (! is_null($instance)) { + $this->spliceIntoParameters($parameters, $key, $instance); + } + } + + return $parameters; + } + + /** + * Attempt to transform the given parameter into a class instance. + * + * @param \ReflectionParameter $parameter + * @param array $parameters + * @param array $originalParameters + * @return mixed + */ + protected function transformDependency(ReflectionParameter $parameter, $parameters, $originalParameters) + { + $class = $parameter->getClass(); + + // If the parameter has a type-hinted class, we will check to see if it is already in + // the list of parameters. If it is we will just skip it as it is probably a model + // binding and we do not want to mess with those; otherwise, we resolve it here. + if ($class && ! $this->alreadyInParameters($class->name, $parameters)) { + return $this->container->make($class->name); + } + } + + /** + * Determine if the given type-hinted class is an implict Eloquent binding. + * + * Must not already be resolved in the parameter list by an explicit model binding. + * + * @param \ReflectionClass $class + * @param array $parameters + * @return bool + */ + protected function vacantEloquentParameter(ReflectionClass $class, array $parameters) + { + return $class->isSubclassOf(Model::class) && + ! $this->alreadyInParameters($class->name, $parameters); + } + + /** + * Extract an implicit model binding's key out of the parameter list. + * + * @param \ReflectionParameter $parameter + * @param array $originalParameters + * + * @return mixed + */ + protected function extractModelIdentifier(ReflectionParameter $parameter, array $originalParameters) + { + return Arr::first($originalParameters, function ($parameterKey) use ($parameter) { + return $parameterKey === $parameter->name; + }); + } + + /** + * Determine if an object of the given class is in a list of parameters. + * + * @param string $class + * @param array $parameters + * @return bool + */ + protected function alreadyInParameters($class, array $parameters) + { + return ! is_null(Arr::first($parameters, function ($key, $value) use ($class) { + return $value instanceof $class; + })); + } + + /** + * Splice the given value into the parameter list. + * + * @param array $parameters + * @param string $key + * @param mixed $instance + * @return void + */ + protected function spliceIntoParameters(array &$parameters, $key, $instance) + { + array_splice( + $parameters, $key, 0, [$instance] + ); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/Router.php b/core/vendor/laravel/framework/src/Illuminate/Routing/Router.php new file mode 100644 index 0000000..d6aacb4 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/Router.php @@ -0,0 +1,1426 @@ +events = $events; + $this->routes = new RouteCollection; + $this->container = $container ?: new Container; + + $this->bind('_missing', function ($v) { + return explode('/', $v); + }); + } + + /** + * Register a new GET route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + public function get($uri, $action) + { + return $this->addRoute(['GET', 'HEAD'], $uri, $action); + } + + /** + * Register a new POST route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + public function post($uri, $action) + { + return $this->addRoute('POST', $uri, $action); + } + + /** + * Register a new PUT route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + public function put($uri, $action) + { + return $this->addRoute('PUT', $uri, $action); + } + + /** + * Register a new PATCH route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + public function patch($uri, $action) + { + return $this->addRoute('PATCH', $uri, $action); + } + + /** + * Register a new DELETE route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + public function delete($uri, $action) + { + return $this->addRoute('DELETE', $uri, $action); + } + + /** + * Register a new OPTIONS route with the router. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + public function options($uri, $action) + { + return $this->addRoute('OPTIONS', $uri, $action); + } + + /** + * Register a new route responding to all verbs. + * + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + public function any($uri, $action) + { + $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']; + + return $this->addRoute($verbs, $uri, $action); + } + + /** + * Register a new route with the given verbs. + * + * @param array|string $methods + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + public function match($methods, $uri, $action) + { + return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); + } + + /** + * Register an array of controllers with wildcard routing. + * + * @param array $controllers + * @return void + */ + public function controllers(array $controllers) + { + foreach ($controllers as $uri => $controller) { + $this->controller($uri, $controller); + } + } + + /** + * Route a controller to a URI with wildcard routing. + * + * @param string $uri + * @param string $controller + * @param array $names + * @return void + */ + public function controller($uri, $controller, $names = []) + { + $prepended = $controller; + + // First, we will check to see if a controller prefix has been registered in + // the route group. If it has, we will need to prefix it before trying to + // reflect into the class instance and pull out the method for routing. + if (! empty($this->groupStack)) { + $prepended = $this->prependGroupUses($controller); + } + + $routable = (new ControllerInspector) + ->getRoutable($prepended, $uri); + + // When a controller is routed using this method, we use Reflection to parse + // out all of the routable methods for the controller, then register each + // route explicitly for the developers, so reverse routing is possible. + foreach ($routable as $method => $routes) { + foreach ($routes as $route) { + $this->registerInspected($route, $controller, $method, $names); + } + } + + $this->addFallthroughRoute($controller, $uri); + } + + /** + * Register an inspected controller route. + * + * @param array $route + * @param string $controller + * @param string $method + * @param array $names + * @return void + */ + protected function registerInspected($route, $controller, $method, &$names) + { + $action = ['uses' => $controller.'@'.$method]; + + // If a given controller method has been named, we will assign the name to the + // controller action array, which provides for a short-cut to method naming + // so you don't have to define an individual route for these controllers. + $action['as'] = Arr::get($names, $method); + + $this->{$route['verb']}($route['uri'], $action); + } + + /** + * Add a fallthrough route for a controller. + * + * @param string $controller + * @param string $uri + * @return void + */ + protected function addFallthroughRoute($controller, $uri) + { + $missing = $this->any($uri.'/{_missing}', $controller.'@missingMethod'); + + $missing->where('_missing', '(.*)'); + } + + /** + * Register an array of resource controllers. + * + * @param array $resources + * @return void + */ + public function resources(array $resources) + { + foreach ($resources as $name => $controller) { + $this->resource($name, $controller); + } + } + + /** + * Route a resource to a controller. + * + * @param string $name + * @param string $controller + * @param array $options + * @return void + */ + public function resource($name, $controller, array $options = []) + { + if ($this->container && $this->container->bound('Illuminate\Routing\ResourceRegistrar')) { + $registrar = $this->container->make('Illuminate\Routing\ResourceRegistrar'); + } else { + $registrar = new ResourceRegistrar($this); + } + + $registrar->register($name, $controller, $options); + } + + /** + * Create a route group with shared attributes. + * + * @param array $attributes + * @param \Closure $callback + * @return void + */ + public function group(array $attributes, Closure $callback) + { + $this->updateGroupStack($attributes); + + // Once we have updated the group stack, we will execute the user Closure and + // merge in the groups attributes when the route is created. After we have + // run the callback, we will pop the attributes off of this group stack. + call_user_func($callback, $this); + + array_pop($this->groupStack); + } + + /** + * Update the group stack with the given attributes. + * + * @param array $attributes + * @return void + */ + protected function updateGroupStack(array $attributes) + { + if (! empty($this->groupStack)) { + $attributes = $this->mergeGroup($attributes, end($this->groupStack)); + } + + $this->groupStack[] = $attributes; + } + + /** + * Merge the given array with the last group stack. + * + * @param array $new + * @return array + */ + public function mergeWithLastGroup($new) + { + return $this->mergeGroup($new, end($this->groupStack)); + } + + /** + * Merge the given group attributes. + * + * @param array $new + * @param array $old + * @return array + */ + public static function mergeGroup($new, $old) + { + $new['namespace'] = static::formatUsesPrefix($new, $old); + + $new['prefix'] = static::formatGroupPrefix($new, $old); + + if (isset($new['domain'])) { + unset($old['domain']); + } + + $new['where'] = array_merge( + isset($old['where']) ? $old['where'] : [], + isset($new['where']) ? $new['where'] : [] + ); + + if (isset($old['as'])) { + $new['as'] = $old['as'].(isset($new['as']) ? $new['as'] : ''); + } + + return array_merge_recursive(Arr::except($old, ['namespace', 'prefix', 'where', 'as']), $new); + } + + /** + * Format the uses prefix for the new group attributes. + * + * @param array $new + * @param array $old + * @return string|null + */ + protected static function formatUsesPrefix($new, $old) + { + if (isset($new['namespace'])) { + return isset($old['namespace']) + ? trim($old['namespace'], '\\').'\\'.trim($new['namespace'], '\\') + : trim($new['namespace'], '\\'); + } + + return isset($old['namespace']) ? $old['namespace'] : null; + } + + /** + * Format the prefix for the new group attributes. + * + * @param array $new + * @param array $old + * @return string|null + */ + protected static function formatGroupPrefix($new, $old) + { + $oldPrefix = isset($old['prefix']) ? $old['prefix'] : null; + + if (isset($new['prefix'])) { + return trim($oldPrefix, '/').'/'.trim($new['prefix'], '/'); + } + + return $oldPrefix; + } + + /** + * Get the prefix from the last group on the stack. + * + * @return string + */ + public function getLastGroupPrefix() + { + if (! empty($this->groupStack)) { + $last = end($this->groupStack); + + return isset($last['prefix']) ? $last['prefix'] : ''; + } + + return ''; + } + + /** + * Add a route to the underlying route collection. + * + * @param array|string $methods + * @param string $uri + * @param \Closure|array|string $action + * @return \Illuminate\Routing\Route + */ + protected function addRoute($methods, $uri, $action) + { + return $this->routes->add($this->createRoute($methods, $uri, $action)); + } + + /** + * Create a new route instance. + * + * @param array|string $methods + * @param string $uri + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + protected function createRoute($methods, $uri, $action) + { + // If the route is routing to a controller we will parse the route action into + // an acceptable array format before registering it and creating this route + // instance itself. We need to build the Closure that will call this out. + if ($this->actionReferencesController($action)) { + $action = $this->convertToControllerAction($action); + } + + $route = $this->newRoute( + $methods, $this->prefix($uri), $action + ); + + // If we have groups that need to be merged, we will merge them now after this + // route has already been created and is ready to go. After we're done with + // the merge we will be ready to return the route back out to the caller. + if ($this->hasGroupStack()) { + $this->mergeGroupAttributesIntoRoute($route); + } + + $this->addWhereClausesToRoute($route); + + return $route; + } + + /** + * Create a new Route object. + * + * @param array|string $methods + * @param string $uri + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + protected function newRoute($methods, $uri, $action) + { + return (new Route($methods, $uri, $action))->setContainer($this->container); + } + + /** + * Prefix the given URI with the last prefix. + * + * @param string $uri + * @return string + */ + protected function prefix($uri) + { + return trim(trim($this->getLastGroupPrefix(), '/').'/'.trim($uri, '/'), '/') ?: '/'; + } + + /** + * Add the necessary where clauses to the route based on its initial registration. + * + * @param \Illuminate\Routing\Route $route + * @return \Illuminate\Routing\Route + */ + protected function addWhereClausesToRoute($route) + { + $where = isset($route->getAction()['where']) ? $route->getAction()['where'] : []; + + $route->where(array_merge($this->patterns, $where)); + + return $route; + } + + /** + * Merge the group stack with the controller action. + * + * @param \Illuminate\Routing\Route $route + * @return void + */ + protected function mergeGroupAttributesIntoRoute($route) + { + $action = $this->mergeWithLastGroup($route->getAction()); + + $route->setAction($action); + } + + /** + * Determine if the action is routing to a controller. + * + * @param array $action + * @return bool + */ + protected function actionReferencesController($action) + { + if ($action instanceof Closure) { + return false; + } + + return is_string($action) || is_string(isset($action['uses']) ? $action['uses'] : null); + } + + /** + * Add a controller based route action to the action array. + * + * @param array|string $action + * @return array + */ + protected function convertToControllerAction($action) + { + if (is_string($action)) { + $action = ['uses' => $action]; + } + + // Here we'll merge any group "uses" statement if necessary so that the action + // has the proper clause for this property. Then we can simply set the name + // of the controller on the action and return the action array for usage. + if (! empty($this->groupStack)) { + $action['uses'] = $this->prependGroupUses($action['uses']); + } + + // Here we will set this controller name on the action array just so we always + // have a copy of it for reference if we need it. This can be used while we + // search for a controller name or do some other type of fetch operation. + $action['controller'] = $action['uses']; + + return $action; + } + + /** + * Prepend the last group uses onto the use clause. + * + * @param string $uses + * @return string + */ + protected function prependGroupUses($uses) + { + $group = end($this->groupStack); + + return isset($group['namespace']) && strpos($uses, '\\') !== 0 ? $group['namespace'].'\\'.$uses : $uses; + } + + /** + * Dispatch the request to the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function dispatch(Request $request) + { + $this->currentRequest = $request; + + // If no response was returned from the before filter, we will call the proper + // route instance to get the response. If no route is found a response will + // still get returned based on why no routes were found for this request. + $response = $this->callFilter('before', $request); + + if (is_null($response)) { + $response = $this->dispatchToRoute($request); + } + + // Once this route has run and the response has been prepared, we will run the + // after filter to do any last work on the response or for this application + // before we will return the response back to the consuming code for use. + $response = $this->prepareResponse($request, $response); + + $this->callFilter('after', $request, $response); + + return $response; + } + + /** + * Dispatch the request to a route and return the response. + * + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function dispatchToRoute(Request $request) + { + // First we will find a route that matches this request. We will also set the + // route resolver on the request so middlewares assigned to the route will + // receive access to this route instance for checking of the parameters. + $route = $this->findRoute($request); + + $request->setRouteResolver(function () use ($route) { + return $route; + }); + + $this->events->fire('router.matched', [$route, $request]); + + // Once we have successfully matched the incoming request to a given route we + // can call the before filters on that route. This works similar to global + // filters in that if a response is returned we will not call the route. + $response = $this->callRouteBefore($route, $request); + + if (is_null($response)) { + $response = $this->runRouteWithinStack( + $route, $request + ); + } + + $response = $this->prepareResponse($request, $response); + + // After we have a prepared response from the route or filter we will call to + // the "after" filters to do any last minute processing on this request or + // response object before the response is returned back to the consumer. + $this->callRouteAfter($route, $request, $response); + + return $response; + } + + /** + * Run the given route within a Stack "onion" instance. + * + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @return mixed + */ + protected function runRouteWithinStack(Route $route, Request $request) + { + $shouldSkipMiddleware = $this->container->bound('middleware.disable') && + $this->container->make('middleware.disable') === true; + + $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddlewares($route); + + return (new Pipeline($this->container)) + ->send($request) + ->through($middleware) + ->then(function ($request) use ($route) { + return $this->prepareResponse( + $request, + $route->run($request) + ); + }); + } + + /** + * Gather the middleware for the given route. + * + * @param \Illuminate\Routing\Route $route + * @return array + */ + public function gatherRouteMiddlewares(Route $route) + { + return Collection::make($route->middleware())->map(function ($name) { + return Collection::make($this->resolveMiddlewareClassName($name)); + }) + ->collapse()->all(); + } + + /** + * Resolve the middleware name to a class name preserving passed parameters. + * + * @param string $name + * @return string + */ + public function resolveMiddlewareClassName($name) + { + $map = $this->middleware; + + list($name, $parameters) = array_pad(explode(':', $name, 2), 2, null); + + return (isset($map[$name]) ? $map[$name] : $name).($parameters !== null ? ':'.$parameters : ''); + } + + /** + * Find the route matching a given request. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Routing\Route + */ + protected function findRoute($request) + { + $this->current = $route = $this->routes->match($request); + + $this->container->instance('Illuminate\Routing\Route', $route); + + return $this->substituteBindings($route); + } + + /** + * Substitute the route bindings onto the route. + * + * @param \Illuminate\Routing\Route $route + * @return \Illuminate\Routing\Route + */ + protected function substituteBindings($route) + { + foreach ($route->parameters() as $key => $value) { + if (isset($this->binders[$key])) { + $route->setParameter($key, $this->performBinding($key, $value, $route)); + } + } + + return $route; + } + + /** + * Call the binding callback for the given key. + * + * @param string $key + * @param string $value + * @param \Illuminate\Routing\Route $route + * @return mixed + */ + protected function performBinding($key, $value, $route) + { + return call_user_func($this->binders[$key], $value, $route); + } + + /** + * Register a route matched event listener. + * + * @param string|callable $callback + * @return void + */ + public function matched($callback) + { + $this->events->listen('router.matched', $callback); + } + + /** + * Register a new "before" filter with the router. + * + * @param string|callable $callback + * @return void + * + * @deprecated since version 5.1. + */ + public function before($callback) + { + $this->addGlobalFilter('before', $callback); + } + + /** + * Register a new "after" filter with the router. + * + * @param string|callable $callback + * @return void + * + * @deprecated since version 5.1. + */ + public function after($callback) + { + $this->addGlobalFilter('after', $callback); + } + + /** + * Register a new global filter with the router. + * + * @param string $filter + * @param string|callable $callback + * @return void + */ + protected function addGlobalFilter($filter, $callback) + { + $this->events->listen('router.'.$filter, $this->parseFilter($callback)); + } + + /** + * Get all of the defined middleware short-hand names. + * + * @return array + */ + public function getMiddleware() + { + return $this->middleware; + } + + /** + * Register a short-hand name for a middleware. + * + * @param string $name + * @param string $class + * @return $this + */ + public function middleware($name, $class) + { + $this->middleware[$name] = $class; + + return $this; + } + + /** + * Register a new filter with the router. + * + * @param string $name + * @param string|callable $callback + * @return void + * + * @deprecated since version 5.1. + */ + public function filter($name, $callback) + { + $this->events->listen('router.filter: '.$name, $this->parseFilter($callback)); + } + + /** + * Parse the registered filter. + * + * @param callable|string $callback + * @return mixed + */ + protected function parseFilter($callback) + { + if (is_string($callback) && ! Str::contains($callback, '@')) { + return $callback.'@filter'; + } + + return $callback; + } + + /** + * Register a pattern-based filter with the router. + * + * @param string $pattern + * @param string $name + * @param array|null $methods + * @return void + * + * @deprecated since version 5.1. + */ + public function when($pattern, $name, $methods = null) + { + if (! is_null($methods)) { + $methods = array_map('strtoupper', (array) $methods); + } + + $this->patternFilters[$pattern][] = compact('name', 'methods'); + } + + /** + * Register a regular expression based filter with the router. + * + * @param string $pattern + * @param string $name + * @param array|null $methods + * @return void + * + * @deprecated since version 5.1. + */ + public function whenRegex($pattern, $name, $methods = null) + { + if (! is_null($methods)) { + $methods = array_map('strtoupper', (array) $methods); + } + + $this->regexFilters[$pattern][] = compact('name', 'methods'); + } + + /** + * Register a model binder for a wildcard. + * + * @param string $key + * @param string $class + * @param \Closure|null $callback + * @return void + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function model($key, $class, Closure $callback = null) + { + $this->bind($key, function ($value) use ($class, $callback) { + if (is_null($value)) { + return; + } + + // For model binders, we will attempt to retrieve the models using the first + // method on the model instance. If we cannot retrieve the models we'll + // throw a not found exception otherwise we will return the instance. + $instance = $this->container->make($class); + + if ($model = $instance->where($instance->getRouteKeyName(), $value)->first()) { + return $model; + } + + // If a callback was supplied to the method we will call that to determine + // what we should do when the model is not found. This just gives these + // developer a little greater flexibility to decide what will happen. + if ($callback instanceof Closure) { + return call_user_func($callback, $value); + } + + throw new NotFoundHttpException; + }); + } + + /** + * Add a new route parameter binder. + * + * @param string $key + * @param string|callable $binder + * @return void + */ + public function bind($key, $binder) + { + if (is_string($binder)) { + $binder = $this->createClassBinding($binder); + } + + $this->binders[str_replace('-', '_', $key)] = $binder; + } + + /** + * Create a class based binding using the IoC container. + * + * @param string $binding + * @return \Closure + */ + public function createClassBinding($binding) + { + return function ($value, $route) use ($binding) { + // If the binding has an @ sign, we will assume it's being used to delimit + // the class name from the bind method name. This allows for bindings + // to run multiple bind methods in a single class for convenience. + $segments = explode('@', $binding); + + $method = count($segments) == 2 ? $segments[1] : 'bind'; + + $callable = [$this->container->make($segments[0]), $method]; + + return call_user_func($callable, $value, $route); + }; + } + + /** + * Set a global where pattern on all routes. + * + * @param string $key + * @param string $pattern + * @return void + */ + public function pattern($key, $pattern) + { + $this->patterns[$key] = $pattern; + } + + /** + * Set a group of global where patterns on all routes. + * + * @param array $patterns + * @return void + */ + public function patterns($patterns) + { + foreach ($patterns as $key => $pattern) { + $this->pattern($key, $pattern); + } + } + + /** + * Call the given filter with the request and response. + * + * @param string $filter + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return mixed + */ + protected function callFilter($filter, $request, $response = null) + { + return $this->events->until('router.'.$filter, [$request, $response]); + } + + /** + * Call the given route's before filters. + * + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function callRouteBefore($route, $request) + { + $response = $this->callPatternFilters($route, $request); + + return $response ?: $this->callAttachedBefores($route, $request); + } + + /** + * Call the pattern based filters for the request. + * + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @return mixed + */ + protected function callPatternFilters($route, $request) + { + foreach ($this->findPatternFilters($request) as $filter => $parameters) { + $response = $this->callRouteFilter($filter, $parameters, $route, $request); + + if (! is_null($response)) { + return $response; + } + } + } + + /** + * Find the patterned filters matching a request. + * + * @param \Illuminate\Http\Request $request + * @return array + * + * @deprecated since version 5.1. + */ + public function findPatternFilters($request) + { + $results = []; + + list($path, $method) = [$request->path(), $request->getMethod()]; + + foreach ($this->patternFilters as $pattern => $filters) { + // To find the patterned middlewares for a request, we just need to check these + // registered patterns against the path info for the current request to this + // applications, and when it matches we will merge into these middlewares. + if (Str::is($pattern, $path)) { + $merge = $this->patternsByMethod($method, $filters); + + $results = array_merge($results, $merge); + } + } + + foreach ($this->regexFilters as $pattern => $filters) { + // To find the patterned middlewares for a request, we just need to check these + // registered patterns against the path info for the current request to this + // applications, and when it matches we will merge into these middlewares. + if (preg_match($pattern, $path)) { + $merge = $this->patternsByMethod($method, $filters); + + $results = array_merge($results, $merge); + } + } + + return $results; + } + + /** + * Filter pattern filters that don't apply to the request verb. + * + * @param string $method + * @param array $filters + * @return array + */ + protected function patternsByMethod($method, $filters) + { + $results = []; + + foreach ($filters as $filter) { + // The idea here is to check and see if the pattern filter applies to this HTTP + // request based on the request methods. Pattern filters might be limited by + // the request verb to make it simply to assign to the given verb at once. + if ($this->filterSupportsMethod($filter, $method)) { + $parsed = Route::parseFilters($filter['name']); + + $results = array_merge($results, $parsed); + } + } + + return $results; + } + + /** + * Determine if the given pattern filters applies to a given method. + * + * @param array $filter + * @param array $method + * @return bool + */ + protected function filterSupportsMethod($filter, $method) + { + $methods = $filter['methods']; + + return is_null($methods) || in_array($method, $methods); + } + + /** + * Call the given route's before (non-pattern) filters. + * + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @return mixed + */ + protected function callAttachedBefores($route, $request) + { + foreach ($route->beforeFilters() as $filter => $parameters) { + $response = $this->callRouteFilter($filter, $parameters, $route, $request); + + if (! is_null($response)) { + return $response; + } + } + } + + /** + * Call the given route's after filters. + * + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return mixed + * + * @deprecated since version 5.1. + */ + public function callRouteAfter($route, $request, $response) + { + foreach ($route->afterFilters() as $filter => $parameters) { + $this->callRouteFilter($filter, $parameters, $route, $request, $response); + } + } + + /** + * Call the given route filter. + * + * @param string $filter + * @param array $parameters + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response|null $response + * @return mixed + * + * @deprecated since version 5.1. + */ + public function callRouteFilter($filter, $parameters, $route, $request, $response = null) + { + $data = array_merge([$route, $request, $response], $parameters); + + return $this->events->until('router.filter: '.$filter, $this->cleanFilterParameters($data)); + } + + /** + * Clean the parameters being passed to a filter callback. + * + * @param array $parameters + * @return array + */ + protected function cleanFilterParameters(array $parameters) + { + return array_filter($parameters, function ($p) { + return ! is_null($p) && $p !== ''; + }); + } + + /** + * Create a response instance from the given value. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param mixed $response + * @return \Illuminate\Http\Response + */ + public function prepareResponse($request, $response) + { + if ($response instanceof PsrResponseInterface) { + $response = (new HttpFoundationFactory)->createResponse($response); + } elseif (! $response instanceof SymfonyResponse) { + $response = new Response($response); + } + + return $response->prepare($request); + } + + /** + * Determine if the router currently has a group stack. + * + * @return bool + */ + public function hasGroupStack() + { + return ! empty($this->groupStack); + } + + /** + * Get the current group stack for the router. + * + * @return array + */ + public function getGroupStack() + { + return $this->groupStack; + } + + /** + * Get a route parameter for the current route. + * + * @param string $key + * @param string $default + * @return mixed + */ + public function input($key, $default = null) + { + return $this->current()->parameter($key, $default); + } + + /** + * Get the currently dispatched route instance. + * + * @return \Illuminate\Routing\Route + */ + public function getCurrentRoute() + { + return $this->current(); + } + + /** + * Get the currently dispatched route instance. + * + * @return \Illuminate\Routing\Route + */ + public function current() + { + return $this->current; + } + + /** + * Check if a route with the given name exists. + * + * @param string $name + * @return bool + */ + public function has($name) + { + return $this->routes->hasNamedRoute($name); + } + + /** + * Get the current route name. + * + * @return string|null + */ + public function currentRouteName() + { + return $this->current() ? $this->current()->getName() : null; + } + + /** + * Alias for the "currentRouteNamed" method. + * + * @param mixed string + * @return bool + */ + public function is() + { + foreach (func_get_args() as $pattern) { + if (Str::is($pattern, $this->currentRouteName())) { + return true; + } + } + + return false; + } + + /** + * Determine if the current route matches a given name. + * + * @param string $name + * @return bool + */ + public function currentRouteNamed($name) + { + return $this->current() ? $this->current()->getName() == $name : false; + } + + /** + * Get the current route action. + * + * @return string|null + */ + public function currentRouteAction() + { + if (! $this->current()) { + return; + } + + $action = $this->current()->getAction(); + + return isset($action['controller']) ? $action['controller'] : null; + } + + /** + * Alias for the "currentRouteUses" method. + * + * @param mixed string + * @return bool + */ + public function uses() + { + foreach (func_get_args() as $pattern) { + if (Str::is($pattern, $this->currentRouteAction())) { + return true; + } + } + + return false; + } + + /** + * Determine if the current route action matches a given action. + * + * @param string $action + * @return bool + */ + public function currentRouteUses($action) + { + return $this->currentRouteAction() == $action; + } + + /** + * Get the request currently being dispatched. + * + * @return \Illuminate\Http\Request + */ + public function getCurrentRequest() + { + return $this->currentRequest; + } + + /** + * Get the underlying route collection. + * + * @return \Illuminate\Routing\RouteCollection + */ + public function getRoutes() + { + return $this->routes; + } + + /** + * Set the route collection instance. + * + * @param \Illuminate\Routing\RouteCollection $routes + * @return void + */ + public function setRoutes(RouteCollection $routes) + { + foreach ($routes as $route) { + $route->setContainer($this->container); + } + + $this->routes = $routes; + + $this->container->instance('routes', $this->routes); + } + + /** + * Get the global "where" patterns. + * + * @return array + */ + public function getPatterns() + { + return $this->patterns; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php new file mode 100644 index 0000000..e8e7bfb --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php @@ -0,0 +1,147 @@ +registerRouter(); + + $this->registerUrlGenerator(); + + $this->registerRedirector(); + + $this->registerPsrRequest(); + + $this->registerPsrResponse(); + + $this->registerResponseFactory(); + } + + /** + * Register the router instance. + * + * @return void + */ + protected function registerRouter() + { + $this->app['router'] = $this->app->share(function ($app) { + return new Router($app['events'], $app); + }); + } + + /** + * Register the URL generator service. + * + * @return void + */ + protected function registerUrlGenerator() + { + $this->app['url'] = $this->app->share(function ($app) { + $routes = $app['router']->getRoutes(); + + // The URL generator needs the route collection that exists on the router. + // Keep in mind this is an object, so we're passing by references here + // and all the registered routes will be available to the generator. + $app->instance('routes', $routes); + + $url = new UrlGenerator( + $routes, $app->rebinding( + 'request', $this->requestRebinder() + ) + ); + + $url->setSessionResolver(function () { + return $this->app['session']; + }); + + // If the route collection is "rebound", for example, when the routes stay + // cached for the application, we will need to rebind the routes on the + // URL generator instance so it has the latest version of the routes. + $app->rebinding('routes', function ($app, $routes) { + $app['url']->setRoutes($routes); + }); + + return $url; + }); + } + + /** + * Get the URL generator request rebinder. + * + * @return \Closure + */ + protected function requestRebinder() + { + return function ($app, $request) { + $app['url']->setRequest($request); + }; + } + + /** + * Register the Redirector service. + * + * @return void + */ + protected function registerRedirector() + { + $this->app['redirect'] = $this->app->share(function ($app) { + $redirector = new Redirector($app['url']); + + // If the session is set on the application instance, we'll inject it into + // the redirector instance. This allows the redirect responses to allow + // for the quite convenient "with" methods that flash to the session. + if (isset($app['session.store'])) { + $redirector->setSession($app['session.store']); + } + + return $redirector; + }); + } + + /** + * Register a binding for the PSR-7 request implementation. + * + * @return void + */ + protected function registerPsrRequest() + { + $this->app->bind('Psr\Http\Message\ServerRequestInterface', function ($app) { + return (new DiactorosFactory)->createRequest($app->make('request')); + }); + } + + /** + * Register a binding for the PSR-7 response implementation. + * + * @return void + */ + protected function registerPsrResponse() + { + $this->app->bind('Psr\Http\Message\ResponseInterface', function ($app) { + return new PsrResponse(); + }); + } + + /** + * Register the response factory implementation. + * + * @return void + */ + protected function registerResponseFactory() + { + $this->app->singleton('Illuminate\Contracts\Routing\ResponseFactory', function ($app) { + return new ResponseFactory($app['Illuminate\Contracts\View\Factory'], $app['redirect']); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php b/core/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php new file mode 100644 index 0000000..c09c010 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php @@ -0,0 +1,740 @@ + '/', + '%40' => '@', + '%3A' => ':', + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', + '%3F' => '?', + '%26' => '&', + '%23' => '#', + '%25' => '%', + ]; + + /** + * Create a new URL Generator instance. + * + * @param \Illuminate\Routing\RouteCollection $routes + * @param \Illuminate\Http\Request $request + * @return void + */ + public function __construct(RouteCollection $routes, Request $request) + { + $this->routes = $routes; + + $this->setRequest($request); + } + + /** + * Get the full URL for the current request. + * + * @return string + */ + public function full() + { + return $this->request->fullUrl(); + } + + /** + * Get the current URL for the request. + * + * @return string + */ + public function current() + { + return $this->to($this->request->getPathInfo()); + } + + /** + * Get the URL for the previous request. + * + * @return string + */ + public function previous() + { + $referrer = $this->request->headers->get('referer'); + + $url = $referrer ? $this->to($referrer) : $this->getPreviousUrlFromSession(); + + return $url ?: $this->to('/'); + } + + /** + * Generate an absolute URL to the given path. + * + * @param string $path + * @param mixed $extra + * @param bool|null $secure + * @return string + */ + public function to($path, $extra = [], $secure = null) + { + // First we will check if the URL is already a valid URL. If it is we will not + // try to generate a new one but will simply return the URL as is, which is + // convenient since developers do not always have to check if it's valid. + if ($this->isValidUrl($path)) { + return $path; + } + + $scheme = $this->getScheme($secure); + + $extra = $this->formatParameters($extra); + + $tail = implode('/', array_map( + 'rawurlencode', (array) $extra) + ); + + // Once we have the scheme we will compile the "tail" by collapsing the values + // into a single string delimited by slashes. This just makes it convenient + // for passing the array of parameters to this URL as a list of segments. + $root = $this->getRootUrl($scheme); + + if (($queryPosition = strpos($path, '?')) !== false) { + $query = substr($path, $queryPosition); + $path = substr($path, 0, $queryPosition); + } else { + $query = ''; + } + + return $this->trimUrl($root, $path, $tail).$query; + } + + /** + * Generate a secure, absolute URL to the given path. + * + * @param string $path + * @param array $parameters + * @return string + */ + public function secure($path, $parameters = []) + { + return $this->to($path, $parameters, true); + } + + /** + * Generate a URL to an application asset. + * + * @param string $path + * @param bool|null $secure + * @return string + */ + public function asset($path, $secure = null) + { + if ($this->isValidUrl($path)) { + return $path; + } + + // Once we get the root URL, we will check to see if it contains an index.php + // file in the paths. If it does, we will remove it since it is not needed + // for asset paths, but only for routes to endpoints in the application. + $root = $this->getRootUrl($this->getScheme($secure)); + + return $this->removeIndex($root).'/'.trim($path, '/'); + } + + /** + * Generate a URL to an asset from a custom root domain such as CDN, etc. + * + * @param string $root + * @param string $path + * @param bool|null $secure + * @return string + */ + public function assetFrom($root, $path, $secure = null) + { + // Once we get the root URL, we will check to see if it contains an index.php + // file in the paths. If it does, we will remove it since it is not needed + // for asset paths, but only for routes to endpoints in the application. + $root = $this->getRootUrl($this->getScheme($secure), $root); + + return $this->removeIndex($root).'/'.trim($path, '/'); + } + + /** + * Remove the index.php file from a path. + * + * @param string $root + * @return string + */ + protected function removeIndex($root) + { + $i = 'index.php'; + + return Str::contains($root, $i) ? str_replace('/'.$i, '', $root) : $root; + } + + /** + * Generate a URL to a secure asset. + * + * @param string $path + * @return string + */ + public function secureAsset($path) + { + return $this->asset($path, true); + } + + /** + * Get the scheme for a raw URL. + * + * @param bool|null $secure + * @return string + */ + protected function getScheme($secure) + { + if (is_null($secure)) { + if (is_null($this->cachedSchema)) { + $this->cachedSchema = $this->forceSchema ?: $this->request->getScheme().'://'; + } + + return $this->cachedSchema; + } + + return $secure ? 'https://' : 'http://'; + } + + /** + * Force the schema for URLs. + * + * @param string $schema + * @return void + */ + public function forceSchema($schema) + { + $this->cachedSchema = null; + + $this->forceSchema = $schema.'://'; + } + + /** + * Get the URL to a named route. + * + * @param string $name + * @param mixed $parameters + * @param bool $absolute + * @return string + * + * @throws \InvalidArgumentException + */ + public function route($name, $parameters = [], $absolute = true) + { + if (! is_null($route = $this->routes->getByName($name))) { + return $this->toRoute($route, $parameters, $absolute); + } + + throw new InvalidArgumentException("Route [{$name}] not defined."); + } + + /** + * Get the URL for a given route instance. + * + * @param \Illuminate\Routing\Route $route + * @param mixed $parameters + * @param bool $absolute + * @return string + */ + protected function toRoute($route, $parameters, $absolute) + { + $parameters = $this->formatParameters($parameters); + + $domain = $this->getRouteDomain($route, $parameters); + + $uri = strtr(rawurlencode($this->addQueryString($this->trimUrl( + $root = $this->replaceRoot($route, $domain, $parameters), + $this->replaceRouteParameters($route->uri(), $parameters) + ), $parameters)), $this->dontEncode); + + return $absolute ? $uri : '/'.ltrim(str_replace($root, '', $uri), '/'); + } + + /** + * Replace the parameters on the root path. + * + * @param \Illuminate\Routing\Route $route + * @param string $domain + * @param array $parameters + * @return string + */ + protected function replaceRoot($route, $domain, &$parameters) + { + return $this->replaceRouteParameters($this->getRouteRoot($route, $domain), $parameters); + } + + /** + * Replace all of the wildcard parameters for a route path. + * + * @param string $path + * @param array $parameters + * @return string + */ + protected function replaceRouteParameters($path, array &$parameters) + { + if (count($parameters)) { + $path = preg_replace_sub( + '/\{.*?\}/', $parameters, $this->replaceNamedParameters($path, $parameters) + ); + } + + return trim(preg_replace('/\{.*?\?\}/', '', $path), '/'); + } + + /** + * Replace all of the named parameters in the path. + * + * @param string $path + * @param array $parameters + * @return string + */ + protected function replaceNamedParameters($path, &$parameters) + { + return preg_replace_callback('/\{(.*?)\??\}/', function ($m) use (&$parameters) { + return isset($parameters[$m[1]]) ? Arr::pull($parameters, $m[1]) : $m[0]; + }, $path); + } + + /** + * Add a query string to the URI. + * + * @param string $uri + * @param array $parameters + * @return mixed|string + */ + protected function addQueryString($uri, array $parameters) + { + // If the URI has a fragment, we will move it to the end of the URI since it will + // need to come after any query string that may be added to the URL else it is + // not going to be available. We will remove it then append it back on here. + if (! is_null($fragment = parse_url($uri, PHP_URL_FRAGMENT))) { + $uri = preg_replace('/#.*/', '', $uri); + } + + $uri .= $this->getRouteQueryString($parameters); + + return is_null($fragment) ? $uri : $uri."#{$fragment}"; + } + + /** + * Format the array of URL parameters. + * + * @param mixed|array $parameters + * @return array + */ + protected function formatParameters($parameters) + { + return $this->replaceRoutableParameters($parameters); + } + + /** + * Replace UrlRoutable parameters with their route parameter. + * + * @param array $parameters + * @return array + */ + protected function replaceRoutableParameters($parameters = []) + { + $parameters = is_array($parameters) ? $parameters : [$parameters]; + + foreach ($parameters as $key => $parameter) { + if ($parameter instanceof UrlRoutable) { + $parameters[$key] = $parameter->getRouteKey(); + } + } + + return $parameters; + } + + /** + * Get the query string for a given route. + * + * @param array $parameters + * @return string + */ + protected function getRouteQueryString(array $parameters) + { + // First we will get all of the string parameters that are remaining after we + // have replaced the route wildcards. We'll then build a query string from + // these string parameters then use it as a starting point for the rest. + if (count($parameters) == 0) { + return ''; + } + + $query = http_build_query( + $keyed = $this->getStringParameters($parameters) + ); + + // Lastly, if there are still parameters remaining, we will fetch the numeric + // parameters that are in the array and add them to the query string or we + // will make the initial query string if it wasn't started with strings. + if (count($keyed) < count($parameters)) { + $query .= '&'.implode( + '&', $this->getNumericParameters($parameters) + ); + } + + return '?'.trim($query, '&'); + } + + /** + * Get the string parameters from a given list. + * + * @param array $parameters + * @return array + */ + protected function getStringParameters(array $parameters) + { + return Arr::where($parameters, function ($k) { + return is_string($k); + }); + } + + /** + * Get the numeric parameters from a given list. + * + * @param array $parameters + * @return array + */ + protected function getNumericParameters(array $parameters) + { + return Arr::where($parameters, function ($k) { + return is_numeric($k); + }); + } + + /** + * Get the formatted domain for a given route. + * + * @param \Illuminate\Routing\Route $route + * @param array $parameters + * @return string + */ + protected function getRouteDomain($route, &$parameters) + { + return $route->domain() ? $this->formatDomain($route, $parameters) : null; + } + + /** + * Format the domain and port for the route and request. + * + * @param \Illuminate\Routing\Route $route + * @param array $parameters + * @return string + */ + protected function formatDomain($route, &$parameters) + { + return $this->addPortToDomain($this->getDomainAndScheme($route)); + } + + /** + * Get the domain and scheme for the route. + * + * @param \Illuminate\Routing\Route $route + * @return string + */ + protected function getDomainAndScheme($route) + { + return $this->getRouteScheme($route).$route->domain(); + } + + /** + * Add the port to the domain if necessary. + * + * @param string $domain + * @return string + */ + protected function addPortToDomain($domain) + { + $secure = $this->request->isSecure(); + + $port = (int) $this->request->getPort(); + + if (($secure && $port === 443) || (! $secure && $port === 80)) { + return $domain; + } + + return $domain.':'.$port; + } + + /** + * Get the root of the route URL. + * + * @param \Illuminate\Routing\Route $route + * @param string $domain + * @return string + */ + protected function getRouteRoot($route, $domain) + { + return $this->getRootUrl($this->getRouteScheme($route), $domain); + } + + /** + * Get the scheme for the given route. + * + * @param \Illuminate\Routing\Route $route + * @return string + */ + protected function getRouteScheme($route) + { + if ($route->httpOnly()) { + return $this->getScheme(false); + } elseif ($route->httpsOnly()) { + return $this->getScheme(true); + } + + return $this->getScheme(null); + } + + /** + * Get the URL to a controller action. + * + * @param string $action + * @param mixed $parameters + * @param bool $absolute + * @return string + * + * @throws \InvalidArgumentException + */ + public function action($action, $parameters = [], $absolute = true) + { + if ($this->rootNamespace && ! (strpos($action, '\\') === 0)) { + $action = $this->rootNamespace.'\\'.$action; + } else { + $action = trim($action, '\\'); + } + + if (! is_null($route = $this->routes->getByAction($action))) { + return $this->toRoute($route, $parameters, $absolute); + } + + throw new InvalidArgumentException("Action {$action} not defined."); + } + + /** + * Get the base URL for the request. + * + * @param string $scheme + * @param string $root + * @return string + */ + protected function getRootUrl($scheme, $root = null) + { + if (is_null($root)) { + if (is_null($this->cachedRoot)) { + $this->cachedRoot = $this->forcedRoot ?: $this->request->root(); + } + + $root = $this->cachedRoot; + } + + $start = Str::startsWith($root, 'http://') ? 'http://' : 'https://'; + + return preg_replace('~'.$start.'~', $scheme, $root, 1); + } + + /** + * Set the forced root URL. + * + * @param string $root + * @return void + */ + public function forceRootUrl($root) + { + $this->forcedRoot = rtrim($root, '/'); + $this->cachedRoot = null; + } + + /** + * Determine if the given path is a valid URL. + * + * @param string $path + * @return bool + */ + public function isValidUrl($path) + { + if (Str::startsWith($path, ['#', '//', 'mailto:', 'tel:', 'http://', 'https://'])) { + return true; + } + + return filter_var($path, FILTER_VALIDATE_URL) !== false; + } + + /** + * Format the given URL segments into a single URL. + * + * @param string $root + * @param string $path + * @param string $tail + * @return string + */ + protected function trimUrl($root, $path, $tail = '') + { + return trim($root.'/'.trim($path.'/'.$tail, '/'), '/'); + } + + /** + * Get the request instance. + * + * @return \Illuminate\Http\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Set the current request instance. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + + $this->cachedRoot = null; + $this->cachedSchema = null; + } + + /** + * Set the route collection. + * + * @param \Illuminate\Routing\RouteCollection $routes + * @return $this + */ + public function setRoutes(RouteCollection $routes) + { + $this->routes = $routes; + + return $this; + } + + /** + * Get the previous URL from the session if possible. + * + * @return string|null + */ + protected function getPreviousUrlFromSession() + { + $session = $this->getSession(); + + return $session ? $session->previousUrl() : null; + } + + /** + * Get the session implementation from the resolver. + * + * @return \Illuminate\Session\Store|null + */ + protected function getSession() + { + if ($this->sessionResolver) { + return call_user_func($this->sessionResolver); + } + } + + /** + * Set the session resolver for the generator. + * + * @param callable $sessionResolver + * @return $this + */ + public function setSessionResolver(callable $sessionResolver) + { + $this->sessionResolver = $sessionResolver; + + return $this; + } + + /** + * Set the root controller namespace. + * + * @param string $rootNamespace + * @return $this + */ + public function setRootControllerNamespace($rootNamespace) + { + $this->rootNamespace = $rootNamespace; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Routing/composer.json b/core/vendor/laravel/framework/src/Illuminate/Routing/composer.json new file mode 100644 index 0000000..af0a63b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Routing/composer.json @@ -0,0 +1,43 @@ +{ + "name": "illuminate/routing", + "description": "The Illuminate Routing package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/container": "5.1.*", + "illuminate/contracts": "5.1.*", + "illuminate/http": "5.1.*", + "illuminate/pipeline": "5.1.*", + "illuminate/session": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/http-foundation": "2.7.*", + "symfony/http-kernel": "2.7.*", + "symfony/routing": "2.7.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Routing\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "illuminate/console": "Required to use the make commands (5.1.*).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/CacheBasedSessionHandler.php b/core/vendor/laravel/framework/src/Illuminate/Session/CacheBasedSessionHandler.php new file mode 100644 index 0000000..a2990bd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/CacheBasedSessionHandler.php @@ -0,0 +1,94 @@ +cache = $cache; + $this->minutes = $minutes; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return $this->cache->get($sessionId, ''); + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return $this->cache->put($sessionId, $data, $this->minutes); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return $this->cache->forget($sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($lifetime) + { + return true; + } + + /** + * Get the underlying cache repository. + * + * @return \Illuminate\Contracts\Cache\Repository + */ + public function getCache() + { + return $this->cache; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/CommandsServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Session/CommandsServiceProvider.php new file mode 100644 index 0000000..9514c4f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/CommandsServiceProvider.php @@ -0,0 +1,40 @@ +app->singleton('command.session.database', function ($app) { + return new SessionTableCommand($app['files'], $app['composer']); + }); + + $this->commands('command.session.database'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['command.session.database']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/Console/SessionTableCommand.php b/core/vendor/laravel/framework/src/Illuminate/Session/Console/SessionTableCommand.php new file mode 100644 index 0000000..2290d3c --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/Console/SessionTableCommand.php @@ -0,0 +1,81 @@ +files = $files; + $this->composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $fullPath = $this->createBaseMigration(); + + $this->files->put($fullPath, $this->files->get(__DIR__.'/stubs/database.stub')); + + $this->info('Migration created successfully!'); + + $this->composer->dumpAutoloads(); + } + + /** + * Create a base migration file for the session. + * + * @return string + */ + protected function createBaseMigration() + { + $name = 'create_sessions_table'; + + $path = $this->laravel->databasePath().'/migrations'; + + return $this->laravel['migration.creator']->create($name, $path); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub b/core/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub new file mode 100644 index 0000000..0529755 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub @@ -0,0 +1,31 @@ +string('id')->unique(); + $table->text('payload'); + $table->integer('last_activity'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('sessions'); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/CookieSessionHandler.php b/core/vendor/laravel/framework/src/Illuminate/Session/CookieSessionHandler.php new file mode 100644 index 0000000..0a9bac6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/CookieSessionHandler.php @@ -0,0 +1,108 @@ +cookie = $cookie; + $this->minutes = $minutes; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $value = $this->request->cookies->get($sessionId) ?: ''; + + if (! is_null($decoded = json_decode($value, true)) && is_array($decoded)) { + if (isset($decoded['expires']) && time() <= $decoded['expires']) { + return $decoded['data']; + } + } + + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $this->cookie->queue($sessionId, json_encode([ + 'data' => $data, + 'expires' => Carbon::now()->addMinutes($this->minutes)->getTimestamp(), + ]), $this->minutes); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $this->cookie->queue($this->cookie->forget($sessionId)); + } + + /** + * {@inheritdoc} + */ + public function gc($lifetime) + { + return true; + } + + /** + * Set the request instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/DatabaseSessionHandler.php b/core/vendor/laravel/framework/src/Illuminate/Session/DatabaseSessionHandler.php new file mode 100644 index 0000000..d08d1a1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/DatabaseSessionHandler.php @@ -0,0 +1,177 @@ +table = $table; + $this->minutes = $minutes; + $this->connection = $connection; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $session = (object) $this->getQuery()->find($sessionId); + + if (isset($session->last_activity)) { + if ($session->last_activity < Carbon::now()->subMinutes($this->minutes)->getTimestamp()) { + $this->exists = true; + + return; + } + } + + if (isset($session->payload)) { + $this->exists = true; + + return base64_decode($session->payload); + } + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + if ($this->exists) { + $this->performUpdate($sessionId, $data); + } else { + $this->performInsert($sessionId, $data); + } + + $this->exists = true; + } + + /** + * Perform an insert operation on the session ID. + * + * @param string $sessionId + * @param string $data + * @return void + */ + protected function performInsert($sessionId, $data) + { + try { + return $this->getQuery()->insert([ + 'id' => $sessionId, 'payload' => base64_encode($data), 'last_activity' => time(), + ]); + } catch (QueryException $e) { + $this->performUpdate($sessionId, $data); + } + } + + /** + * Perform an update operation on the session ID. + * + * @param string $sessionId + * @param string $data + * @return int + */ + protected function performUpdate($sessionId, $data) + { + return $this->getQuery()->where('id', $sessionId)->update([ + 'payload' => base64_encode($data), 'last_activity' => time(), + ]); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $this->getQuery()->where('id', $sessionId)->delete(); + } + + /** + * {@inheritdoc} + */ + public function gc($lifetime) + { + $this->getQuery()->where('last_activity', '<=', time() - $lifetime)->delete(); + } + + /** + * Get a fresh query builder instance for the table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function getQuery() + { + return $this->connection->table($this->table); + } + + /** + * Set the existence state for the session. + * + * @param bool $value + * @return $this + */ + public function setExists($value) + { + $this->exists = $value; + + return $this; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/EncryptedStore.php b/core/vendor/laravel/framework/src/Illuminate/Session/EncryptedStore.php new file mode 100644 index 0000000..9811d97 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/EncryptedStore.php @@ -0,0 +1,69 @@ +encrypter = $encrypter; + + parent::__construct($name, $handler, $id); + } + + /** + * Prepare the raw string data from the session for unserialization. + * + * @param string $data + * @return string + */ + protected function prepareForUnserialize($data) + { + try { + return $this->encrypter->decrypt($data); + } catch (DecryptException $e) { + return json_encode([]); + } + } + + /** + * Prepare the serialized session data for storage. + * + * @param string $data + * @return string + */ + protected function prepareForStorage($data) + { + return $this->encrypter->encrypt($data); + } + + /** + * Get the encrypter instance. + * + * @return \Illuminate\Contracts\Encryption\Encrypter + */ + public function getEncrypter() + { + return $this->encrypter; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/ExistenceAwareInterface.php b/core/vendor/laravel/framework/src/Illuminate/Session/ExistenceAwareInterface.php new file mode 100644 index 0000000..4a6bd98 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/ExistenceAwareInterface.php @@ -0,0 +1,14 @@ +path = $path; + $this->files = $files; + $this->minutes = $minutes; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + if ($this->files->exists($path = $this->path.'/'.$sessionId)) { + if (filemtime($path) >= Carbon::now()->subMinutes($this->minutes)->getTimestamp()) { + return $this->files->get($path); + } + } + + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $this->files->put($this->path.'/'.$sessionId, $data, true); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $this->files->delete($this->path.'/'.$sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($lifetime) + { + $files = Finder::create() + ->in($this->path) + ->files() + ->ignoreDotFiles(true) + ->date('<= now - '.$lifetime.' seconds'); + + foreach ($files as $file) { + $this->files->delete($file->getRealPath()); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php b/core/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php new file mode 100644 index 0000000..5372024 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php @@ -0,0 +1,244 @@ +manager = $manager; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + $this->sessionHandled = true; + + // If a session driver has been configured, we will need to start the session here + // so that the data is ready for an application. Note that the Laravel sessions + // do not make use of PHP "native" sessions in any way since they are crappy. + if ($this->sessionConfigured()) { + $session = $this->startSession($request); + + $request->setSession($session); + } + + $response = $next($request); + + // Again, if the session has been configured we will need to close out the session + // so that the attributes may be persisted to some storage medium. We will also + // add the session identifier cookie to the application response headers now. + if ($this->sessionConfigured()) { + $this->storeCurrentUrl($request, $session); + + $this->collectGarbage($session); + + $this->addCookieToResponse($response, $session); + } + + return $response; + } + + /** + * Perform any final actions for the request lifecycle. + * + * @param \Illuminate\Http\Request $request + * @param \Symfony\Component\HttpFoundation\Response $response + * @return void + */ + public function terminate($request, $response) + { + if ($this->sessionHandled && $this->sessionConfigured() && ! $this->usingCookieSessions()) { + $this->manager->driver()->save(); + } + } + + /** + * Start the session for the given request. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Session\SessionInterface + */ + protected function startSession(Request $request) + { + with($session = $this->getSession($request))->setRequestOnHandler($request); + + $session->start(); + + return $session; + } + + /** + * Get the session implementation from the manager. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Session\SessionInterface + */ + public function getSession(Request $request) + { + $session = $this->manager->driver(); + + $session->setId($request->cookies->get($session->getName())); + + return $session; + } + + /** + * Store the current URL for the request if necessary. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Session\SessionInterface $session + * @return void + */ + protected function storeCurrentUrl(Request $request, $session) + { + if ($request->method() === 'GET' && $request->route() && ! $request->ajax()) { + $session->setPreviousUrl($request->fullUrl()); + } + } + + /** + * Remove the garbage from the session if necessary. + * + * @param \Illuminate\Session\SessionInterface $session + * @return void + */ + protected function collectGarbage(SessionInterface $session) + { + $config = $this->manager->getSessionConfig(); + + // Here we will see if this request hits the garbage collection lottery by hitting + // the odds needed to perform garbage collection on any given request. If we do + // hit it, we'll call this handler to let it delete all the expired sessions. + if ($this->configHitsLottery($config)) { + $session->getHandler()->gc($this->getSessionLifetimeInSeconds()); + } + } + + /** + * Determine if the configuration odds hit the lottery. + * + * @param array $config + * @return bool + */ + protected function configHitsLottery(array $config) + { + return mt_rand(1, $config['lottery'][1]) <= $config['lottery'][0]; + } + + /** + * Add the session cookie to the application response. + * + * @param \Symfony\Component\HttpFoundation\Response $response + * @param \Illuminate\Session\SessionInterface $session + * @return void + */ + protected function addCookieToResponse(Response $response, SessionInterface $session) + { + if ($this->usingCookieSessions()) { + $this->manager->driver()->save(); + } + + if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig())) { + $response->headers->setCookie(new Cookie( + $session->getName(), $session->getId(), $this->getCookieExpirationDate(), + $config['path'], $config['domain'], Arr::get($config, 'secure', false) + )); + } + } + + /** + * Get the session lifetime in seconds. + * + * @return int + */ + protected function getSessionLifetimeInSeconds() + { + return Arr::get($this->manager->getSessionConfig(), 'lifetime') * 60; + } + + /** + * Get the cookie lifetime in seconds. + * + * @return int + */ + protected function getCookieExpirationDate() + { + $config = $this->manager->getSessionConfig(); + + return $config['expire_on_close'] ? 0 : Carbon::now()->addMinutes($config['lifetime']); + } + + /** + * Determine if a session driver has been configured. + * + * @return bool + */ + protected function sessionConfigured() + { + return ! is_null(Arr::get($this->manager->getSessionConfig(), 'driver')); + } + + /** + * Determine if the configured session driver is persistent. + * + * @param array|null $config + * @return bool + */ + protected function sessionIsPersistent(array $config = null) + { + $config = $config ?: $this->manager->getSessionConfig(); + + return ! in_array($config['driver'], [null, 'array']); + } + + /** + * Determine if the session is using cookie sessions. + * + * @return bool + */ + protected function usingCookieSessions() + { + if (! $this->sessionConfigured()) { + return false; + } + + return $this->manager->driver()->getHandler() instanceof CookieSessionHandler; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/SessionInterface.php b/core/vendor/laravel/framework/src/Illuminate/Session/SessionInterface.php new file mode 100644 index 0000000..1d7f18d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/SessionInterface.php @@ -0,0 +1,31 @@ +buildSession(parent::callCustomCreator($driver)); + } + + /** + * Create an instance of the "array" session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createArrayDriver() + { + return $this->buildSession(new NullSessionHandler); + } + + /** + * Create an instance of the "cookie" session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createCookieDriver() + { + $lifetime = $this->app['config']['session.lifetime']; + + return $this->buildSession(new CookieSessionHandler($this->app['cookie'], $lifetime)); + } + + /** + * Create an instance of the file session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createFileDriver() + { + return $this->createNativeDriver(); + } + + /** + * Create an instance of the file session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createNativeDriver() + { + $path = $this->app['config']['session.files']; + + $lifetime = $this->app['config']['session.lifetime']; + + return $this->buildSession(new FileSessionHandler($this->app['files'], $path, $lifetime)); + } + + /** + * Create an instance of the database session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createDatabaseDriver() + { + $connection = $this->getDatabaseConnection(); + + $table = $this->app['config']['session.table']; + + $lifetime = $this->app['config']['session.lifetime']; + + return $this->buildSession(new DatabaseSessionHandler($connection, $table, $lifetime)); + } + + /** + * Get the database connection for the database driver. + * + * @return \Illuminate\Database\Connection + */ + protected function getDatabaseConnection() + { + $connection = $this->app['config']['session.connection']; + + return $this->app['db']->connection($connection); + } + + /** + * Create an instance of the APC session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createApcDriver() + { + return $this->createCacheBased('apc'); + } + + /** + * Create an instance of the Memcached session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createMemcachedDriver() + { + return $this->createCacheBased('memcached'); + } + + /** + * Create an instance of the Wincache session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createWincacheDriver() + { + return $this->createCacheBased('wincache'); + } + + /** + * Create an instance of the Redis session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createRedisDriver() + { + $handler = $this->createCacheHandler('redis'); + + $handler->getCache()->getStore()->setConnection($this->app['config']['session.connection']); + + return $this->buildSession($handler); + } + + /** + * Create an instance of a cache driven driver. + * + * @param string $driver + * @return \Illuminate\Session\Store + */ + protected function createCacheBased($driver) + { + return $this->buildSession($this->createCacheHandler($driver)); + } + + /** + * Create the cache based session handler instance. + * + * @param string $driver + * @return \Illuminate\Session\CacheBasedSessionHandler + */ + protected function createCacheHandler($driver) + { + $minutes = $this->app['config']['session.lifetime']; + + return new CacheBasedSessionHandler(clone $this->app['cache']->driver($driver), $minutes); + } + + /** + * Build the session instance. + * + * @param \SessionHandlerInterface $handler + * @return \Illuminate\Session\Store + */ + protected function buildSession($handler) + { + if ($this->app['config']['session.encrypt']) { + return new EncryptedStore( + $this->app['config']['session.cookie'], $handler, $this->app['encrypter'] + ); + } else { + return new Store($this->app['config']['session.cookie'], $handler); + } + } + + /** + * Get the session configuration. + * + * @return array + */ + public function getSessionConfig() + { + return $this->app['config']['session']; + } + + /** + * Get the default session driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['session.driver']; + } + + /** + * Set the default session driver name. + * + * @param string $name + * @return void + */ + public function setDefaultDriver($name) + { + $this->app['config']['session.driver'] = $name; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php new file mode 100644 index 0000000..bc49ebc --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php @@ -0,0 +1,51 @@ +registerSessionManager(); + + $this->registerSessionDriver(); + + $this->app->singleton('Illuminate\Session\Middleware\StartSession'); + } + + /** + * Register the session manager instance. + * + * @return void + */ + protected function registerSessionManager() + { + $this->app->singleton('session', function ($app) { + return new SessionManager($app); + }); + } + + /** + * Register the session driver instance. + * + * @return void + */ + protected function registerSessionDriver() + { + $this->app->singleton('session.store', function ($app) { + // First, we will create the session manager which is responsible for the + // creation of the various session drivers when they are needed by the + // application instance, and will resolve them on a lazy load basis. + $manager = $app['session']; + + return $manager->driver(); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/Store.php b/core/vendor/laravel/framework/src/Illuminate/Session/Store.php new file mode 100644 index 0000000..3419713 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/Store.php @@ -0,0 +1,699 @@ +setId($id); + $this->name = $name; + $this->handler = $handler; + $this->metaBag = new MetadataBag; + } + + /** + * {@inheritdoc} + */ + public function start() + { + $this->loadSession(); + + if (! $this->has('_token')) { + $this->regenerateToken(); + } + + return $this->started = true; + } + + /** + * Load the session data from the handler. + * + * @return void + */ + protected function loadSession() + { + $this->attributes = array_merge($this->attributes, $this->readFromHandler()); + + foreach (array_merge($this->bags, [$this->metaBag]) as $bag) { + $this->initializeLocalBag($bag); + + $bag->initialize($this->bagData[$bag->getStorageKey()]); + } + } + + /** + * Read the session data from the handler. + * + * @return array + */ + protected function readFromHandler() + { + $data = $this->handler->read($this->getId()); + + if ($data) { + $data = @unserialize($this->prepareForUnserialize($data)); + + if ($data !== false && $data !== null && is_array($data)) { + return $data; + } + } + + return []; + } + + /** + * Prepare the raw string data from the session for unserialization. + * + * @param string $data + * @return string + */ + protected function prepareForUnserialize($data) + { + return $data; + } + + /** + * Initialize a bag in storage if it doesn't exist. + * + * @param \Symfony\Component\HttpFoundation\Session\SessionBagInterface $bag + * @return void + */ + protected function initializeLocalBag($bag) + { + $this->bagData[$bag->getStorageKey()] = $this->pull($bag->getStorageKey(), []); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + if (! $this->isValidId($id)) { + $id = $this->generateSessionId(); + } + + $this->id = $id; + } + + /** + * Determine if this is a valid session ID. + * + * @param string $id + * @return bool + */ + public function isValidId($id) + { + return is_string($id) && preg_match('/^[a-f0-9]{40}$/', $id); + } + + /** + * Get a new, random session ID. + * + * @return string + */ + protected function generateSessionId() + { + return sha1(uniqid('', true).Str::random(25).microtime(true)); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function invalidate($lifetime = null) + { + $this->clear(); + + return $this->migrate(true, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function migrate($destroy = false, $lifetime = null) + { + if ($destroy) { + $this->handler->destroy($this->getId()); + } + + $this->setExists(false); + + $this->id = $this->generateSessionId(); + + return true; + } + + /** + * Generate a new session identifier. + * + * @param bool $destroy + * @return bool + */ + public function regenerate($destroy = false) + { + return $this->migrate($destroy); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->addBagDataToSession(); + + $this->ageFlashData(); + + $this->handler->write($this->getId(), $this->prepareForStorage(serialize($this->attributes))); + + $this->started = false; + } + + /** + * Prepare the serialized session data for storage. + * + * @param string $data + * @return string + */ + protected function prepareForStorage($data) + { + return $data; + } + + /** + * Merge all of the bag data into the session. + * + * @return void + */ + protected function addBagDataToSession() + { + foreach (array_merge($this->bags, [$this->metaBag]) as $bag) { + $key = $bag->getStorageKey(); + + if (isset($this->bagData[$key])) { + $this->put($key, $this->bagData[$key]); + } + } + } + + /** + * Age the flash data for the session. + * + * @return void + */ + public function ageFlashData() + { + $this->forget($this->get('flash.old', [])); + + $this->put('flash.old', $this->get('flash.new', [])); + + $this->put('flash.new', []); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return ! is_null($this->get($name)); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return Arr::get($this->attributes, $name, $default); + } + + /** + * Get the value of a given key and then forget it. + * + * @param string $key + * @param string $default + * @return mixed + */ + public function pull($key, $default = null) + { + return Arr::pull($this->attributes, $key, $default); + } + + /** + * Determine if the session contains old input. + * + * @param string $key + * @return bool + */ + public function hasOldInput($key = null) + { + $old = $this->getOldInput($key); + + return is_null($key) ? count($old) > 0 : ! is_null($old); + } + + /** + * Get the requested item from the flashed input array. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getOldInput($key = null, $default = null) + { + $input = $this->get('_old_input', []); + + // Input that is flashed to the session can be easily retrieved by the + // developer, making repopulating old forms and the like much more + // convenient, since the request's previous input is available. + return Arr::get($input, $key, $default); + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + Arr::set($this->attributes, $name, $value); + } + + /** + * Put a key / value pair or array of key / value pairs in the session. + * + * @param string|array $key + * @param mixed $value + * @return void + */ + public function put($key, $value = null) + { + if (! is_array($key)) { + $key = [$key => $value]; + } + + foreach ($key as $arrayKey => $arrayValue) { + $this->set($arrayKey, $arrayValue); + } + } + + /** + * Push a value onto a session array. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function push($key, $value) + { + $array = $this->get($key, []); + + $array[] = $value; + + $this->put($key, $array); + } + + /** + * Flash a key / value pair to the session. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function flash($key, $value) + { + $this->put($key, $value); + + $this->push('flash.new', $key); + + $this->removeFromOldFlashData([$key]); + } + + /** + * Flash a key / value pair to the session + * for immediate use. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function now($key, $value) + { + $this->put($key, $value); + + $this->push('flash.old', $key); + } + + /** + * Flash an input array to the session. + * + * @param array $value + * @return void + */ + public function flashInput(array $value) + { + $this->flash('_old_input', $value); + } + + /** + * Reflash all of the session flash data. + * + * @return void + */ + public function reflash() + { + $this->mergeNewFlashes($this->get('flash.old', [])); + + $this->put('flash.old', []); + } + + /** + * Reflash a subset of the current flash data. + * + * @param array|mixed $keys + * @return void + */ + public function keep($keys = null) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + $this->mergeNewFlashes($keys); + + $this->removeFromOldFlashData($keys); + } + + /** + * Merge new flash keys into the new flash array. + * + * @param array $keys + * @return void + */ + protected function mergeNewFlashes(array $keys) + { + $values = array_unique(array_merge($this->get('flash.new', []), $keys)); + + $this->put('flash.new', $values); + } + + /** + * Remove the given keys from the old flash data. + * + * @param array $keys + * @return void + */ + protected function removeFromOldFlashData(array $keys) + { + $this->put('flash.old', array_diff($this->get('flash.old', []), $keys)); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->put($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + return Arr::pull($this->attributes, $name); + } + + /** + * Remove one or many items from the session. + * + * @param string|array $keys + * @return void + */ + public function forget($keys) + { + Arr::forget($this->attributes, $keys); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->attributes = []; + + foreach ($this->bags as $bag) { + $bag->clear(); + } + } + + /** + * Remove all of the items from the session. + * + * @return void + */ + public function flush() + { + $this->clear(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getStorageKey()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + return Arr::get($this->bags, $name, function () { + throw new InvalidArgumentException('Bag not registered.'); + }); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + return $this->metaBag; + } + + /** + * Get the raw bag data array for a given bag. + * + * @param string $name + * @return array + */ + public function getBagData($name) + { + return Arr::get($this->bagData, $name, []); + } + + /** + * Get the CSRF token value. + * + * @return string + */ + public function token() + { + return $this->get('_token'); + } + + /** + * Get the CSRF token value. + * + * @return string + */ + public function getToken() + { + return $this->token(); + } + + /** + * Regenerate the CSRF token value. + * + * @return void + */ + public function regenerateToken() + { + $this->put('_token', Str::random(40)); + } + + /** + * Get the previous URL from the session. + * + * @return string|null + */ + public function previousUrl() + { + return $this->get('_previous.url'); + } + + /** + * Set the "previous" URL in the session. + * + * @param string $url + * @return void + */ + public function setPreviousUrl($url) + { + return $this->put('_previous.url', $url); + } + + /** + * Set the existence of the session on the handler if applicable. + * + * @param bool $value + * @return void + */ + public function setExists($value) + { + if ($this->handler instanceof ExistenceAwareInterface) { + $this->handler->setExists($value); + } + } + + /** + * Get the underlying session handler implementation. + * + * @return \SessionHandlerInterface + */ + public function getHandler() + { + return $this->handler; + } + + /** + * Determine if the session handler needs a request. + * + * @return bool + */ + public function handlerNeedsRequest() + { + return $this->handler instanceof CookieSessionHandler; + } + + /** + * Set the request on the handler instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return void + */ + public function setRequestOnHandler(Request $request) + { + if ($this->handlerNeedsRequest()) { + $this->handler->setRequest($request); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Session/TokenMismatchException.php b/core/vendor/laravel/framework/src/Illuminate/Session/TokenMismatchException.php new file mode 100644 index 0000000..98d99a1 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Session/TokenMismatchException.php @@ -0,0 +1,10 @@ +=5.5.9", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "nesbot/carbon": "~1.19", + "symfony/finder": "2.7.*", + "symfony/http-foundation": "2.7.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Session\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "illuminate/console": "Required to use the session:table command (5.1.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/AggregateServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Support/AggregateServiceProvider.php new file mode 100644 index 0000000..ca5f9a8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/AggregateServiceProvider.php @@ -0,0 +1,52 @@ +instances = []; + + foreach ($this->providers as $provider) { + $this->instances[] = $this->app->register($provider); + } + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + $provides = []; + + foreach ($this->providers as $provider) { + $instance = $this->app->resolveProviderClass($provider); + + $provides = array_merge($provides, $instance->provides()); + } + + return $provides; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Arr.php b/core/vendor/laravel/framework/src/Illuminate/Support/Arr.php new file mode 100644 index 0000000..93e9569 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Arr.php @@ -0,0 +1,486 @@ + $value) { + list($innerKey, $innerValue) = call_user_func($callback, $key, $value); + + $results[$innerKey] = $innerValue; + } + + return $results; + } + + /** + * Collapse an array of arrays into a single array. + * + * @param \ArrayAccess|array $array + * @return array + */ + public static function collapse($array) + { + $results = []; + + foreach ($array as $values) { + if ($values instanceof Collection) { + $values = $values->all(); + } + + $results = array_merge($results, $values); + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + public static function divide($array) + { + return [array_keys($array), array_values($array)]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value)) { + $results = array_merge($results, static::dot($value, $prepend.$key.'.')); + } else { + $results[$prepend.$key] = $value; + } + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of items. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function except($array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Fetch a flattened array of a nested array element. + * + * @param array $array + * @param string $key + * @return array + * + * @deprecated since version 5.1. Use pluck instead. + */ + public static function fetch($array, $key) + { + foreach (explode('.', $key) as $segment) { + $results = []; + + foreach ($array as $value) { + if (array_key_exists($segment, $value = (array) $value)) { + $results[] = $value[$segment]; + } + } + + $array = array_values($results); + } + + return array_values($results); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, callable $callback, $default = null) + { + foreach ($array as $key => $value) { + if (call_user_func($callback, $key, $value)) { + return $value; + } + } + + return value($default); + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, callable $callback, $default = null) + { + return static::first(array_reverse($array), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @return array + */ + public static function flatten($array) + { + $return = []; + + array_walk_recursive($array, function ($x) use (&$return) { + $return[] = $x; + }); + + return $return; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + * @return void + */ + public static function forget(&$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (count($keys) === 0) { + return; + } + + foreach ($keys as $key) { + $parts = explode('.', $key); + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + $parts = []; + } + } + + unset($array[array_shift($parts)]); + + // clean up after each pass + $array = &$original; + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (is_null($key)) { + return $array; + } + + if (isset($array[$key])) { + return $array[$key]; + } + + foreach (explode('.', $key) as $segment) { + if (! is_array($array) || ! array_key_exists($segment, $array)) { + return value($default); + } + + $array = $array[$segment]; + } + + return $array; + } + + /** + * Check if an item exists in an array using "dot" notation. + * + * @param array $array + * @param string $key + * @return bool + */ + public static function has($array, $key) + { + if (empty($array) || is_null($key)) { + return false; + } + + if (array_key_exists($key, $array)) { + return true; + } + + foreach (explode('.', $key) as $segment) { + if (! is_array($array) || ! array_key_exists($segment, $array)) { + return false; + } + + $array = $array[$segment]; + } + + return true; + } + + /** + * Determines if an array is associative. + * + * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. + * + * @param array $array + * @return bool + */ + public static function isAssoc(array $array) + { + $keys = array_keys($array); + + return array_keys($keys) !== $keys; + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Pluck an array of values from an array. + * + * @param array $array + * @param string|array $value + * @param string|array|null $key + * @return array + */ + public static function pluck($array, $value, $key = null) + { + $results = []; + + list($value, $key) = static::explodePluckParameters($value, $key); + + foreach ($array as $item) { + $itemValue = data_get($item, $value); + + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = data_get($item, $key); + + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected static function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + public static function prepend($array, $value, $key = null) + { + if (is_null($key)) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (! isset($array[$key]) || ! is_array($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Sort the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function sort($array, callable $callback) + { + return Collection::make($array)->sortBy($callback)->all(); + } + + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @return array + */ + public static function sortRecursive($array) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value); + } + } + + if (static::isAssoc($array)) { + ksort($array); + } else { + sort($array); + } + + return $array; + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function where($array, callable $callback) + { + $filtered = []; + + foreach ($array as $key => $value) { + if (call_user_func($callback, $key, $value)) { + $filtered[$key] = $value; + } + } + + return $filtered; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php b/core/vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php new file mode 100644 index 0000000..6a8d235 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php @@ -0,0 +1,104 @@ +items = is_array($items) ? $items : $this->getArrayableItems($items); + } + + /** + * Create a new collection instance if the value isn't one already. + * + * @param mixed $items + * @return static + */ + public static function make($items = []) + { + return new static($items); + } + + /** + * Get all of the items in the collection. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Get the average value of a given key. + * + * @param string|null $key + * @return mixed + */ + public function avg($key = null) + { + if ($count = $this->count()) { + return $this->sum($key) / $count; + } + } + + /** + * Alias for the "avg" method. + * + * @param string|null $key + * @return mixed + */ + public function average($key = null) + { + return $this->avg($key); + } + + /** + * Collapse the collection of items into a single array. + * + * @return static + */ + public function collapse() + { + return new static(Arr::collapse($this->items)); + } + + /** + * Determine if an item exists in the collection. + * + * @param mixed $key + * @param mixed $value + * @return bool + */ + public function contains($key, $value = null) + { + if (func_num_args() == 2) { + return $this->contains(function ($k, $item) use ($key, $value) { + return data_get($item, $key) == $value; + }); + } + + if ($this->useAsCallable($key)) { + return ! is_null($this->first($key)); + } + + return in_array($key, $this->items); + } + + /** + * Get the items in the collection that are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->getArrayableItems($items))); + } + + /** + * Execute a callback over each item. + * + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + if ($callback($item, $key) === false) { + break; + } + } + + return $this; + } + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function every($step, $offset = 0) + { + $new = []; + + $position = 0; + + foreach ($this->items as $key => $item) { + if ($position % $step === $offset) { + $new[] = $item; + } + + $position++; + } + + return new static($new); + } + + /** + * Get all items except for those with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::except($this->items, $keys)); + } + + /** + * Fetch a nested element of the collection. + * + * @param string $key + * @return static + * + * @deprecated since version 5.1. Use pluck instead. + */ + public function fetch($key) + { + return new static(Arr::fetch($this->items, $key)); + } + + /** + * Run a filter over each of the items. + * + * @param callable|null $callback + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $value + * @param bool $strict + * @return static + */ + public function where($key, $value, $strict = true) + { + return $this->filter(function ($item) use ($key, $value, $strict) { + return $strict ? data_get($item, $key) === $value + : data_get($item, $key) == $value; + }); + } + + /** + * Filter items by the given key value pair using loose comparison. + * + * @param string $key + * @param mixed $value + * @return static + */ + public function whereLoose($key, $value) + { + return $this->where($key, $value, false); + } + + /** + * Get the first item from the collection. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + if (is_null($callback)) { + return count($this->items) > 0 ? reset($this->items) : value($default); + } + + return Arr::first($this->items, $callback, $default); + } + + /** + * Get a flattened array of the items in the collection. + * + * @return static + */ + public function flatten() + { + return new static(Arr::flatten($this->items)); + } + + /** + * Flip the items in the collection. + * + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * Remove an item from the collection by key. + * + * @param string|array $keys + * @return $this + */ + public function forget($keys) + { + foreach ((array) $keys as $key) { + $this->offsetUnset($key); + } + + return $this; + } + + /** + * Get an item from the collection by key. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if ($this->offsetExists($key)) { + return $this->items[$key]; + } + + return value($default); + } + + /** + * Group an associative array by a field or using a callback. + * + * @param callable|string $groupBy + * @param bool $preserveKeys + * @return static + */ + public function groupBy($groupBy, $preserveKeys = false) + { + $groupBy = $this->valueRetriever($groupBy); + + $results = []; + + foreach ($this->items as $key => $value) { + $groupKey = $groupBy($value, $key); + + if (! array_key_exists($groupKey, $results)) { + $results[$groupKey] = new static; + } + + $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value); + } + + return new static($results); + } + + /** + * Key an associative array by a field or using a callback. + * + * @param callable|string $keyBy + * @return static + */ + public function keyBy($keyBy) + { + $keyBy = $this->valueRetriever($keyBy); + + $results = []; + + foreach ($this->items as $item) { + $results[$keyBy($item)] = $item; + } + + return new static($results); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param mixed $key + * @return bool + */ + public function has($key) + { + return $this->offsetExists($key); + } + + /** + * Concatenate values of a given key as a string. + * + * @param string $value + * @param string $glue + * @return string + */ + public function implode($value, $glue = null) + { + $first = $this->first(); + + if (is_array($first) || is_object($first)) { + return implode($glue, $this->pluck($value)->all()); + } + + return implode($value, $this->items); + } + + /** + * Intersect the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->getArrayableItems($items))); + } + + /** + * Determine if the collection is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Determine if the given value is callable, but not a string. + * + * @param mixed $value + * @return bool + */ + protected function useAsCallable($value) + { + return ! is_string($value) && is_callable($value); + } + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * Get the last item from the collection. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + if (is_null($callback)) { + return count($this->items) > 0 ? end($this->items) : value($default); + } + + return Arr::last($this->items, $callback, $default); + } + + /** + * Get the values of a given key. + * + * @param string $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null) + { + return new static(Arr::pluck($this->items, $value, $key)); + } + + /** + * Alias for the "pluck" method. + * + * @param string $value + * @param string|null $key + * @return static + */ + public function lists($value, $key = null) + { + return $this->pluck($value, $key); + } + + /** + * Run a map over each of the items. + * + * @param callable $callback + * @return static + */ + public function map(callable $callback) + { + $keys = array_keys($this->items); + + $items = array_map($callback, $this->items, $keys); + + return new static(array_combine($keys, $items)); + } + + /** + * Map a collection and flatten the result by a single level. + * + * @param callable $callback + * @return static + */ + public function flatMap(callable $callback) + { + return $this->map($callback)->collapse(); + } + + /** + * Get the max value of a given key. + * + * @param string|null $key + * @return mixed + */ + public function max($key = null) + { + return $this->reduce(function ($result, $item) use ($key) { + $value = data_get($item, $key); + + return is_null($result) || $value > $result ? $value : $result; + }); + } + + /** + * Merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the min value of a given key. + * + * @param string|null $key + * @return mixed + */ + public function min($key = null) + { + return $this->reduce(function ($result, $item) use ($key) { + $value = data_get($item, $key); + + return is_null($result) || $value < $result ? $value : $result; + }); + } + + /** + * Get the items with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function only($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::only($this->items, $keys)); + } + + /** + * "Paginate" the collection by slicing it into a smaller collection. + * + * @param int $page + * @param int $perPage + * @return static + */ + public function forPage($page, $perPage) + { + return $this->slice(($page - 1) * $perPage, $perPage); + } + + /** + * Get and remove the last item from the collection. + * + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * Push an item onto the beginning of the collection. + * + * @param mixed $value + * @param mixed $key + * @return $this + */ + public function prepend($value, $key = null) + { + $this->items = Arr::prepend($this->items, $value, $key); + + return $this; + } + + /** + * Push an item onto the end of the collection. + * + * @param mixed $value + * @return $this + */ + public function push($value) + { + $this->offsetSet(null, $value); + + return $this; + } + + /** + * Get and remove an item from the collection. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function pull($key, $default = null) + { + return Arr::pull($this->items, $key, $default); + } + + /** + * Put an item in the collection by key. + * + * @param mixed $key + * @param mixed $value + * @return $this + */ + public function put($key, $value) + { + $this->offsetSet($key, $value); + + return $this; + } + + /** + * Get one or more items randomly from the collection. + * + * @param int $amount + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function random($amount = 1) + { + if ($amount > ($count = $this->count())) { + throw new InvalidArgumentException("You requested {$amount} items, but there are only {$count} items in the collection"); + } + + $keys = array_rand($this->items, $amount); + + if ($amount == 1) { + return $this->items[$keys]; + } + + return new static(array_intersect_key($this->items, array_flip($keys))); + } + + /** + * Reduce the collection to a single value. + * + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * Create a collection of all elements that do not pass a given truth test. + * + * @param callable|mixed $callback + * @return static + */ + public function reject($callback) + { + if ($this->useAsCallable($callback)) { + return $this->filter(function ($item) use ($callback) { + return ! $callback($item); + }); + } + + return $this->filter(function ($item) use ($callback) { + return $item != $callback; + }); + } + + /** + * Reverse items order. + * + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param mixed $value + * @param bool $strict + * @return mixed + */ + public function search($value, $strict = false) + { + if (! $this->useAsCallable($value)) { + return array_search($value, $this->items, $strict); + } + + foreach ($this->items as $key => $item) { + if (call_user_func($value, $item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Get and remove the first item from the collection. + * + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * Shuffle the items in the collection. + * + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + + return new static($items); + } + + /** + * Slice the underlying collection array. + * + * @param int $offset + * @param int $length + * @param bool $preserveKeys + * @return static + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + /** + * Chunk the underlying collection array. + * + * @param int $size + * @param bool $preserveKeys + * @return static + */ + public function chunk($size, $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * Sort through each item with a callback. + * + * @param callable|null $callback + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + + $callback ? uasort($items, $callback) : uasort($items, function ($a, $b) { + if ($a == $b) { + return 0; + } + + return ($a < $b) ? -1 : 1; + }); + + return new static($items); + } + + /** + * Sort the collection using the given callback. + * + * @param callable|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false) + { + $results = []; + + $callback = $this->valueRetriever($callback); + + // First we will loop through the items and get the comparator from a callback + // function which we were given. Then, we will sort the returned values and + // and grab the corresponding values for the sorted keys from this array. + foreach ($this->items as $key => $value) { + $results[$key] = $callback($value, $key); + } + + $descending ? arsort($results, $options) + : asort($results, $options); + + // Once we have sorted all of the keys in the array, we will loop through them + // and grab the corresponding model so we can set the underlying items list + // to the sorted version. Then we'll just return the collection instance. + foreach (array_keys($results) as $key) { + $results[$key] = $this->items[$key]; + } + + return new static($results); + } + + /** + * Sort the collection in descending order using the given callback. + * + * @param callable|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR) + { + return $this->sortBy($callback, $options, true); + } + + /** + * Splice a portion of the underlying collection array. + * + * @param int $offset + * @param int|null $length + * @param mixed $replacement + * @return static + */ + public function splice($offset, $length = null, $replacement = []) + { + if (func_num_args() == 1) { + return new static(array_splice($this->items, $offset)); + } + + return new static(array_splice($this->items, $offset, $length, $replacement)); + } + + /** + * Get the sum of the given values. + * + * @param callable|string|null $callback + * @return mixed + */ + public function sum($callback = null) + { + if (is_null($callback)) { + return array_sum($this->items); + } + + $callback = $this->valueRetriever($callback); + + return $this->reduce(function ($result, $item) use ($callback) { + return $result += $callback($item); + }, 0); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit) + { + if ($limit < 0) { + return $this->slice($limit, abs($limit)); + } + + return $this->slice(0, $limit); + } + + /** + * Transform each item in the collection using a callback. + * + * @param callable $callback + * @return $this + */ + public function transform(callable $callback) + { + $this->items = $this->map($callback)->all(); + + return $this; + } + + /** + * Return only unique items from the collection array. + * + * @param string|callable|null $key + * @return static + */ + public function unique($key = null) + { + if (is_null($key)) { + return new static(array_unique($this->items, SORT_REGULAR)); + } + + $key = $this->valueRetriever($key); + + $exists = []; + + return $this->reject(function ($item) use ($key, &$exists) { + if (in_array($id = $key($item), $exists)) { + return true; + } + + $exists[] = $id; + }); + } + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * Get a value retrieving callback. + * + * @param string $value + * @return callable + */ + protected function valueRetriever($value) + { + if ($this->useAsCallable($value)) { + return $value; + } + + return function ($item) use ($value) { + return data_get($item, $value); + }; + } + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @param mixed ...$items + * @return static + */ + public function zip($items) + { + $arrayableItems = array_map(function ($items) { + return $this->getArrayableItems($items); + }, func_get_args()); + + $params = array_merge([function () { + return new static(func_get_args()); + }, $this->items], $arrayableItems); + + return new static(call_user_func_array('array_map', $params)); + } + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function toArray() + { + return array_map(function ($value) { + return $value instanceof Arrayable ? $value->toArray() : $value; + }, $this->items); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Get a CachingIterator instance. + * + * @param int $flags + * @return \CachingIterator + */ + public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING) + { + return new CachingIterator($this->getIterator(), $flags); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key) + { + return array_key_exists($key, $this->items); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->items[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->items[$key]); + } + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * Results array of items from Collection or Arrayable. + * + * @param mixed $items + * @return array + */ + protected function getArrayableItems($items) + { + if ($items instanceof self) { + return $items->all(); + } elseif ($items instanceof Arrayable) { + return $items->toArray(); + } elseif ($items instanceof Jsonable) { + return json_decode($items->toJson(), true); + } + + return (array) $items; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Debug/Dumper.php b/core/vendor/laravel/framework/src/Illuminate/Support/Debug/Dumper.php new file mode 100644 index 0000000..0c84a8b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Debug/Dumper.php @@ -0,0 +1,26 @@ +dump((new VarCloner)->cloneVar($value)); + } else { + var_dump($value); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Debug/HtmlDumper.php b/core/vendor/laravel/framework/src/Illuminate/Support/Debug/HtmlDumper.php new file mode 100644 index 0000000..5825ac8 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Debug/HtmlDumper.php @@ -0,0 +1,29 @@ + 'background-color:#fff; color:#222; line-height:1.2em; font-weight:normal; font:12px Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:100000', + 'num' => 'color:#a71d5d', + 'const' => 'color:#795da3', + 'str' => 'color:#df5000', + 'cchr' => 'color:#222', + 'note' => 'color:#a71d5d', + 'ref' => 'color:#a0a0a0', + 'public' => 'color:#795da3', + 'protected' => 'color:#795da3', + 'private' => 'color:#795da3', + 'meta' => 'color:#b729d9', + 'key' => 'color:#df5000', + 'index' => 'color:#a71d5d', + ]; +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Facades/App.php b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/App.php new file mode 100644 index 0000000..2675d56 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/App.php @@ -0,0 +1,19 @@ +getEngineResolver()->resolve('blade')->getCompiler(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Bus.php b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Bus.php new file mode 100644 index 0000000..7c42e9e --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Bus.php @@ -0,0 +1,19 @@ +cookie($key, null)); + } + + /** + * Retrieve a cookie from the request. + * + * @param string $key + * @param mixed $default + * @return string + */ + public static function get($key = null, $default = null) + { + return static::$app['request']->cookie($key, $default); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() + { + return 'cookie'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php new file mode 100644 index 0000000..0eef08d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php @@ -0,0 +1,19 @@ +instance(static::getFacadeAccessor(), $instance); + } + + /** + * Initiate a mock expectation on the facade. + * + * @param mixed + * @return \Mockery\Expectation + */ + public static function shouldReceive() + { + $name = static::getFacadeAccessor(); + + if (static::isMock()) { + $mock = static::$resolvedInstance[$name]; + } else { + $mock = static::createFreshMockInstance($name); + } + + return call_user_func_array([$mock, 'shouldReceive'], func_get_args()); + } + + /** + * Create a fresh mock instance for the given class. + * + * @param string $name + * @return \Mockery\Expectation + */ + protected static function createFreshMockInstance($name) + { + static::$resolvedInstance[$name] = $mock = static::createMockByName($name); + + $mock->shouldAllowMockingProtectedMethods(); + + if (isset(static::$app)) { + static::$app->instance($name, $mock); + } + + return $mock; + } + + /** + * Create a fresh mock instance for the given class. + * + * @param string $name + * @return \Mockery\Expectation + */ + protected static function createMockByName($name) + { + $class = static::getMockableClass($name); + + return $class ? Mockery::mock($class) : Mockery::mock(); + } + + /** + * Determines whether a mock is set as the instance of the facade. + * + * @return bool + */ + protected static function isMock() + { + $name = static::getFacadeAccessor(); + + return isset(static::$resolvedInstance[$name]) && static::$resolvedInstance[$name] instanceof MockInterface; + } + + /** + * Get the mockable class for the bound instance. + * + * @return string|null + */ + protected static function getMockableClass() + { + if ($root = static::getFacadeRoot()) { + return get_class($root); + } + } + + /** + * Get the root object behind the facade. + * + * @return mixed + */ + public static function getFacadeRoot() + { + return static::resolveFacadeInstance(static::getFacadeAccessor()); + } + + /** + * Get the registered name of the component. + * + * @return string + * + * @throws \RuntimeException + */ + protected static function getFacadeAccessor() + { + throw new RuntimeException('Facade does not implement getFacadeAccessor method.'); + } + + /** + * Resolve the facade root instance from the container. + * + * @param string|object $name + * @return mixed + */ + protected static function resolveFacadeInstance($name) + { + if (is_object($name)) { + return $name; + } + + if (isset(static::$resolvedInstance[$name])) { + return static::$resolvedInstance[$name]; + } + + return static::$resolvedInstance[$name] = static::$app[$name]; + } + + /** + * Clear a resolved facade instance. + * + * @param string $name + * @return void + */ + public static function clearResolvedInstance($name) + { + unset(static::$resolvedInstance[$name]); + } + + /** + * Clear all of the resolved instances. + * + * @return void + */ + public static function clearResolvedInstances() + { + static::$resolvedInstance = []; + } + + /** + * Get the application instance behind the facade. + * + * @return \Illuminate\Contracts\Foundation\Application + */ + public static function getFacadeApplication() + { + return static::$app; + } + + /** + * Set the application instance. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + public static function setFacadeApplication($app) + { + static::$app = $app; + } + + /** + * Handle dynamic, static calls to the object. + * + * @param string $method + * @param array $args + * @return mixed + */ + public static function __callStatic($method, $args) + { + $instance = static::getFacadeRoot(); + + if (! $instance) { + throw new RuntimeException('A facade root has not been set.'); + } + + switch (count($args)) { + case 0: + return $instance->$method(); + case 1: + return $instance->$method($args[0]); + case 2: + return $instance->$method($args[0], $args[1]); + case 3: + return $instance->$method($args[0], $args[1], $args[2]); + case 4: + return $instance->$method($args[0], $args[1], $args[2], $args[3]); + default: + return call_user_func_array([$instance, $method], $args); + } + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Facades/File.php b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/File.php new file mode 100644 index 0000000..0f81bf6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/File.php @@ -0,0 +1,19 @@ +input($key, $default); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() + { + return 'request'; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php new file mode 100644 index 0000000..e5862b9 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php @@ -0,0 +1,19 @@ +connection($name)->getSchemaBuilder(); + } + + /** + * Get a schema builder instance for the default connection. + * + * @return \Illuminate\Database\Schema\Builder + */ + protected static function getFacadeAccessor() + { + return static::$app['db']->connection()->getSchemaBuilder(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Session.php b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Session.php new file mode 100644 index 0000000..bc9b5fd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Facades/Session.php @@ -0,0 +1,20 @@ + $value) { + $this->attributes[$key] = $value; + } + } + + /** + * Get an attribute from the container. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + + return value($default); + } + + /** + * Get the attributes from the container. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Convert the Fluent instance to an array. + * + * @return array + */ + public function toArray() + { + return $this->attributes; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the Fluent instance to JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->{$offset}); + } + + /** + * Get the value for a given offset. + * + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->{$offset}; + } + + /** + * Set the value at the given offset. + * + * @param string $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->{$offset} = $value; + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->{$offset}); + } + + /** + * Handle dynamic calls to the container to set attributes. + * + * @param string $method + * @param array $parameters + * @return $this + */ + public function __call($method, $parameters) + { + $this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true; + + return $this; + } + + /** + * Dynamically retrieve the value of an attribute. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Dynamically set the value of an attribute. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->attributes[$key] = $value; + } + + /** + * Dynamically check if an attribute is set. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->attributes[$key]); + } + + /** + * Dynamically unset an attribute. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->attributes[$key]); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/HtmlString.php b/core/vendor/laravel/framework/src/Illuminate/Support/HtmlString.php new file mode 100644 index 0000000..8d246cf --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/HtmlString.php @@ -0,0 +1,46 @@ +html = $html; + } + + /** + * Get the the HTML string. + * + * @return string + */ + public function toHtml() + { + return $this->html; + } + + /** + * Get the the HTML string. + * + * @return string + */ + public function __toString() + { + return $this->toHtml(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Manager.php b/core/vendor/laravel/framework/src/Illuminate/Support/Manager.php new file mode 100644 index 0000000..29154c6 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Manager.php @@ -0,0 +1,139 @@ +app = $app; + } + + /** + * Get the default driver name. + * + * @return string + */ + abstract public function getDefaultDriver(); + + /** + * Get a driver instance. + * + * @param string $driver + * @return mixed + */ + public function driver($driver = null) + { + $driver = $driver ?: $this->getDefaultDriver(); + + // If the given driver has not been created before, we will create the instances + // here and cache it so we can return it next time very quickly. If there is + // already a driver created by this name, we'll just return that instance. + if (! isset($this->drivers[$driver])) { + $this->drivers[$driver] = $this->createDriver($driver); + } + + return $this->drivers[$driver]; + } + + /** + * Create a new driver instance. + * + * @param string $driver + * @return mixed + * + * @throws \InvalidArgumentException + */ + protected function createDriver($driver) + { + $method = 'create'.ucfirst($driver).'Driver'; + + // We'll check to see if a creator method exists for the given driver. If not we + // will check for a custom driver creator, which allows developers to create + // drivers using their own customized driver creator Closure to create it. + if (isset($this->customCreators[$driver])) { + return $this->callCustomCreator($driver); + } elseif (method_exists($this, $method)) { + return $this->$method(); + } + + throw new InvalidArgumentException("Driver [$driver] not supported."); + } + + /** + * Call a custom driver creator. + * + * @param string $driver + * @return mixed + */ + protected function callCustomCreator($driver) + { + return $this->customCreators[$driver]($this->app); + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * @return $this + */ + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback; + + return $this; + } + + /** + * Get all of the created "drivers". + * + * @return array + */ + public function getDrivers() + { + return $this->drivers; + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->driver(), $method], $parameters); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/MessageBag.php b/core/vendor/laravel/framework/src/Illuminate/Support/MessageBag.php new file mode 100644 index 0000000..a58b154 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/MessageBag.php @@ -0,0 +1,309 @@ + $value) { + $this->messages[$key] = (array) $value; + } + } + + /** + * Get the keys present in the message bag. + * + * @return array + */ + public function keys() + { + return array_keys($this->messages); + } + + /** + * Add a message to the bag. + * + * @param string $key + * @param string $message + * @return $this + */ + public function add($key, $message) + { + if ($this->isUnique($key, $message)) { + $this->messages[$key][] = $message; + } + + return $this; + } + + /** + * Merge a new array of messages into the bag. + * + * @param \Illuminate\Contracts\Support\MessageProvider|array $messages + * @return $this + */ + public function merge($messages) + { + if ($messages instanceof MessageProvider) { + $messages = $messages->getMessageBag()->getMessages(); + } + + $this->messages = array_merge_recursive($this->messages, $messages); + + return $this; + } + + /** + * Determine if a key and message combination already exists. + * + * @param string $key + * @param string $message + * @return bool + */ + protected function isUnique($key, $message) + { + $messages = (array) $this->messages; + + return ! isset($messages[$key]) || ! in_array($message, $messages[$key]); + } + + /** + * Determine if messages exist for a given key. + * + * @param string $key + * @return bool + */ + public function has($key = null) + { + return $this->first($key) !== ''; + } + + /** + * Get the first message from the bag for a given key. + * + * @param string $key + * @param string $format + * @return string + */ + public function first($key = null, $format = null) + { + $messages = is_null($key) ? $this->all($format) : $this->get($key, $format); + + return count($messages) > 0 ? $messages[0] : ''; + } + + /** + * Get all of the messages from the bag for a given key. + * + * @param string $key + * @param string $format + * @return array + */ + public function get($key, $format = null) + { + // If the message exists in the container, we will transform it and return + // the message. Otherwise, we'll return an empty array since the entire + // methods is to return back an array of messages in the first place. + if (array_key_exists($key, $this->messages)) { + return $this->transform($this->messages[$key], $this->checkFormat($format), $key); + } + + return []; + } + + /** + * Get all of the messages for every key in the bag. + * + * @param string $format + * @return array + */ + public function all($format = null) + { + $format = $this->checkFormat($format); + + $all = []; + + foreach ($this->messages as $key => $messages) { + $all = array_merge($all, $this->transform($messages, $format, $key)); + } + + return $all; + } + + /** + * Format an array of messages. + * + * @param array $messages + * @param string $format + * @param string $messageKey + * @return array + */ + protected function transform($messages, $format, $messageKey) + { + $messages = (array) $messages; + + // We will simply spin through the given messages and transform each one + // replacing the :message place holder with the real message allowing + // the messages to be easily formatted to each developer's desires. + $replace = [':message', ':key']; + + foreach ($messages as &$message) { + $message = str_replace($replace, [$message, $messageKey], $format); + } + + return $messages; + } + + /** + * Get the appropriate format based on the given format. + * + * @param string $format + * @return string + */ + protected function checkFormat($format) + { + return $format ?: $this->format; + } + + /** + * Get the raw messages in the container. + * + * @return array + */ + public function getMessages() + { + return $this->messages; + } + + /** + * Get the messages for the instance. + * + * @return \Illuminate\Support\MessageBag + */ + public function getMessageBag() + { + return $this; + } + + /** + * Get the default message format. + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set the default message format. + * + * @param string $format + * @return \Illuminate\Support\MessageBag + */ + public function setFormat($format = ':message') + { + $this->format = $format; + + return $this; + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function isEmpty() + { + return ! $this->any(); + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function any() + { + return $this->count() > 0; + } + + /** + * Get the number of messages in the container. + * + * @return int + */ + public function count() + { + return count($this->messages, COUNT_RECURSIVE) - count($this->messages); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return $this->getMessages(); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + + /** + * Convert the message bag to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php b/core/vendor/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php new file mode 100644 index 0000000..a111985 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php @@ -0,0 +1,104 @@ +parsed[$key])) { + return $this->parsed[$key]; + } + + // If the key does not contain a double colon, it means the key is not in a + // namespace, and is just a regular configuration item. Namespaces are a + // tool for organizing configuration items for things such as modules. + if (strpos($key, '::') === false) { + $segments = explode('.', $key); + + $parsed = $this->parseBasicSegments($segments); + } else { + $parsed = $this->parseNamespacedSegments($key); + } + + // Once we have the parsed array of this key's elements, such as its groups + // and namespace, we will cache each array inside a simple list that has + // the key and the parsed array for quick look-ups for later requests. + return $this->parsed[$key] = $parsed; + } + + /** + * Parse an array of basic segments. + * + * @param array $segments + * @return array + */ + protected function parseBasicSegments(array $segments) + { + // The first segment in a basic array will always be the group, so we can go + // ahead and grab that segment. If there is only one total segment we are + // just pulling an entire group out of the array and not a single item. + $group = $segments[0]; + + if (count($segments) == 1) { + return [null, $group, null]; + } + + // If there is more than one segment in this group, it means we are pulling + // a specific item out of a groups and will need to return the item name + // as well as the group so we know which item to pull from the arrays. + else { + $item = implode('.', array_slice($segments, 1)); + + return [null, $group, $item]; + } + } + + /** + * Parse an array of namespaced segments. + * + * @param string $key + * @return array + */ + protected function parseNamespacedSegments($key) + { + list($namespace, $item) = explode('::', $key); + + // First we'll just explode the first segment to get the namespace and group + // since the item should be in the remaining segments. Once we have these + // two pieces of data we can proceed with parsing out the item's value. + $itemSegments = explode('.', $item); + + $groupAndItem = array_slice($this->parseBasicSegments($itemSegments), 1); + + return array_merge([$namespace], $groupAndItem); + } + + /** + * Set the parsed value of a key. + * + * @param string $key + * @param array $parsed + * @return void + */ + public function setParsedKey($key, $parsed) + { + $this->parsed[$key] = $parsed; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Pluralizer.php b/core/vendor/laravel/framework/src/Illuminate/Support/Pluralizer.php new file mode 100644 index 0000000..7ba7a4d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Pluralizer.php @@ -0,0 +1,104 @@ +app = $app; + } + + /** + * Register the service provider. + * + * @return void + */ + abstract public function register(); + + /** + * Merge the given configuration with the existing configuration. + * + * @param string $path + * @param string $key + * @return void + */ + protected function mergeConfigFrom($path, $key) + { + $config = $this->app['config']->get($key, []); + + $this->app['config']->set($key, array_merge(require $path, $config)); + } + + /** + * Register a view file namespace. + * + * @param string $path + * @param string $namespace + * @return void + */ + protected function loadViewsFrom($path, $namespace) + { + if (is_dir($appPath = $this->app->basePath().'/resources/views/vendor/'.$namespace)) { + $this->app['view']->addNamespace($namespace, $appPath); + } + + $this->app['view']->addNamespace($namespace, $path); + } + + /** + * Register a translation file namespace. + * + * @param string $path + * @param string $namespace + * @return void + */ + protected function loadTranslationsFrom($path, $namespace) + { + $this->app['translator']->addNamespace($namespace, $path); + } + + /** + * Register paths to be published by the publish command. + * + * @param array $paths + * @param string $group + * @return void + */ + protected function publishes(array $paths, $group = null) + { + $class = get_class($this); + + if (! array_key_exists($class, static::$publishes)) { + static::$publishes[$class] = []; + } + + static::$publishes[$class] = array_merge(static::$publishes[$class], $paths); + + if ($group) { + if (! array_key_exists($group, static::$publishGroups)) { + static::$publishGroups[$group] = []; + } + + static::$publishGroups[$group] = array_merge(static::$publishGroups[$group], $paths); + } + } + + /** + * Get the paths to publish. + * + * @param string $provider + * @param string $group + * @return array + */ + public static function pathsToPublish($provider = null, $group = null) + { + if ($provider && $group) { + if (empty(static::$publishes[$provider]) || empty(static::$publishGroups[$group])) { + return []; + } + + return array_intersect(static::$publishes[$provider], static::$publishGroups[$group]); + } + + if ($group && array_key_exists($group, static::$publishGroups)) { + return static::$publishGroups[$group]; + } + + if ($provider && array_key_exists($provider, static::$publishes)) { + return static::$publishes[$provider]; + } + + if ($group || $provider) { + return []; + } + + $paths = []; + + foreach (static::$publishes as $class => $publish) { + $paths = array_merge($paths, $publish); + } + + return $paths; + } + + /** + * Register the package's custom Artisan commands. + * + * @param array|mixed $commands + * @return void + */ + public function commands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + + // To register the commands with Artisan, we will grab each of the arguments + // passed into the method and listen for Artisan "start" event which will + // give us the Artisan console instance which we will give commands to. + $events = $this->app['events']; + + $events->listen('artisan.start', function ($artisan) use ($commands) { + $artisan->resolveCommands($commands); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return []; + } + + /** + * Get the events that trigger this service provider to register. + * + * @return array + */ + public function when() + { + return []; + } + + /** + * Determine if the provider is deferred. + * + * @return bool + */ + public function isDeferred() + { + return $this->defer; + } + + /** + * Get a list of files that should be compiled for the package. + * + * @return array + */ + public static function compiles() + { + return []; + } + + /** + * Dynamically handle missing method calls. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if ($method == 'boot') { + return; + } + + throw new BadMethodCallException("Call to undefined method [{$method}]"); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Str.php b/core/vendor/laravel/framework/src/Illuminate/Support/Str.php new file mode 100644 index 0000000..2c3a7cb --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Str.php @@ -0,0 +1,443 @@ +container = $container; + + if (! $this->container->bound('config')) { + $this->container->instance('config', new Fluent); + } + } + + /** + * Make this capsule instance available globally. + * + * @return void + */ + public function setAsGlobal() + { + static::$instance = $this; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Contracts\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php b/core/vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php new file mode 100644 index 0000000..a6a54fd --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php @@ -0,0 +1,83 @@ +bindTo($this, get_class($this)), $parameters); + } else { + return call_user_func_array(static::$macros[$method], $parameters); + } + } + + throw new BadMethodCallException("Method {$method} does not exist."); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/ViewErrorBag.php b/core/vendor/laravel/framework/src/Illuminate/Support/ViewErrorBag.php new file mode 100644 index 0000000..9fe3b96 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/ViewErrorBag.php @@ -0,0 +1,107 @@ +bags[$key]); + } + + /** + * Get a MessageBag instance from the bags. + * + * @param string $key + * @return \Illuminate\Contracts\Support\MessageBag + */ + public function getBag($key) + { + return Arr::get($this->bags, $key) ?: new MessageBag; + } + + /** + * Get all the bags. + * + * @return array + */ + public function getBags() + { + return $this->bags; + } + + /** + * Add a new MessageBag instance to the bags. + * + * @param string $key + * @param \Illuminate\Contracts\Support\MessageBag $bag + * @return $this + */ + public function put($key, MessageBagContract $bag) + { + $this->bags[$key] = $bag; + + return $this; + } + + /** + * Get the number of messages in the default bag. + * + * @return int + */ + public function count() + { + return $this->default->count(); + } + + /** + * Dynamically call methods on the default bag. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->default, $method], $parameters); + } + + /** + * Dynamically access a view error bag. + * + * @param string $key + * @return \Illuminate\Contracts\Support\MessageBag + */ + public function __get($key) + { + return $this->getBag($key); + } + + /** + * Dynamically set a view error bag. + * + * @param string $key + * @param \Illuminate\Contracts\Support\MessageBag $value + * @return void + */ + public function __set($key, $value) + { + $this->put($key, $value); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/composer.json b/core/vendor/laravel/framework/src/Illuminate/Support/composer.json new file mode 100644 index 0000000..a96f06a --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/composer.json @@ -0,0 +1,42 @@ +{ + "name": "illuminate/support", + "description": "The Illuminate Support package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "ext-mbstring": "*", + "illuminate/contracts": "5.1.*", + "doctrine/inflector": "~1.0", + "danielstjules/stringy": "~1.8", + "paragonie/random_compat": "~1.4" + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + }, + "files": [ + "helpers.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "jeremeamia/superclosure": "Required to be able to serialize closures (~2.0).", + "symfony/var-dumper": "Improves the dd function (2.7.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Support/helpers.php b/core/vendor/laravel/framework/src/Illuminate/Support/helpers.php new file mode 100644 index 0000000..3ea27f5 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Support/helpers.php @@ -0,0 +1,800 @@ + $value) { + if (is_numeric($key)) { + $start++; + + $array[$start] = Arr::pull($array, $key); + } + } + + return $array; + } +} + +if (! function_exists('array_add')) { + /** + * Add an element to an array using "dot" notation if it doesn't exist. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + function array_add($array, $key, $value) + { + return Arr::add($array, $key, $value); + } +} + +if (! function_exists('array_build')) { + /** + * Build a new array using a callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + function array_build($array, callable $callback) + { + return Arr::build($array, $callback); + } +} + +if (! function_exists('array_collapse')) { + /** + * Collapse an array of arrays into a single array. + * + * @param \ArrayAccess|array $array + * @return array + */ + function array_collapse($array) + { + return Arr::collapse($array); + } +} + +if (! function_exists('array_divide')) { + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + function array_divide($array) + { + return Arr::divide($array); + } +} + +if (! function_exists('array_dot')) { + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + function array_dot($array, $prepend = '') + { + return Arr::dot($array, $prepend); + } +} + +if (! function_exists('array_except')) { + /** + * Get all of the given array except for a specified array of items. + * + * @param array $array + * @param array|string $keys + * @return array + */ + function array_except($array, $keys) + { + return Arr::except($array, $keys); + } +} + +if (! function_exists('array_fetch')) { + /** + * Fetch a flattened array of a nested array element. + * + * @param array $array + * @param string $key + * @return array + * + * @deprecated since version 5.1. Use array_pluck instead. + */ + function array_fetch($array, $key) + { + return Arr::fetch($array, $key); + } +} + +if (! function_exists('array_first')) { + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable $callback + * @param mixed $default + * @return mixed + */ + function array_first($array, callable $callback, $default = null) + { + return Arr::first($array, $callback, $default); + } +} + +if (! function_exists('array_flatten')) { + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @return array + */ + function array_flatten($array) + { + return Arr::flatten($array); + } +} + +if (! function_exists('array_forget')) { + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + * @return void + */ + function array_forget(&$array, $keys) + { + return Arr::forget($array, $keys); + } +} + +if (! function_exists('array_get')) { + /** + * Get an item from an array using "dot" notation. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + function array_get($array, $key, $default = null) + { + return Arr::get($array, $key, $default); + } +} + +if (! function_exists('array_has')) { + /** + * Check if an item exists in an array using "dot" notation. + * + * @param array $array + * @param string $key + * @return bool + */ + function array_has($array, $key) + { + return Arr::has($array, $key); + } +} + +if (! function_exists('array_last')) { + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable $callback + * @param mixed $default + * @return mixed + */ + function array_last($array, $callback, $default = null) + { + return Arr::last($array, $callback, $default); + } +} + +if (! function_exists('array_only')) { + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + function array_only($array, $keys) + { + return Arr::only($array, $keys); + } +} + +if (! function_exists('array_pluck')) { + /** + * Pluck an array of values from an array. + * + * @param array $array + * @param string|array $value + * @param string|array|null $key + * @return array + */ + function array_pluck($array, $value, $key = null) + { + return Arr::pluck($array, $value, $key); + } +} + +if (! function_exists('array_prepend')) { + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + function array_prepend($array, $value, $key = null) + { + return Arr::prepend($array, $value, $key); + } +} + +if (! function_exists('array_pull')) { + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + function array_pull(&$array, $key, $default = null) + { + return Arr::pull($array, $key, $default); + } +} + +if (! function_exists('array_set')) { + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + function array_set(&$array, $key, $value) + { + return Arr::set($array, $key, $value); + } +} + +if (! function_exists('array_sort')) { + /** + * Sort the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + function array_sort($array, callable $callback) + { + return Arr::sort($array, $callback); + } +} + +if (! function_exists('array_sort_recursive')) { + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @return array + */ + function array_sort_recursive($array) + { + return Arr::sortRecursive($array); + } +} + +if (! function_exists('array_where')) { + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + function array_where($array, callable $callback) + { + return Arr::where($array, $callback); + } +} + +if (! function_exists('camel_case')) { + /** + * Convert a value to camel case. + * + * @param string $value + * @return string + */ + function camel_case($value) + { + return Str::camel($value); + } +} + +if (! function_exists('class_basename')) { + /** + * Get the class "basename" of the given object / class. + * + * @param string|object $class + * @return string + */ + function class_basename($class) + { + $class = is_object($class) ? get_class($class) : $class; + + return basename(str_replace('\\', '/', $class)); + } +} + +if (! function_exists('class_uses_recursive')) { + /** + * Returns all traits used by a class, its subclasses and trait of their traits. + * + * @param string $class + * @return array + */ + function class_uses_recursive($class) + { + $results = []; + + foreach (array_merge([$class => $class], class_parents($class)) as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} + +if (! function_exists('collect')) { + /** + * Create a collection from the given value. + * + * @param mixed $value + * @return \Illuminate\Support\Collection + */ + function collect($value = null) + { + return new Collection($value); + } +} + +if (! function_exists('data_get')) { + /** + * Get an item from an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array $key + * @param mixed $default + * @return mixed + */ + function data_get($target, $key, $default = null) + { + if (is_null($key)) { + return $target; + } + + $key = is_array($key) ? $key : explode('.', $key); + + foreach ($key as $segment) { + if (is_array($target)) { + if (! array_key_exists($segment, $target)) { + return value($default); + } + + $target = $target[$segment]; + } elseif ($target instanceof ArrayAccess) { + if (! isset($target[$segment])) { + return value($default); + } + + $target = $target[$segment]; + } elseif (is_object($target)) { + if (! isset($target->{$segment})) { + return value($default); + } + + $target = $target->{$segment}; + } else { + return value($default); + } + } + + return $target; + } +} + +if (! function_exists('dd')) { + /** + * Dump the passed variables and end the script. + * + * @param mixed + * @return void + */ + function dd() + { + array_map(function ($x) { + (new Dumper)->dump($x); + }, func_get_args()); + + die(1); + } +} + +if (! function_exists('e')) { + /** + * Escape HTML entities in a string. + * + * @param \Illuminate\Contracts\Support\Htmlable|string $value + * @return string + */ + function e($value) + { + if ($value instanceof Htmlable) { + return $value->toHtml(); + } + + return htmlentities($value, ENT_QUOTES, 'UTF-8', false); + } +} + +if (! function_exists('ends_with')) { + /** + * Determine if a given string ends with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + function ends_with($haystack, $needles) + { + return Str::endsWith($haystack, $needles); + } +} + +if (! function_exists('head')) { + /** + * Get the first element of an array. Useful for method chaining. + * + * @param array $array + * @return mixed + */ + function head($array) + { + return reset($array); + } +} + +if (! function_exists('last')) { + /** + * Get the last element from an array. + * + * @param array $array + * @return mixed + */ + function last($array) + { + return end($array); + } +} + +if (! function_exists('object_get')) { + /** + * Get an item from an object using "dot" notation. + * + * @param object $object + * @param string $key + * @param mixed $default + * @return mixed + */ + function object_get($object, $key, $default = null) + { + if (is_null($key) || trim($key) == '') { + return $object; + } + + foreach (explode('.', $key) as $segment) { + if (! is_object($object) || ! isset($object->{$segment})) { + return value($default); + } + + $object = $object->{$segment}; + } + + return $object; + } +} + +if (! function_exists('preg_replace_sub')) { + /** + * Replace a given pattern with each value in the array in sequentially. + * + * @param string $pattern + * @param array $replacements + * @param string $subject + * @return string + */ + function preg_replace_sub($pattern, &$replacements, $subject) + { + return preg_replace_callback($pattern, function ($match) use (&$replacements) { + foreach ($replacements as $key => $value) { + return array_shift($replacements); + } + }, $subject); + } +} + +if (! function_exists('snake_case')) { + /** + * Convert a string to snake case. + * + * @param string $value + * @param string $delimiter + * @return string + */ + function snake_case($value, $delimiter = '_') + { + return Str::snake($value, $delimiter); + } +} + +if (! function_exists('starts_with')) { + /** + * Determine if a given string starts with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + function starts_with($haystack, $needles) + { + return Str::startsWith($haystack, $needles); + } +} + +if (! function_exists('str_contains')) { + /** + * Determine if a given string contains a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + function str_contains($haystack, $needles) + { + return Str::contains($haystack, $needles); + } +} + +if (! function_exists('str_finish')) { + /** + * Cap a string with a single instance of a given value. + * + * @param string $value + * @param string $cap + * @return string + */ + function str_finish($value, $cap) + { + return Str::finish($value, $cap); + } +} + +if (! function_exists('str_is')) { + /** + * Determine if a given string matches a given pattern. + * + * @param string $pattern + * @param string $value + * @return bool + */ + function str_is($pattern, $value) + { + return Str::is($pattern, $value); + } +} + +if (! function_exists('str_limit')) { + /** + * Limit the number of characters in a string. + * + * @param string $value + * @param int $limit + * @param string $end + * @return string + */ + function str_limit($value, $limit = 100, $end = '...') + { + return Str::limit($value, $limit, $end); + } +} + +if (! function_exists('str_plural')) { + /** + * Get the plural form of an English word. + * + * @param string $value + * @param int $count + * @return string + */ + function str_plural($value, $count = 2) + { + return Str::plural($value, $count); + } +} + +if (! function_exists('str_random')) { + /** + * Generate a more truly "random" alpha-numeric string. + * + * @param int $length + * @return string + * + * @throws \RuntimeException + */ + function str_random($length = 16) + { + return Str::random($length); + } +} + +if (! function_exists('str_replace_array')) { + /** + * Replace a given value in the string sequentially with an array. + * + * @param string $search + * @param array $replace + * @param string $subject + * @return string + */ + function str_replace_array($search, array $replace, $subject) + { + foreach ($replace as $value) { + $subject = preg_replace('/'.$search.'/', $value, $subject, 1); + } + + return $subject; + } +} + +if (! function_exists('str_singular')) { + /** + * Get the singular form of an English word. + * + * @param string $value + * @return string + */ + function str_singular($value) + { + return Str::singular($value); + } +} + +if (! function_exists('str_slug')) { + /** + * Generate a URL friendly "slug" from a given string. + * + * @param string $title + * @param string $separator + * @return string + */ + function str_slug($title, $separator = '-') + { + return Str::slug($title, $separator); + } +} + +if (! function_exists('studly_case')) { + /** + * Convert a value to studly caps case. + * + * @param string $value + * @return string + */ + function studly_case($value) + { + return Str::studly($value); + } +} + +if (! function_exists('title_case')) { + /** + * Convert a value to title case. + * + * @param string $value + * @return string + */ + function title_case($value) + { + return Str::title($value); + } +} + +if (! function_exists('trait_uses_recursive')) { + /** + * Returns all traits used by a trait and its traits. + * + * @param string $trait + * @return array + */ + function trait_uses_recursive($trait) + { + $traits = class_uses($trait); + + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (! function_exists('value')) { + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + function value($value) + { + return $value instanceof Closure ? $value() : $value; + } +} + +if (! function_exists('windows_os')) { + /** + * Determine whether the current envrionment is Windows based. + * + * @return bool + */ + function windows_os() + { + return strtolower(substr(PHP_OS, 0, 3)) === 'win'; + } +} + +if (! function_exists('with')) { + /** + * Return the given object. Useful for chaining. + * + * @param mixed $object + * @return mixed + */ + function with($object) + { + return $object; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php b/core/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php new file mode 100644 index 0000000..835accc --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php @@ -0,0 +1,127 @@ +path = $path; + $this->files = $files; + } + + /** + * Load the messages for the given locale. + * + * @param string $locale + * @param string $group + * @param string $namespace + * @return array + */ + public function load($locale, $group, $namespace = null) + { + if (is_null($namespace) || $namespace == '*') { + return $this->loadPath($this->path, $locale, $group); + } + + return $this->loadNamespaced($locale, $group, $namespace); + } + + /** + * Load a namespaced translation group. + * + * @param string $locale + * @param string $group + * @param string $namespace + * @return array + */ + protected function loadNamespaced($locale, $group, $namespace) + { + if (isset($this->hints[$namespace])) { + $lines = $this->loadPath($this->hints[$namespace], $locale, $group); + + return $this->loadNamespaceOverrides($lines, $locale, $group, $namespace); + } + + return []; + } + + /** + * Load a local namespaced translation group for overrides. + * + * @param array $lines + * @param string $locale + * @param string $group + * @param string $namespace + * @return array + */ + protected function loadNamespaceOverrides(array $lines, $locale, $group, $namespace) + { + $file = "{$this->path}/vendor/{$namespace}/{$locale}/{$group}.php"; + + if ($this->files->exists($file)) { + return array_replace_recursive($lines, $this->files->getRequire($file)); + } + + return $lines; + } + + /** + * Load a locale from a given path. + * + * @param string $path + * @param string $locale + * @param string $group + * @return array + */ + protected function loadPath($path, $locale, $group) + { + if ($this->files->exists($full = "{$path}/{$locale}/{$group}.php")) { + return $this->files->getRequire($full); + } + + return []; + } + + /** + * Add a new namespace to the loader. + * + * @param string $namespace + * @param string $hint + * @return void + */ + public function addNamespace($namespace, $hint) + { + $this->hints[$namespace] = $hint; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php b/core/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php new file mode 100644 index 0000000..9e45572 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php @@ -0,0 +1,25 @@ +registerLoader(); + + $this->app->singleton('translator', function ($app) { + $loader = $app['translation.loader']; + + // When registering the translator component, we'll need to set the default + // locale as well as the fallback locale. So, we'll grab the application + // configuration so we can easily get both of these values from there. + $locale = $app['config']['app.locale']; + + $trans = new Translator($loader, $locale); + + $trans->setFallback($app['config']['app.fallback_locale']); + + return $trans; + }); + } + + /** + * Register the translation line loader. + * + * @return void + */ + protected function registerLoader() + { + $this->app->singleton('translation.loader', function ($app) { + return new FileLoader($app['files'], $app['path.lang']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['translator', 'translation.loader']; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Translation/Translator.php b/core/vendor/laravel/framework/src/Illuminate/Translation/Translator.php new file mode 100644 index 0000000..62f09c7 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Translation/Translator.php @@ -0,0 +1,383 @@ +loader = $loader; + $this->locale = $locale; + } + + /** + * Determine if a translation exists for a given locale. + * + * @param string $key + * @param string|null $locale + * @return bool + */ + public function hasForLocale($key, $locale = null) + { + return $this->has($key, $locale, false); + } + + /** + * Determine if a translation exists. + * + * @param string $key + * @param string|null $locale + * @param bool $fallback + * @return bool + */ + public function has($key, $locale = null, $fallback = true) + { + return $this->get($key, [], $locale, $fallback) !== $key; + } + + /** + * Get the translation for the given key. + * + * @param string $key + * @param array $replace + * @param string|null $locale + * @param bool $fallback + * @return string + */ + public function get($key, array $replace = [], $locale = null, $fallback = true) + { + list($namespace, $group, $item) = $this->parseKey($key); + + // Here we will get the locale that should be used for the language line. If one + // was not passed, we will use the default locales which was given to us when + // the translator was instantiated. Then, we can load the lines and return. + $locales = $fallback ? $this->parseLocale($locale) : [$locale ?: $this->locale]; + + foreach ($locales as $locale) { + $this->load($namespace, $group, $locale); + + $line = $this->getLine( + $namespace, $group, $locale, $item, $replace + ); + + if (! is_null($line)) { + break; + } + } + + // If the line doesn't exist, we will return back the key which was requested as + // that will be quick to spot in the UI if language keys are wrong or missing + // from the application's language files. Otherwise we can return the line. + if (! isset($line)) { + return $key; + } + + return $line; + } + + /** + * Retrieve a language line out the loaded array. + * + * @param string $namespace + * @param string $group + * @param string $locale + * @param string $item + * @param array $replace + * @return string|array|null + */ + protected function getLine($namespace, $group, $locale, $item, array $replace) + { + $line = Arr::get($this->loaded[$namespace][$group][$locale], $item); + + if (is_string($line)) { + return $this->makeReplacements($line, $replace); + } elseif (is_array($line) && count($line) > 0) { + return $line; + } + } + + /** + * Make the place-holder replacements on a line. + * + * @param string $line + * @param array $replace + * @return string + */ + protected function makeReplacements($line, array $replace) + { + $replace = $this->sortReplacements($replace); + + foreach ($replace as $key => $value) { + $line = str_replace(':'.$key, $value, $line); + } + + return $line; + } + + /** + * Sort the replacements array. + * + * @param array $replace + * @return array + */ + protected function sortReplacements(array $replace) + { + return (new Collection($replace))->sortBy(function ($value, $key) { + return mb_strlen($key) * -1; + }); + } + + /** + * Get a translation according to an integer value. + * + * @param string $key + * @param int $number + * @param array $replace + * @param string $locale + * @return string + */ + public function choice($key, $number, array $replace = [], $locale = null) + { + $line = $this->get($key, $replace, $locale = $locale ?: $this->locale ?: $this->fallback); + + $replace['count'] = $number; + + return $this->makeReplacements($this->getSelector()->choose($line, $number, $locale), $replace); + } + + /** + * Get the translation for a given key. + * + * @param string $id + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + public function trans($id, array $parameters = [], $domain = 'messages', $locale = null) + { + return $this->get($id, $parameters, $locale); + } + + /** + * Get a translation according to an integer value. + * + * @param string $id + * @param int $number + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + public function transChoice($id, $number, array $parameters = [], $domain = 'messages', $locale = null) + { + return $this->choice($id, $number, $parameters, $locale); + } + + /** + * Load the specified language group. + * + * @param string $namespace + * @param string $group + * @param string $locale + * @return void + */ + public function load($namespace, $group, $locale) + { + if ($this->isLoaded($namespace, $group, $locale)) { + return; + } + + // The loader is responsible for returning the array of language lines for the + // given namespace, group, and locale. We'll set the lines in this array of + // lines that have already been loaded so that we can easily access them. + $lines = $this->loader->load($locale, $group, $namespace); + + $this->loaded[$namespace][$group][$locale] = $lines; + } + + /** + * Determine if the given group has been loaded. + * + * @param string $namespace + * @param string $group + * @param string $locale + * @return bool + */ + protected function isLoaded($namespace, $group, $locale) + { + return isset($this->loaded[$namespace][$group][$locale]); + } + + /** + * Add a new namespace to the loader. + * + * @param string $namespace + * @param string $hint + * @return void + */ + public function addNamespace($namespace, $hint) + { + $this->loader->addNamespace($namespace, $hint); + } + + /** + * Parse a key into namespace, group, and item. + * + * @param string $key + * @return array + */ + public function parseKey($key) + { + $segments = parent::parseKey($key); + + if (is_null($segments[0])) { + $segments[0] = '*'; + } + + return $segments; + } + + /** + * Get the array of locales to be checked. + * + * @param string|null $locale + * @return array + */ + protected function parseLocale($locale) + { + if (! is_null($locale)) { + return array_filter([$locale, $this->fallback]); + } + + return array_filter([$this->locale, $this->fallback]); + } + + /** + * Get the message selector instance. + * + * @return \Symfony\Component\Translation\MessageSelector + */ + public function getSelector() + { + if (! isset($this->selector)) { + $this->selector = new MessageSelector; + } + + return $this->selector; + } + + /** + * Set the message selector instance. + * + * @param \Symfony\Component\Translation\MessageSelector $selector + * @return void + */ + public function setSelector(MessageSelector $selector) + { + $this->selector = $selector; + } + + /** + * Get the language line loader implementation. + * + * @return \Illuminate\Translation\LoaderInterface + */ + public function getLoader() + { + return $this->loader; + } + + /** + * Get the default locale being used. + * + * @return string + */ + public function locale() + { + return $this->getLocale(); + } + + /** + * Get the default locale being used. + * + * @return string + */ + public function getLocale() + { + return $this->locale; + } + + /** + * Set the default locale. + * + * @param string $locale + * @return void + */ + public function setLocale($locale) + { + $this->locale = $locale; + } + + /** + * Get the fallback locale being used. + * + * @return string + */ + public function getFallback() + { + return $this->fallback; + } + + /** + * Set the fallback locale being used. + * + * @param string $fallback + * @return void + */ + public function setFallback($fallback) + { + $this->fallback = $fallback; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Translation/composer.json b/core/vendor/laravel/framework/src/Illuminate/Translation/composer.json new file mode 100644 index 0000000..e3e355d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Translation/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/translation", + "description": "The Illuminate Translation package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/filesystem": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/translation": "2.7.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Translation\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Validation/DatabasePresenceVerifier.php b/core/vendor/laravel/framework/src/Illuminate/Validation/DatabasePresenceVerifier.php new file mode 100644 index 0000000..42aa353 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Validation/DatabasePresenceVerifier.php @@ -0,0 +1,120 @@ +db = $db; + } + + /** + * Count the number of objects in a collection having the given value. + * + * @param string $collection + * @param string $column + * @param string $value + * @param int $excludeId + * @param string $idColumn + * @param array $extra + * @return int + */ + public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = []) + { + $query = $this->table($collection)->where($column, '=', $value); + + if (! is_null($excludeId) && $excludeId != 'NULL') { + $query->where($idColumn ?: 'id', '<>', $excludeId); + } + + foreach ($extra as $key => $extraValue) { + $this->addWhere($query, $key, $extraValue); + } + + return $query->count(); + } + + /** + * Count the number of objects in a collection with the given values. + * + * @param string $collection + * @param string $column + * @param array $values + * @param array $extra + * @return int + */ + public function getMultiCount($collection, $column, array $values, array $extra = []) + { + $query = $this->table($collection)->whereIn($column, $values); + + foreach ($extra as $key => $extraValue) { + $this->addWhere($query, $key, $extraValue); + } + + return $query->count(); + } + + /** + * Add a "where" clause to the given query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $key + * @param string $extraValue + * @return void + */ + protected function addWhere($query, $key, $extraValue) + { + if ($extraValue === 'NULL') { + $query->whereNull($key); + } elseif ($extraValue === 'NOT_NULL') { + $query->whereNotNull($key); + } else { + $query->where($key, $extraValue); + } + } + + /** + * Get a query builder for the given table. + * + * @param string $table + * @return \Illuminate\Database\Query\Builder + */ + protected function table($table) + { + return $this->db->connection($this->connection)->table($table)->useWritePdo(); + } + + /** + * Set the connection to be used. + * + * @param string $connection + * @return void + */ + public function setConnection($connection) + { + $this->connection = $connection; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Validation/Factory.php b/core/vendor/laravel/framework/src/Illuminate/Validation/Factory.php new file mode 100644 index 0000000..bbc5e22 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Validation/Factory.php @@ -0,0 +1,241 @@ +container = $container; + $this->translator = $translator; + } + + /** + * Create a new Validator instance. + * + * @param array $data + * @param array $rules + * @param array $messages + * @param array $customAttributes + * @return \Illuminate\Validation\Validator + */ + public function make(array $data, array $rules, array $messages = [], array $customAttributes = []) + { + // The presence verifier is responsible for checking the unique and exists data + // for the validator. It is behind an interface so that multiple versions of + // it may be written besides database. We'll inject it into the validator. + $validator = $this->resolve($data, $rules, $messages, $customAttributes); + + if (! is_null($this->verifier)) { + $validator->setPresenceVerifier($this->verifier); + } + + // Next we'll set the IoC container instance of the validator, which is used to + // resolve out class based validator extensions. If it is not set then these + // types of extensions will not be possible on these validation instances. + if (! is_null($this->container)) { + $validator->setContainer($this->container); + } + + $this->addExtensions($validator); + + return $validator; + } + + /** + * Add the extensions to a validator instance. + * + * @param \Illuminate\Validation\Validator $validator + * @return void + */ + protected function addExtensions(Validator $validator) + { + $validator->addExtensions($this->extensions); + + // Next, we will add the implicit extensions, which are similar to the required + // and accepted rule in that they are run even if the attributes is not in a + // array of data that is given to a validator instances via instantiation. + $implicit = $this->implicitExtensions; + + $validator->addImplicitExtensions($implicit); + + $validator->addReplacers($this->replacers); + + $validator->setFallbackMessages($this->fallbackMessages); + } + + /** + * Resolve a new Validator instance. + * + * @param array $data + * @param array $rules + * @param array $messages + * @param array $customAttributes + * @return \Illuminate\Validation\Validator + */ + protected function resolve(array $data, array $rules, array $messages, array $customAttributes) + { + if (is_null($this->resolver)) { + return new Validator($this->translator, $data, $rules, $messages, $customAttributes); + } + + return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes); + } + + /** + * Register a custom validator extension. + * + * @param string $rule + * @param \Closure|string $extension + * @param string $message + * @return void + */ + public function extend($rule, $extension, $message = null) + { + $this->extensions[$rule] = $extension; + + if ($message) { + $this->fallbackMessages[Str::snake($rule)] = $message; + } + } + + /** + * Register a custom implicit validator extension. + * + * @param string $rule + * @param \Closure|string $extension + * @param string $message + * @return void + */ + public function extendImplicit($rule, $extension, $message = null) + { + $this->implicitExtensions[$rule] = $extension; + + if ($message) { + $this->fallbackMessages[Str::snake($rule)] = $message; + } + } + + /** + * Register a custom implicit validator message replacer. + * + * @param string $rule + * @param \Closure|string $replacer + * @return void + */ + public function replacer($rule, $replacer) + { + $this->replacers[$rule] = $replacer; + } + + /** + * Set the Validator instance resolver. + * + * @param \Closure $resolver + * @return void + */ + public function resolver(Closure $resolver) + { + $this->resolver = $resolver; + } + + /** + * Get the Translator implementation. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getTranslator() + { + return $this->translator; + } + + /** + * Get the Presence Verifier implementation. + * + * @return \Illuminate\Validation\PresenceVerifierInterface + */ + public function getPresenceVerifier() + { + return $this->verifier; + } + + /** + * Set the Presence Verifier implementation. + * + * @param \Illuminate\Validation\PresenceVerifierInterface $presenceVerifier + * @return void + */ + public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier) + { + $this->verifier = $presenceVerifier; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Validation/PresenceVerifierInterface.php b/core/vendor/laravel/framework/src/Illuminate/Validation/PresenceVerifierInterface.php new file mode 100644 index 0000000..7449629 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Validation/PresenceVerifierInterface.php @@ -0,0 +1,30 @@ +getValidatorInstance(); + + if (! $this->passesAuthorization()) { + $this->failedAuthorization(); + } elseif (! $instance->passes()) { + $this->failedValidation($instance); + } + } + + /** + * Get the validator instance for the request. + * + * @return \Illuminate\Validation\Validator + */ + protected function getValidatorInstance() + { + return $this->validator(); + } + + /** + * Handle a failed validation attempt. + * + * @param \Illuminate\Validation\Validator $validator + * @return mixed + */ + protected function failedValidation(Validator $validator) + { + throw new ValidationException($validator); + } + + /** + * Determine if the request passes the authorization check. + * + * @return bool + */ + protected function passesAuthorization() + { + if (method_exists($this, 'authorize')) { + return $this->authorize(); + } + + return true; + } + + /** + * Handle a failed authorization attempt. + * + * @return mixed + */ + protected function failedAuthorization() + { + throw new UnauthorizedException; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Validation/ValidationServiceProvider.php b/core/vendor/laravel/framework/src/Illuminate/Validation/ValidationServiceProvider.php new file mode 100644 index 0000000..5eaf08f --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Validation/ValidationServiceProvider.php @@ -0,0 +1,68 @@ +registerValidationResolverHook(); + + $this->registerPresenceVerifier(); + + $this->registerValidationFactory(); + } + + /** + * Register the "ValidatesWhenResolved" container hook. + * + * @return void + */ + protected function registerValidationResolverHook() + { + $this->app->afterResolving(function (ValidatesWhenResolved $resolved) { + $resolved->validate(); + }); + } + + /** + * Register the validation factory. + * + * @return void + */ + protected function registerValidationFactory() + { + $this->app->singleton('validator', function ($app) { + $validator = new Factory($app['translator'], $app); + + // The validation presence verifier is responsible for determining the existence + // of values in a given data collection, typically a relational database or + // other persistent data stores. And it is used to check for uniqueness. + if (isset($app['validation.presence'])) { + $validator->setPresenceVerifier($app['validation.presence']); + } + + return $validator; + }); + } + + /** + * Register the database presence verifier. + * + * @return void + */ + protected function registerPresenceVerifier() + { + $this->app->singleton('validation.presence', function ($app) { + return new DatabasePresenceVerifier($app['db']); + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Validation/Validator.php b/core/vendor/laravel/framework/src/Illuminate/Validation/Validator.php new file mode 100644 index 0000000..9c5e74d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Validation/Validator.php @@ -0,0 +1,2696 @@ +translator = $translator; + $this->customMessages = $messages; + $this->data = $this->parseData($data); + $this->rules = $this->explodeRules($rules); + $this->customAttributes = $customAttributes; + } + + /** + * Parse the data and hydrate the files array. + * + * @param array $data + * @param string $arrayKey + * @return array + */ + protected function parseData(array $data, $arrayKey = null) + { + if (is_null($arrayKey)) { + $this->files = []; + } + + foreach ($data as $key => $value) { + $key = ($arrayKey) ? "$arrayKey.$key" : $key; + + // If this value is an instance of the HttpFoundation File class we will + // remove it from the data array and add it to the files array, which + // we use to conveniently separate out these files from other data. + if ($value instanceof File) { + $this->files[$key] = $value; + + unset($data[$key]); + } elseif (is_array($value)) { + $this->parseData($value, $key); + } + } + + return $data; + } + + /** + * Explode the rules into an array of rules. + * + * @param string|array $rules + * @return array + */ + protected function explodeRules($rules) + { + foreach ($rules as $key => &$rule) { + $rule = (is_string($rule)) ? explode('|', $rule) : $rule; + } + + return $rules; + } + + /** + * After an after validation callback. + * + * @param callable|string $callback + * @return $this + */ + public function after($callback) + { + $this->after[] = function () use ($callback) { + return call_user_func_array($callback, [$this]); + }; + + return $this; + } + + /** + * Add conditions to a given field based on a Closure. + * + * @param string $attribute + * @param string|array $rules + * @param callable $callback + * @return void + */ + public function sometimes($attribute, $rules, callable $callback) + { + $payload = new Fluent(array_merge($this->data, $this->files)); + + if (call_user_func($callback, $payload)) { + foreach ((array) $attribute as $key) { + $this->mergeRules($key, $rules); + } + } + } + + /** + * Define a set of rules that apply to each element in an array attribute. + * + * @param string $attribute + * @param string|array $rules + * @return void + * + * @throws \InvalidArgumentException + */ + public function each($attribute, $rules) + { + $data = Arr::get($this->data, $attribute); + + if (! is_array($data)) { + if ($this->hasRule($attribute, 'Array')) { + return; + } + + throw new InvalidArgumentException('Attribute for each() must be an array.'); + } + + foreach ($data as $dataKey => $dataValue) { + foreach ((array) $rules as $ruleKey => $ruleValue) { + if (! is_string($ruleKey)) { + $this->mergeRules("$attribute.$dataKey", $ruleValue); + } else { + $this->mergeRules("$attribute.$dataKey.$ruleKey", $ruleValue); + } + } + } + } + + /** + * Merge additional rules into a given attribute. + * + * @param string $attribute + * @param string|array $rules + * @return void + */ + public function mergeRules($attribute, $rules) + { + $current = isset($this->rules[$attribute]) ? $this->rules[$attribute] : []; + + $merge = head($this->explodeRules([$rules])); + + $this->rules[$attribute] = array_merge($current, $merge); + } + + /** + * Determine if the data passes the validation rules. + * + * @return bool + */ + public function passes() + { + $this->messages = new MessageBag; + + // We'll spin through each rule, validating the attributes attached to that + // rule. Any error messages will be added to the containers with each of + // the other error messages, returning true if we don't have messages. + foreach ($this->rules as $attribute => $rules) { + foreach ($rules as $rule) { + $this->validate($attribute, $rule); + } + } + + // Here we will spin through all of the "after" hooks on this validator and + // fire them off. This gives the callbacks a chance to perform all kinds + // of other validation that needs to get wrapped up in this operation. + foreach ($this->after as $after) { + call_user_func($after); + } + + return count($this->messages->all()) === 0; + } + + /** + * Determine if the data fails the validation rules. + * + * @return bool + */ + public function fails() + { + return ! $this->passes(); + } + + /** + * Validate a given attribute against a rule. + * + * @param string $attribute + * @param string $rule + * @return void + */ + protected function validate($attribute, $rule) + { + list($rule, $parameters) = $this->parseRule($rule); + + if ($rule == '') { + return; + } + + // We will get the value for the given attribute from the array of data and then + // verify that the attribute is indeed validatable. Unless the rule implies + // that the attribute is required, rules are not run for missing values. + $value = $this->getValue($attribute); + + $validatable = $this->isValidatable($rule, $attribute, $value); + + $method = "validate{$rule}"; + + if ($validatable && ! $this->$method($attribute, $value, $parameters, $this)) { + $this->addFailure($attribute, $rule, $parameters); + } + } + + /** + * Returns the data which was valid. + * + * @return array + */ + public function valid() + { + if (! $this->messages) { + $this->passes(); + } + + return array_diff_key($this->data, $this->messages()->toArray()); + } + + /** + * Returns the data which was invalid. + * + * @return array + */ + public function invalid() + { + if (! $this->messages) { + $this->passes(); + } + + return array_intersect_key($this->data, $this->messages()->toArray()); + } + + /** + * Get the value of a given attribute. + * + * @param string $attribute + * @return mixed + */ + protected function getValue($attribute) + { + if (! is_null($value = Arr::get($this->data, $attribute))) { + return $value; + } elseif (! is_null($value = Arr::get($this->files, $attribute))) { + return $value; + } + } + + /** + * Determine if the attribute is validatable. + * + * @param string $rule + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function isValidatable($rule, $attribute, $value) + { + return $this->presentOrRuleIsImplicit($rule, $attribute, $value) && + $this->passesOptionalCheck($attribute) && + $this->hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute); + } + + /** + * Determine if the field is present, or the rule implies required. + * + * @param string $rule + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function presentOrRuleIsImplicit($rule, $attribute, $value) + { + return $this->validateRequired($attribute, $value) || $this->isImplicit($rule); + } + + /** + * Determine if the attribute passes any optional check. + * + * @param string $attribute + * @return bool + */ + protected function passesOptionalCheck($attribute) + { + if ($this->hasRule($attribute, ['Sometimes'])) { + return array_key_exists($attribute, Arr::dot($this->data)) + || in_array($attribute, array_keys($this->data)) + || array_key_exists($attribute, $this->files); + } + + return true; + } + + /** + * Determine if a given rule implies the attribute is required. + * + * @param string $rule + * @return bool + */ + protected function isImplicit($rule) + { + return in_array($rule, $this->implicitRules); + } + + /** + * Determine if it's a necessary presence validation. + * + * This is to avoid possible database type comparison errors. + * + * @param string $rule + * @param string $attribute + * @return bool + */ + protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute) + { + return in_array($rule, ['Unique', 'Exists']) + ? ! $this->messages->has($attribute) : true; + } + + /** + * Add a failed rule and error message to the collection. + * + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return void + */ + protected function addFailure($attribute, $rule, $parameters) + { + $this->addError($attribute, $rule, $parameters); + + $this->failedRules[$attribute][$rule] = $parameters; + } + + /** + * Add an error message to the validator's collection of messages. + * + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return void + */ + protected function addError($attribute, $rule, $parameters) + { + $message = $this->getMessage($attribute, $rule); + + $message = $this->doReplacements($message, $attribute, $rule, $parameters); + + $this->messages->add($attribute, $message); + } + + /** + * "Validate" optional attributes. + * + * Always returns true, just lets us put sometimes in rules. + * + * @return bool + */ + protected function validateSometimes() + { + return true; + } + + /** + * Validate that a required attribute exists. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateRequired($attribute, $value) + { + if (is_null($value)) { + return false; + } elseif (is_string($value) && trim($value) === '') { + return false; + } elseif ((is_array($value) || $value instanceof Countable) && count($value) < 1) { + return false; + } elseif ($value instanceof File) { + return (string) $value->getPath() != ''; + } + + return true; + } + + /** + * Validate the given attribute is filled if it is present. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateFilled($attribute, $value) + { + if (array_key_exists($attribute, $this->data) || array_key_exists($attribute, $this->files)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Determine if any of the given attributes fail the required test. + * + * @param array $attributes + * @return bool + */ + protected function anyFailingRequired(array $attributes) + { + foreach ($attributes as $key) { + if (! $this->validateRequired($key, $this->getValue($key))) { + return true; + } + } + + return false; + } + + /** + * Determine if all of the given attributes fail the required test. + * + * @param array $attributes + * @return bool + */ + protected function allFailingRequired(array $attributes) + { + foreach ($attributes as $key) { + if ($this->validateRequired($key, $this->getValue($key))) { + return false; + } + } + + return true; + } + + /** + * Validate that an attribute exists when any other attribute exists. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredWith($attribute, $value, $parameters) + { + if (! $this->allFailingRequired($parameters)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when all other attributes exists. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredWithAll($attribute, $value, $parameters) + { + if (! $this->anyFailingRequired($parameters)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when another attribute does not. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredWithout($attribute, $value, $parameters) + { + if ($this->anyFailingRequired($parameters)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when all other attributes do not. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredWithoutAll($attribute, $value, $parameters) + { + if ($this->allFailingRequired($parameters)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when another attribute has a given value. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredIf($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'required_if'); + + $data = Arr::get($this->data, $parameters[0]); + + $values = array_slice($parameters, 1); + + if (in_array($data, $values)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when another attribute does not have a given value. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredUnless($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'required_unless'); + + $data = Arr::get($this->data, $parameters[0]); + + $values = array_slice($parameters, 1); + + if (! in_array($data, $values)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Get the number of attributes in a list that are present. + * + * @param array $attributes + * @return int + */ + protected function getPresentCount($attributes) + { + $count = 0; + + foreach ($attributes as $key) { + if (Arr::get($this->data, $key) || Arr::get($this->files, $key)) { + $count++; + } + } + + return $count; + } + + /** + * Validate that an attribute has a matching confirmation. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateConfirmed($attribute, $value) + { + return $this->validateSame($attribute, $value, [$attribute.'_confirmation']); + } + + /** + * Validate that two attributes match. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateSame($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'same'); + + $other = Arr::get($this->data, $parameters[0]); + + return isset($other) && $value === $other; + } + + /** + * Validate that an attribute is different from another attribute. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateDifferent($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'different'); + + $other = Arr::get($this->data, $parameters[0]); + + return isset($other) && $value !== $other; + } + + /** + * Validate that an attribute was "accepted". + * + * This validation rule implies the attribute is "required". + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateAccepted($attribute, $value) + { + $acceptable = ['yes', 'on', '1', 1, true, 'true']; + + return $this->validateRequired($attribute, $value) && in_array($value, $acceptable, true); + } + + /** + * Validate that an attribute is an array. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateArray($attribute, $value) + { + return is_array($value); + } + + /** + * Validate that an attribute is a boolean. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateBoolean($attribute, $value) + { + $acceptable = [true, false, 0, 1, '0', '1']; + + return in_array($value, $acceptable, true); + } + + /** + * Validate that an attribute is an integer. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateInteger($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_INT) !== false; + } + + /** + * Validate that an attribute is numeric. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateNumeric($attribute, $value) + { + return is_numeric($value); + } + + /** + * Validate that an attribute is a string. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateString($attribute, $value) + { + return is_string($value); + } + + /** + * Validate the attribute is a valid JSON string. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateJson($attribute, $value) + { + json_decode($value); + + return json_last_error() === JSON_ERROR_NONE; + } + + /** + * Validate that an attribute has a given number of digits. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateDigits($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'digits'); + + return $this->validateNumeric($attribute, $value) + && strlen((string) $value) == $parameters[0]; + } + + /** + * Validate that an attribute is between a given number of digits. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateDigitsBetween($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'digits_between'); + + $length = strlen((string) $value); + + return $this->validateNumeric($attribute, $value) + && $length >= $parameters[0] && $length <= $parameters[1]; + } + + /** + * Validate the size of an attribute. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateSize($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'size'); + + return $this->getSize($attribute, $value) == $parameters[0]; + } + + /** + * Validate the size of an attribute is between a set of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateBetween($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'between'); + + $size = $this->getSize($attribute, $value); + + return $size >= $parameters[0] && $size <= $parameters[1]; + } + + /** + * Validate the size of an attribute is greater than a minimum value. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateMin($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'min'); + + return $this->getSize($attribute, $value) >= $parameters[0]; + } + + /** + * Validate the size of an attribute is less than a maximum value. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateMax($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'max'); + + if ($value instanceof UploadedFile && ! $value->isValid()) { + return false; + } + + return $this->getSize($attribute, $value) <= $parameters[0]; + } + + /** + * Get the size of an attribute. + * + * @param string $attribute + * @param mixed $value + * @return mixed + */ + protected function getSize($attribute, $value) + { + $hasNumeric = $this->hasRule($attribute, $this->numericRules); + + // This method will determine if the attribute is a number, string, or file and + // return the proper size accordingly. If it is a number, then number itself + // is the size. If it is a file, we take kilobytes, and for a string the + // entire length of the string will be considered the attribute size. + if (is_numeric($value) && $hasNumeric) { + return $value; + } elseif (is_array($value)) { + return count($value); + } elseif ($value instanceof File) { + return $value->getSize() / 1024; + } + + return mb_strlen($value); + } + + /** + * Validate an attribute is contained within a list of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateIn($attribute, $value, $parameters) + { + if (is_array($value) && $this->hasRule($attribute, 'Array')) { + return count(array_diff($value, $parameters)) == 0; + } + + return ! is_array($value) && in_array((string) $value, $parameters); + } + + /** + * Validate an attribute is not contained within a list of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateNotIn($attribute, $value, $parameters) + { + return ! $this->validateIn($attribute, $value, $parameters); + } + + /** + * Validate the uniqueness of an attribute value on a given database table. + * + * If a database column is not specified, the attribute will be used. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateUnique($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'unique'); + + list($connection, $table) = $this->parseTable($parameters[0]); + + // The second parameter position holds the name of the column that needs to + // be verified as unique. If this parameter isn't specified we will just + // assume that this column to be verified shares the attribute's name. + $column = isset($parameters[1]) ? $parameters[1] : $attribute; + + list($idColumn, $id) = [null, null]; + + if (isset($parameters[2])) { + list($idColumn, $id) = $this->getUniqueIds($parameters); + + if (strtolower($id) == 'null') { + $id = null; + } + } + + // The presence verifier is responsible for counting rows within this store + // mechanism which might be a relational database or any other permanent + // data store like Redis, etc. We will use it to determine uniqueness. + $verifier = $this->getPresenceVerifier(); + + if (! is_null($connection)) { + $verifier->setConnection($connection); + } + + $extra = $this->getUniqueExtra($parameters); + + return $verifier->getCount( + $table, $column, $value, $id, $idColumn, $extra + + ) == 0; + } + + /** + * Parse the connection / table for the unique / exists rules. + * + * @param string $table + * @return array + */ + protected function parseTable($table) + { + return Str::contains($table, '.') ? explode('.', $table, 2) : [null, $table]; + } + + /** + * Get the excluded ID column and value for the unique rule. + * + * @param array $parameters + * @return array + */ + protected function getUniqueIds($parameters) + { + $idColumn = isset($parameters[3]) ? $parameters[3] : 'id'; + + return [$idColumn, $parameters[2]]; + } + + /** + * Get the extra conditions for a unique rule. + * + * @param array $parameters + * @return array + */ + protected function getUniqueExtra($parameters) + { + if (isset($parameters[4])) { + return $this->getExtraConditions(array_slice($parameters, 4)); + } + + return []; + } + + /** + * Validate the existence of an attribute value in a database table. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateExists($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'exists'); + + list($connection, $table) = $this->parseTable($parameters[0]); + + // The second parameter position holds the name of the column that should be + // verified as existing. If this parameter is not specified we will guess + // that the columns being "verified" shares the given attribute's name. + $column = isset($parameters[1]) ? $parameters[1] : $attribute; + + $expected = (is_array($value)) ? count($value) : 1; + + return $this->getExistCount($connection, $table, $column, $value, $parameters) >= $expected; + } + + /** + * Get the number of records that exist in storage. + * + * @param mixed $connection + * @param string $table + * @param string $column + * @param mixed $value + * @param array $parameters + * @return int + */ + protected function getExistCount($connection, $table, $column, $value, $parameters) + { + $verifier = $this->getPresenceVerifier(); + + if (! is_null($connection)) { + $verifier->setConnection($connection); + } + + $extra = $this->getExtraExistConditions($parameters); + + if (is_array($value)) { + return $verifier->getMultiCount($table, $column, $value, $extra); + } + + return $verifier->getCount($table, $column, $value, null, null, $extra); + } + + /** + * Get the extra exist conditions. + * + * @param array $parameters + * @return array + */ + protected function getExtraExistConditions(array $parameters) + { + return $this->getExtraConditions(array_values(array_slice($parameters, 2))); + } + + /** + * Get the extra conditions for a unique / exists rule. + * + * @param array $segments + * @return array + */ + protected function getExtraConditions(array $segments) + { + $extra = []; + + $count = count($segments); + + for ($i = 0; $i < $count; $i = $i + 2) { + $extra[$segments[$i]] = $segments[$i + 1]; + } + + return $extra; + } + + /** + * Validate that an attribute is a valid IP. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateIp($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_IP) !== false; + } + + /** + * Validate that an attribute is a valid e-mail address. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateEmail($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; + } + + /** + * Validate that an attribute is a valid URL. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateUrl($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_URL) !== false; + } + + /** + * Validate that an attribute is an active URL. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateActiveUrl($attribute, $value) + { + if (! is_string($value)) { + return false; + } + + if ($url = parse_url($value, PHP_URL_HOST)) { + return count(dns_get_record($url, DNS_A | DNS_AAAA)) > 0; + } + + return false; + } + + /** + * Validate the MIME type of a file is an image MIME type. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateImage($attribute, $value) + { + return $this->validateMimes($attribute, $value, ['jpeg', 'png', 'gif', 'bmp', 'svg']); + } + + /** + * Validate the guessed extension of a file upload is in a set of file extensions. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateMimes($attribute, $value, $parameters) + { + if (! $this->isAValidFileInstance($value)) { + return false; + } + + return $value->getPath() != '' && in_array($value->guessExtension(), $parameters); + } + + /** + * Validate the MIME type of a file upload attribute is in a set of MIME types. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateMimetypes($attribute, $value, $parameters) + { + if (! $this->isAValidFileInstance($value)) { + return false; + } + + return $value->getPath() != '' && in_array($value->getMimeType(), $parameters); + } + + /** + * Check that the given value is a valid file instance. + * + * @param mixed $value + * @return bool + */ + protected function isAValidFileInstance($value) + { + if ($value instanceof UploadedFile && ! $value->isValid()) { + return false; + } + + return $value instanceof File; + } + + /** + * Validate that an attribute contains only alphabetic characters. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateAlpha($attribute, $value) + { + return is_string($value) && preg_match('/^[\pL\pM]+$/u', $value); + } + + /** + * Validate that an attribute contains only alpha-numeric characters. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateAlphaNum($attribute, $value) + { + if (! is_string($value) && ! is_numeric($value)) { + return false; + } + + return preg_match('/^[\pL\pM\pN]+$/u', $value); + } + + /** + * Validate that an attribute contains only alpha-numeric characters, dashes, and underscores. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateAlphaDash($attribute, $value) + { + if (! is_string($value) && ! is_numeric($value)) { + return false; + } + + return preg_match('/^[\pL\pM\pN_-]+$/u', $value); + } + + /** + * Validate that an attribute passes a regular expression check. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateRegex($attribute, $value, $parameters) + { + if (! is_string($value) && ! is_numeric($value)) { + return false; + } + + $this->requireParameterCount(1, $parameters, 'regex'); + + return preg_match($parameters[0], $value); + } + + /** + * Validate that an attribute is a valid date. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateDate($attribute, $value) + { + if ($value instanceof DateTime) { + return true; + } + + if ((! is_string($value) && ! is_numeric($value)) || strtotime($value) === false) { + return false; + } + + $date = date_parse($value); + + return checkdate($date['month'], $date['day'], $date['year']); + } + + /** + * Validate that an attribute matches a date format. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateDateFormat($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'date_format'); + + if (! is_string($value) && ! is_numeric($value)) { + return false; + } + + $parsed = date_parse_from_format($parameters[0], $value); + + return $parsed['error_count'] === 0 && $parsed['warning_count'] === 0; + } + + /** + * Validate the date is before a given date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateBefore($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'before'); + + if (! is_string($value) && ! is_numeric($value) && ! $value instanceof Carbon) { + return false; + } + + if ($format = $this->getDateFormat($attribute)) { + return $this->validateBeforeWithFormat($format, $value, $parameters); + } + + if (! ($date = strtotime($parameters[0]))) { + return strtotime($value) < strtotime($this->getValue($parameters[0])); + } + + return strtotime($value) < $date; + } + + /** + * Validate the date is before a given date with a given format. + * + * @param string $format + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateBeforeWithFormat($format, $value, $parameters) + { + $param = $this->getValue($parameters[0]) ?: $parameters[0]; + + return $this->checkDateTimeOrder($format, $value, $param); + } + + /** + * Validate the date is after a given date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateAfter($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'after'); + + if (! is_string($value) && ! is_numeric($value) && ! $value instanceof Carbon) { + return false; + } + + if ($format = $this->getDateFormat($attribute)) { + return $this->validateAfterWithFormat($format, $value, $parameters); + } + + if (! ($date = strtotime($parameters[0]))) { + return strtotime($value) > strtotime($this->getValue($parameters[0])); + } + + return strtotime($value) > $date; + } + + /** + * Validate the date is after a given date with a given format. + * + * @param string $format + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateAfterWithFormat($format, $value, $parameters) + { + $param = $this->getValue($parameters[0]) ?: $parameters[0]; + + return $this->checkDateTimeOrder($format, $param, $value); + } + + /** + * Given two date/time strings, check that one is after the other. + * + * @param string $format + * @param string $before + * @param string $after + * @return bool + */ + protected function checkDateTimeOrder($format, $before, $after) + { + $before = $this->getDateTimeWithOptionalFormat($format, $before); + + $after = $this->getDateTimeWithOptionalFormat($format, $after); + + return ($before && $after) && ($after > $before); + } + + /** + * Get a DateTime instance from a string. + * + * @param string $format + * @param string $value + * @return \DateTime|null + */ + protected function getDateTimeWithOptionalFormat($format, $value) + { + $date = DateTime::createFromFormat($format, $value); + + if ($date) { + return $date; + } + + try { + return new DateTime($value); + } catch (Exception $e) { + // + } + } + + /** + * Validate that an attribute is a valid timezone. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateTimezone($attribute, $value) + { + try { + new DateTimeZone($value); + } catch (Exception $e) { + return false; + } + + return true; + } + + /** + * Get the date format for an attribute if it has one. + * + * @param string $attribute + * @return string|null + */ + protected function getDateFormat($attribute) + { + if ($result = $this->getRule($attribute, 'DateFormat')) { + return $result[1][0]; + } + } + + /** + * Get the validation message for an attribute and rule. + * + * @param string $attribute + * @param string $rule + * @return string + */ + protected function getMessage($attribute, $rule) + { + $lowerRule = Str::snake($rule); + + $inlineMessage = $this->getInlineMessage($attribute, $lowerRule); + + // First we will retrieve the custom message for the validation rule if one + // exists. If a custom validation message is being used we'll return the + // custom message, otherwise we'll keep searching for a valid message. + if (! is_null($inlineMessage)) { + return $inlineMessage; + } + + $customKey = "validation.custom.{$attribute}.{$lowerRule}"; + + $customMessage = $this->translator->trans($customKey); + + // First we check for a custom defined validation message for the attribute + // and rule. This allows the developer to specify specific messages for + // only some attributes and rules that need to get specially formed. + if ($customMessage !== $customKey) { + return $customMessage; + } + + // If the rule being validated is a "size" rule, we will need to gather the + // specific error message for the type of attribute being validated such + // as a number, file or string which all have different message types. + elseif (in_array($rule, $this->sizeRules)) { + return $this->getSizeMessage($attribute, $rule); + } + + // Finally, if no developer specified messages have been set, and no other + // special messages apply for this rule, we will just pull the default + // messages out of the translator service for this validation rule. + $key = "validation.{$lowerRule}"; + + if ($key != ($value = $this->translator->trans($key))) { + return $value; + } + + return $this->getInlineMessage( + $attribute, $lowerRule, $this->fallbackMessages + ) ?: $key; + } + + /** + * Get the inline message for a rule if it exists. + * + * @param string $attribute + * @param string $lowerRule + * @param array $source + * @return string|null + */ + protected function getInlineMessage($attribute, $lowerRule, $source = null) + { + $source = $source ?: $this->customMessages; + + $keys = ["{$attribute}.{$lowerRule}", $lowerRule]; + + // First we will check for a custom message for an attribute specific rule + // message for the fields, then we will check for a general custom line + // that is not attribute specific. If we find either we'll return it. + foreach ($keys as $key) { + if (isset($source[$key])) { + return $source[$key]; + } + } + } + + /** + * Get the proper error message for an attribute and size rule. + * + * @param string $attribute + * @param string $rule + * @return string + */ + protected function getSizeMessage($attribute, $rule) + { + $lowerRule = Str::snake($rule); + + // There are three different types of size validations. The attribute may be + // either a number, file, or string so we will check a few things to know + // which type of value it is and return the correct line for that type. + $type = $this->getAttributeType($attribute); + + $key = "validation.{$lowerRule}.{$type}"; + + return $this->translator->trans($key); + } + + /** + * Get the data type of the given attribute. + * + * @param string $attribute + * @return string + */ + protected function getAttributeType($attribute) + { + // We assume that the attributes present in the file array are files so that + // means that if the attribute does not have a numeric rule and the files + // list doesn't have it we'll just consider it a string by elimination. + if ($this->hasRule($attribute, $this->numericRules)) { + return 'numeric'; + } elseif ($this->hasRule($attribute, ['Array'])) { + return 'array'; + } elseif (array_key_exists($attribute, $this->files)) { + return 'file'; + } + + return 'string'; + } + + /** + * Replace all error message place-holders with actual values. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function doReplacements($message, $attribute, $rule, $parameters) + { + $message = str_replace(':attribute', $this->getAttribute($attribute), $message); + + if (isset($this->replacers[Str::snake($rule)])) { + $message = $this->callReplacer($message, $attribute, Str::snake($rule), $parameters); + } elseif (method_exists($this, $replacer = "replace{$rule}")) { + $message = $this->$replacer($message, $attribute, $rule, $parameters); + } + + return $message; + } + + /** + * Transform an array of attributes to their displayable form. + * + * @param array $values + * @return array + */ + protected function getAttributeList(array $values) + { + $attributes = []; + + // For each attribute in the list we will simply get its displayable form as + // this is convenient when replacing lists of parameters like some of the + // replacement functions do when formatting out the validation message. + foreach ($values as $key => $value) { + $attributes[$key] = $this->getAttribute($value); + } + + return $attributes; + } + + /** + * Get the displayable name of the attribute. + * + * @param string $attribute + * @return string + */ + protected function getAttribute($attribute) + { + // The developer may dynamically specify the array of custom attributes + // on this Validator instance. If the attribute exists in this array + // it takes precedence over all other ways we can pull attributes. + if (isset($this->customAttributes[$attribute])) { + return $this->customAttributes[$attribute]; + } + + $key = "validation.attributes.{$attribute}"; + + // We allow for the developer to specify language lines for each of the + // attributes allowing for more displayable counterparts of each of + // the attributes. This provides the ability for simple formats. + if (($line = $this->translator->trans($key)) !== $key) { + return $line; + } + + // If no language line has been specified for the attribute all of the + // underscores are removed from the attribute name and that will be + // used as default versions of the attribute's displayable names. + return str_replace('_', ' ', Str::snake($attribute)); + } + + /** + * Get the displayable name of the value. + * + * @param string $attribute + * @param mixed $value + * @return string + */ + public function getDisplayableValue($attribute, $value) + { + if (isset($this->customValues[$attribute][$value])) { + return $this->customValues[$attribute][$value]; + } + + $key = "validation.values.{$attribute}.{$value}"; + + if (($line = $this->translator->trans($key)) !== $key) { + return $line; + } + + return $value; + } + + /** + * Replace all place-holders for the between rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceBetween($message, $attribute, $rule, $parameters) + { + return str_replace([':min', ':max'], $parameters, $message); + } + + /** + * Replace all place-holders for the digits rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceDigits($message, $attribute, $rule, $parameters) + { + return str_replace(':digits', $parameters[0], $message); + } + + /** + * Replace all place-holders for the digits (between) rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceDigitsBetween($message, $attribute, $rule, $parameters) + { + return $this->replaceBetween($message, $attribute, $rule, $parameters); + } + + /** + * Replace all place-holders for the size rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceSize($message, $attribute, $rule, $parameters) + { + return str_replace(':size', $parameters[0], $message); + } + + /** + * Replace all place-holders for the min rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceMin($message, $attribute, $rule, $parameters) + { + return str_replace(':min', $parameters[0], $message); + } + + /** + * Replace all place-holders for the max rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceMax($message, $attribute, $rule, $parameters) + { + return str_replace(':max', $parameters[0], $message); + } + + /** + * Replace all place-holders for the in rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceIn($message, $attribute, $rule, $parameters) + { + foreach ($parameters as &$parameter) { + $parameter = $this->getDisplayableValue($attribute, $parameter); + } + + return str_replace(':values', implode(', ', $parameters), $message); + } + + /** + * Replace all place-holders for the not_in rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceNotIn($message, $attribute, $rule, $parameters) + { + return $this->replaceIn($message, $attribute, $rule, $parameters); + } + + /** + * Replace all place-holders for the mimes rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceMimes($message, $attribute, $rule, $parameters) + { + return str_replace(':values', implode(', ', $parameters), $message); + } + + /** + * Replace all place-holders for the required_with rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredWith($message, $attribute, $rule, $parameters) + { + $parameters = $this->getAttributeList($parameters); + + return str_replace(':values', implode(' / ', $parameters), $message); + } + + /** + * Replace all place-holders for the required_with_all rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredWithAll($message, $attribute, $rule, $parameters) + { + return $this->replaceRequiredWith($message, $attribute, $rule, $parameters); + } + + /** + * Replace all place-holders for the required_without rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredWithout($message, $attribute, $rule, $parameters) + { + return $this->replaceRequiredWith($message, $attribute, $rule, $parameters); + } + + /** + * Replace all place-holders for the required_without_all rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredWithoutAll($message, $attribute, $rule, $parameters) + { + return $this->replaceRequiredWith($message, $attribute, $rule, $parameters); + } + + /** + * Replace all place-holders for the required_if rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredIf($message, $attribute, $rule, $parameters) + { + $parameters[1] = $this->getDisplayableValue($parameters[0], Arr::get($this->data, $parameters[0])); + + $parameters[0] = $this->getAttribute($parameters[0]); + + return str_replace([':other', ':value'], $parameters, $message); + } + + /** + * Replace all place-holders for the required_unless rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredUnless($message, $attribute, $rule, $parameters) + { + $other = $this->getAttribute(array_shift($parameters)); + + return str_replace([':other', ':values'], [$other, implode(', ', $parameters)], $message); + } + + /** + * Replace all place-holders for the same rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceSame($message, $attribute, $rule, $parameters) + { + return str_replace(':other', $this->getAttribute($parameters[0]), $message); + } + + /** + * Replace all place-holders for the different rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceDifferent($message, $attribute, $rule, $parameters) + { + return $this->replaceSame($message, $attribute, $rule, $parameters); + } + + /** + * Replace all place-holders for the date_format rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceDateFormat($message, $attribute, $rule, $parameters) + { + return str_replace(':format', $parameters[0], $message); + } + + /** + * Replace all place-holders for the before rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceBefore($message, $attribute, $rule, $parameters) + { + if (! (strtotime($parameters[0]))) { + return str_replace(':date', $this->getAttribute($parameters[0]), $message); + } + + return str_replace(':date', $parameters[0], $message); + } + + /** + * Replace all place-holders for the after rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceAfter($message, $attribute, $rule, $parameters) + { + return $this->replaceBefore($message, $attribute, $rule, $parameters); + } + + /** + * Determine if the given attribute has a rule in the given set. + * + * @param string $attribute + * @param string|array $rules + * @return bool + */ + protected function hasRule($attribute, $rules) + { + return ! is_null($this->getRule($attribute, $rules)); + } + + /** + * Get a rule and its parameters for a given attribute. + * + * @param string $attribute + * @param string|array $rules + * @return array|null + */ + protected function getRule($attribute, $rules) + { + if (! array_key_exists($attribute, $this->rules)) { + return; + } + + $rules = (array) $rules; + + foreach ($this->rules[$attribute] as $rule) { + list($rule, $parameters) = $this->parseRule($rule); + + if (in_array($rule, $rules)) { + return [$rule, $parameters]; + } + } + } + + /** + * Extract the rule name and parameters from a rule. + * + * @param array|string $rules + * @return array + */ + protected function parseRule($rules) + { + if (is_array($rules)) { + $rules = $this->parseArrayRule($rules); + } else { + $rules = $this->parseStringRule($rules); + } + + $rules[0] = $this->normalizeRule($rules[0]); + + return $rules; + } + + /** + * Parse an array based rule. + * + * @param array $rules + * @return array + */ + protected function parseArrayRule(array $rules) + { + return [Str::studly(trim(Arr::get($rules, 0))), array_slice($rules, 1)]; + } + + /** + * Parse a string based rule. + * + * @param string $rules + * @return array + */ + protected function parseStringRule($rules) + { + $parameters = []; + + // The format for specifying validation rules and parameters follows an + // easy {rule}:{parameters} formatting convention. For instance the + // rule "Max:3" states that the value may only be three letters. + if (strpos($rules, ':') !== false) { + list($rules, $parameter) = explode(':', $rules, 2); + + $parameters = $this->parseParameters($rules, $parameter); + } + + return [Str::studly(trim($rules)), $parameters]; + } + + /** + * Parse a parameter list. + * + * @param string $rule + * @param string $parameter + * @return array + */ + protected function parseParameters($rule, $parameter) + { + if (strtolower($rule) == 'regex') { + return [$parameter]; + } + + return str_getcsv($parameter); + } + + /** + * Normalizes a rule so that we can accept short types. + * + * @param string $rule + * @return string + */ + protected function normalizeRule($rule) + { + switch ($rule) { + case 'Int': + return 'Integer'; + case 'Bool': + return 'Boolean'; + default: + return $rule; + } + } + + /** + * Get the array of custom validator extensions. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Register an array of custom validator extensions. + * + * @param array $extensions + * @return void + */ + public function addExtensions(array $extensions) + { + if ($extensions) { + $keys = array_map('\Illuminate\Support\Str::snake', array_keys($extensions)); + + $extensions = array_combine($keys, array_values($extensions)); + } + + $this->extensions = array_merge($this->extensions, $extensions); + } + + /** + * Register an array of custom implicit validator extensions. + * + * @param array $extensions + * @return void + */ + public function addImplicitExtensions(array $extensions) + { + $this->addExtensions($extensions); + + foreach ($extensions as $rule => $extension) { + $this->implicitRules[] = Str::studly($rule); + } + } + + /** + * Register a custom validator extension. + * + * @param string $rule + * @param \Closure|string $extension + * @return void + */ + public function addExtension($rule, $extension) + { + $this->extensions[Str::snake($rule)] = $extension; + } + + /** + * Register a custom implicit validator extension. + * + * @param string $rule + * @param \Closure|string $extension + * @return void + */ + public function addImplicitExtension($rule, $extension) + { + $this->addExtension($rule, $extension); + + $this->implicitRules[] = Str::studly($rule); + } + + /** + * Get the array of custom validator message replacers. + * + * @return array + */ + public function getReplacers() + { + return $this->replacers; + } + + /** + * Register an array of custom validator message replacers. + * + * @param array $replacers + * @return void + */ + public function addReplacers(array $replacers) + { + if ($replacers) { + $keys = array_map('\Illuminate\Support\Str::snake', array_keys($replacers)); + + $replacers = array_combine($keys, array_values($replacers)); + } + + $this->replacers = array_merge($this->replacers, $replacers); + } + + /** + * Register a custom validator message replacer. + * + * @param string $rule + * @param \Closure|string $replacer + * @return void + */ + public function addReplacer($rule, $replacer) + { + $this->replacers[Str::snake($rule)] = $replacer; + } + + /** + * Get the data under validation. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Set the data under validation. + * + * @param array $data + * @return void + */ + public function setData(array $data) + { + $this->data = $this->parseData($data); + } + + /** + * Get the validation rules. + * + * @return array + */ + public function getRules() + { + return $this->rules; + } + + /** + * Set the validation rules. + * + * @param array $rules + * @return $this + */ + public function setRules(array $rules) + { + $this->rules = $this->explodeRules($rules); + + return $this; + } + + /** + * Set the custom attributes on the validator. + * + * @param array $attributes + * @return $this + */ + public function setAttributeNames(array $attributes) + { + $this->customAttributes = $attributes; + + return $this; + } + + /** + * Set the custom values on the validator. + * + * @param array $values + * @return $this + */ + public function setValueNames(array $values) + { + $this->customValues = $values; + + return $this; + } + + /** + * Get the files under validation. + * + * @return array + */ + public function getFiles() + { + return $this->files; + } + + /** + * Set the files under validation. + * + * @param array $files + * @return $this + */ + public function setFiles(array $files) + { + $this->files = $files; + + return $this; + } + + /** + * Get the Presence Verifier implementation. + * + * @return \Illuminate\Validation\PresenceVerifierInterface + * + * @throws \RuntimeException + */ + public function getPresenceVerifier() + { + if (! isset($this->presenceVerifier)) { + throw new RuntimeException('Presence verifier has not been set.'); + } + + return $this->presenceVerifier; + } + + /** + * Set the Presence Verifier implementation. + * + * @param \Illuminate\Validation\PresenceVerifierInterface $presenceVerifier + * @return void + */ + public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier) + { + $this->presenceVerifier = $presenceVerifier; + } + + /** + * Get the Translator implementation. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getTranslator() + { + return $this->translator; + } + + /** + * Set the Translator implementation. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @return void + */ + public function setTranslator(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + /** + * Get the custom messages for the validator. + * + * @return array + */ + public function getCustomMessages() + { + return $this->customMessages; + } + + /** + * Set the custom messages for the validator. + * + * @param array $messages + * @return void + */ + public function setCustomMessages(array $messages) + { + $this->customMessages = array_merge($this->customMessages, $messages); + } + + /** + * Get the custom attributes used by the validator. + * + * @return array + */ + public function getCustomAttributes() + { + return $this->customAttributes; + } + + /** + * Add custom attributes to the validator. + * + * @param array $customAttributes + * @return $this + */ + public function addCustomAttributes(array $customAttributes) + { + $this->customAttributes = array_merge($this->customAttributes, $customAttributes); + + return $this; + } + + /** + * Get the custom values for the validator. + * + * @return array + */ + public function getCustomValues() + { + return $this->customValues; + } + + /** + * Add the custom values for the validator. + * + * @param array $customValues + * @return $this + */ + public function addCustomValues(array $customValues) + { + $this->customValues = array_merge($this->customValues, $customValues); + + return $this; + } + + /** + * Get the fallback messages for the validator. + * + * @return array + */ + public function getFallbackMessages() + { + return $this->fallbackMessages; + } + + /** + * Set the fallback messages for the validator. + * + * @param array $messages + * @return void + */ + public function setFallbackMessages(array $messages) + { + $this->fallbackMessages = $messages; + } + + /** + * Get the failed validation rules. + * + * @return array + */ + public function failed() + { + return $this->failedRules; + } + + /** + * Get the message container for the validator. + * + * @return \Illuminate\Support\MessageBag + */ + public function messages() + { + if (! $this->messages) { + $this->passes(); + } + + return $this->messages; + } + + /** + * An alternative more semantic shortcut to the message container. + * + * @return \Illuminate\Support\MessageBag + */ + public function errors() + { + return $this->messages(); + } + + /** + * Get the messages for the instance. + * + * @return \Illuminate\Support\MessageBag + */ + public function getMessageBag() + { + return $this->messages(); + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + + /** + * Call a custom validator extension. + * + * @param string $rule + * @param array $parameters + * @return bool|null + */ + protected function callExtension($rule, $parameters) + { + $callback = $this->extensions[$rule]; + + if ($callback instanceof Closure) { + return call_user_func_array($callback, $parameters); + } elseif (is_string($callback)) { + return $this->callClassBasedExtension($callback, $parameters); + } + } + + /** + * Call a class based validator extension. + * + * @param string $callback + * @param array $parameters + * @return bool + */ + protected function callClassBasedExtension($callback, $parameters) + { + list($class, $method) = explode('@', $callback); + + return call_user_func_array([$this->container->make($class), $method], $parameters); + } + + /** + * Call a custom validator message replacer. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string|null + */ + protected function callReplacer($message, $attribute, $rule, $parameters) + { + $callback = $this->replacers[$rule]; + + if ($callback instanceof Closure) { + return call_user_func_array($callback, func_get_args()); + } elseif (is_string($callback)) { + return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters); + } + } + + /** + * Call a class based validator message replacer. + * + * @param string $callback + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters) + { + list($class, $method) = explode('@', $callback); + + return call_user_func_array([$this->container->make($class), $method], array_slice(func_get_args(), 1)); + } + + /** + * Require a certain number of parameters to be present. + * + * @param int $count + * @param array $parameters + * @param string $rule + * @return void + * @throws \InvalidArgumentException + */ + protected function requireParameterCount($count, $parameters, $rule) + { + if (count($parameters) < $count) { + throw new InvalidArgumentException("Validation rule $rule requires at least $count parameters."); + } + } + + /** + * Handle dynamic calls to class methods. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + $rule = Str::snake(substr($method, 8)); + + if (isset($this->extensions[$rule])) { + return $this->callExtension($rule, $parameters); + } + + throw new BadMethodCallException("Method [$method] does not exist."); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/Validation/composer.json b/core/vendor/laravel/framework/src/Illuminate/Validation/composer.json new file mode 100644 index 0000000..3733144 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/Validation/composer.json @@ -0,0 +1,38 @@ +{ + "name": "illuminate/validation", + "description": "The Illuminate Validation package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/container": "5.1.*", + "illuminate/contracts": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/http-foundation": "2.7.*", + "symfony/translation": "2.7.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\Validation\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "suggest": { + "illuminate/database": "Required to use the database presence verifier (5.1.*)." + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php b/core/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php new file mode 100644 index 0000000..c257a82 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php @@ -0,0 +1,889 @@ +setPath($path); + } + + $contents = $this->compileString($this->files->get($this->getPath())); + + if (! is_null($this->cachePath)) { + $this->files->put($this->getCompiledPath($this->getPath()), $contents); + } + } + + /** + * Get the path currently being compiled. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set the path currently being compiled. + * + * @param string $path + * @return void + */ + public function setPath($path) + { + $this->path = $path; + } + + /** + * Compile the given Blade template contents. + * + * @param string $value + * @return string + */ + public function compileString($value) + { + $result = ''; + + $this->footer = []; + + // Here we will loop through all of the tokens returned by the Zend lexer and + // parse each one into the corresponding valid PHP. We will then have this + // template as the correctly rendered PHP that can be rendered natively. + foreach (token_get_all($value) as $token) { + $result .= is_array($token) ? $this->parseToken($token) : $token; + } + + // If there are any footer lines that need to get added to a template we will + // add them here at the end of the template. This gets used mainly for the + // template inheritance via the extends keyword that should be appended. + if (count($this->footer) > 0) { + $result = ltrim($result, PHP_EOL) + .PHP_EOL.implode(PHP_EOL, array_reverse($this->footer)); + } + + return $result; + } + + /** + * Parse the tokens from the template. + * + * @param array $token + * @return string + */ + protected function parseToken($token) + { + list($id, $content) = $token; + + if ($id == T_INLINE_HTML) { + foreach ($this->compilers as $type) { + $content = $this->{"compile{$type}"}($content); + } + } + + return $content; + } + + /** + * Execute the user defined extensions. + * + * @param string $value + * @return string + */ + protected function compileExtensions($value) + { + foreach ($this->extensions as $compiler) { + $value = call_user_func($compiler, $value, $this); + } + + return $value; + } + + /** + * Compile Blade comments into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileComments($value) + { + $pattern = sprintf('/%s--(.*?)--%s/s', $this->contentTags[0], $this->contentTags[1]); + + return preg_replace($pattern, '', $value); + } + + /** + * Compile Blade echos into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileEchos($value) + { + foreach ($this->getEchoMethods() as $method => $length) { + $value = $this->$method($value); + } + + return $value; + } + + /** + * Get the echo methods in the proper order for compilation. + * + * @return array + */ + protected function getEchoMethods() + { + $methods = [ + 'compileRawEchos' => strlen(stripcslashes($this->rawTags[0])), + 'compileEscapedEchos' => strlen(stripcslashes($this->escapedTags[0])), + 'compileRegularEchos' => strlen(stripcslashes($this->contentTags[0])), + ]; + + uksort($methods, function ($method1, $method2) use ($methods) { + // Ensure the longest tags are processed first + if ($methods[$method1] > $methods[$method2]) { + return -1; + } + if ($methods[$method1] < $methods[$method2]) { + return 1; + } + + // Otherwise give preference to raw tags (assuming they've overridden) + if ($method1 === 'compileRawEchos') { + return -1; + } + if ($method2 === 'compileRawEchos') { + return 1; + } + + if ($method1 === 'compileEscapedEchos') { + return -1; + } + if ($method2 === 'compileEscapedEchos') { + return 1; + } + }); + + return $methods; + } + + /** + * Compile Blade statements that start with "@". + * + * @param string $value + * @return mixed + */ + protected function compileStatements($value) + { + $callback = function ($match) { + if (method_exists($this, $method = 'compile'.ucfirst($match[1]))) { + $match[0] = $this->$method(Arr::get($match, 3)); + } elseif (isset($this->customDirectives[$match[1]])) { + $match[0] = call_user_func($this->customDirectives[$match[1]], Arr::get($match, 3)); + } + + return isset($match[3]) ? $match[0] : $match[0].$match[2]; + }; + + return preg_replace_callback('/\B@(\w+)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value); + } + + /** + * Compile the "raw" echo statements. + * + * @param string $value + * @return string + */ + protected function compileRawEchos($value) + { + $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]); + + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3]; + + return $matches[1] ? substr($matches[0], 1) : 'compileEchoDefaults($matches[2]).'; ?>'.$whitespace; + }; + + return preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the "regular" echo statements. + * + * @param string $value + * @return string + */ + protected function compileRegularEchos($value) + { + $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]); + + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3]; + + $wrapped = sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2])); + + return $matches[1] ? substr($matches[0], 1) : ''.$whitespace; + }; + + return preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the escaped echo statements. + * + * @param string $value + * @return string + */ + protected function compileEscapedEchos($value) + { + $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]); + + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3]; + + return $matches[1] ? $matches[0] : 'compileEchoDefaults($matches[2]).'); ?>'.$whitespace; + }; + + return preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the default values for the echo statement. + * + * @param string $value + * @return string + */ + public function compileEchoDefaults($value) + { + return preg_replace('/^(?=\$)(.+?)(?:\s+or\s+)(.+?)$/s', 'isset($1) ? $1 : $2', $value); + } + + /** + * Compile the each statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEach($expression) + { + return "renderEach{$expression}; ?>"; + } + + /** + * Compile the inject statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileInject($expression) + { + $segments = explode(',', preg_replace("/[\(\)\\\"\']/", '', $expression)); + + return '"; + } + + /** + * Compile the yield statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileYield($expression) + { + return "yieldContent{$expression}; ?>"; + } + + /** + * Compile the show statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileShow($expression) + { + return 'yieldSection(); ?>'; + } + + /** + * Compile the section statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileSection($expression) + { + return "startSection{$expression}; ?>"; + } + + /** + * Compile the append statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileAppend($expression) + { + return 'appendSection(); ?>'; + } + + /** + * Compile the end-section statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndsection($expression) + { + return 'stopSection(); ?>'; + } + + /** + * Compile the stop statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileStop($expression) + { + return 'stopSection(); ?>'; + } + + /** + * Compile the overwrite statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileOverwrite($expression) + { + return 'stopSection(true); ?>'; + } + + /** + * Compile the unless statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileUnless($expression) + { + return ""; + } + + /** + * Compile the end unless statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndunless($expression) + { + return ''; + } + + /** + * Compile the lang statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileLang($expression) + { + return "get$expression; ?>"; + } + + /** + * Compile the choice statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileChoice($expression) + { + return "choice$expression; ?>"; + } + + /** + * Compile the else statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElse($expression) + { + return ''; + } + + /** + * Compile the for statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileFor($expression) + { + return ""; + } + + /** + * Compile the foreach statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileForeach($expression) + { + return ""; + } + + /** + * Compile the forelse statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileForelse($expression) + { + $empty = '$__empty_'.++$this->forelseCounter; + + return ""; + } + + /** + * Compile the can statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileCan($expression) + { + return "check{$expression}): ?>"; + } + + /** + * Compile the cannot statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileCannot($expression) + { + return "denies{$expression}): ?>"; + } + + /** + * Compile the if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIf($expression) + { + return ""; + } + + /** + * Compile the else-if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElseif($expression) + { + return ""; + } + + /** + * Compile the forelse statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEmpty($expression) + { + $empty = '$__empty_'.$this->forelseCounter--; + + return ""; + } + + /** + * Compile the while statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileWhile($expression) + { + return ""; + } + + /** + * Compile the end-while statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndwhile($expression) + { + return ''; + } + + /** + * Compile the end-for statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndfor($expression) + { + return ''; + } + + /** + * Compile the end-for-each statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndforeach($expression) + { + return ''; + } + + /** + * Compile the end-can statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndcan($expression) + { + return ''; + } + + /** + * Compile the end-cannot statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndcannot($expression) + { + return ''; + } + + /** + * Compile the end-if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndif($expression) + { + return ''; + } + + /** + * Compile the end-for-else statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndforelse($expression) + { + return ''; + } + + /** + * Compile the extends statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileExtends($expression) + { + if (Str::startsWith($expression, '(')) { + $expression = substr($expression, 1, -1); + } + + $data = "make($expression, array_except(get_defined_vars(), array('__data', '__path')))->render(); ?>"; + + $this->footer[] = $data; + + return ''; + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileInclude($expression) + { + if (Str::startsWith($expression, '(')) { + $expression = substr($expression, 1, -1); + } + + return "make($expression, array_except(get_defined_vars(), array('__data', '__path')))->render(); ?>"; + } + + /** + * Compile the stack statements into the content. + * + * @param string $expression + * @return string + */ + protected function compileStack($expression) + { + return "yieldContent{$expression}; ?>"; + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compilePush($expression) + { + return "startSection{$expression}; ?>"; + } + + /** + * Compile the endpush statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndpush($expression) + { + return 'appendSection(); ?>'; + } + + /** + * Get the extensions used by the compiler. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Register a custom Blade compiler. + * + * @param callable $compiler + * @return void + */ + public function extend(callable $compiler) + { + $this->extensions[] = $compiler; + } + + /** + * Register a handler for custom directives. + * + * @param string $name + * @param callable $handler + * @return void + */ + public function directive($name, callable $handler) + { + $this->customDirectives[$name] = $handler; + } + + /** + * Get the list of custom directives. + * + * @return array + */ + public function getCustomDirectives() + { + return $this->customDirectives; + } + + /** + * Gets the raw tags used by the compiler. + * + * @return array + */ + public function getRawTags() + { + return $this->rawTags; + } + + /** + * Sets the raw tags used for the compiler. + * + * @param string $openTag + * @param string $closeTag + * @return void + */ + public function setRawTags($openTag, $closeTag) + { + $this->rawTags = [preg_quote($openTag), preg_quote($closeTag)]; + } + + /** + * Sets the content tags used for the compiler. + * + * @param string $openTag + * @param string $closeTag + * @param bool $escaped + * @return void + */ + public function setContentTags($openTag, $closeTag, $escaped = false) + { + $property = ($escaped === true) ? 'escapedTags' : 'contentTags'; + + $this->{$property} = [preg_quote($openTag), preg_quote($closeTag)]; + } + + /** + * Sets the escaped content tags used for the compiler. + * + * @param string $openTag + * @param string $closeTag + * @return void + */ + public function setEscapedContentTags($openTag, $closeTag) + { + $this->setContentTags($openTag, $closeTag, true); + } + + /** + * Gets the content tags used for the compiler. + * + * @return string + */ + public function getContentTags() + { + return $this->getTags(); + } + + /** + * Gets the escaped content tags used for the compiler. + * + * @return string + */ + public function getEscapedContentTags() + { + return $this->getTags(true); + } + + /** + * Gets the tags used for the compiler. + * + * @param bool $escaped + * @return array + */ + protected function getTags($escaped = false) + { + $tags = $escaped ? $this->escapedTags : $this->contentTags; + + return array_map('stripcslashes', $tags); + } + + /** + * Set the echo format to be used by the compiler. + * + * @param string $format + * @return void + */ + public function setEchoFormat($format) + { + $this->echoFormat = $format; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Compilers/Compiler.php b/core/vendor/laravel/framework/src/Illuminate/View/Compilers/Compiler.php new file mode 100644 index 0000000..73ded29 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Compilers/Compiler.php @@ -0,0 +1,75 @@ +files = $files; + $this->cachePath = $cachePath; + } + + /** + * Get the path to the compiled version of a view. + * + * @param string $path + * @return string + */ + public function getCompiledPath($path) + { + return $this->cachePath.'/'.md5($path); + } + + /** + * Determine if the view at the given path is expired. + * + * @param string $path + * @return bool + */ + public function isExpired($path) + { + $compiled = $this->getCompiledPath($path); + + // If the compiled file doesn't exist we will indicate that the view is expired + // so that it can be re-compiled. Else, we will verify the last modification + // of the views is less than the modification times of the compiled views. + if (! $this->files->exists($compiled)) { + return true; + } + + $lastModified = $this->files->lastModified($path); + + return $lastModified >= $this->files->lastModified($compiled); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Compilers/CompilerInterface.php b/core/vendor/laravel/framework/src/Illuminate/View/Compilers/CompilerInterface.php new file mode 100644 index 0000000..dfcb023 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Compilers/CompilerInterface.php @@ -0,0 +1,30 @@ +compiler = $compiler; + } + + /** + * Get the evaluated contents of the view. + * + * @param string $path + * @param array $data + * @return string + */ + public function get($path, array $data = []) + { + $this->lastCompiled[] = $path; + + // If this given view has expired, which means it has simply been edited since + // it was last compiled, we will re-compile the views so we can evaluate a + // fresh copy of the view. We'll pass the compiler the path of the view. + if ($this->compiler->isExpired($path)) { + $this->compiler->compile($path); + } + + $compiled = $this->compiler->getCompiledPath($path); + + // Once we have the path to the compiled file, we will evaluate the paths with + // typical PHP just like any other templates. We also keep a stack of views + // which have been rendered for right exception messages to be generated. + $results = $this->evaluatePath($compiled, $data); + + array_pop($this->lastCompiled); + + return $results; + } + + /** + * Handle a view exception. + * + * @param \Exception $e + * @param int $obLevel + * @return void + * + * @throws $e + */ + protected function handleViewException($e, $obLevel) + { + $e = new ErrorException($this->getMessage($e), 0, 1, $e->getFile(), $e->getLine(), $e); + + parent::handleViewException($e, $obLevel); + } + + /** + * Get the exception message for an exception. + * + * @param \Exception $e + * @return string + */ + protected function getMessage($e) + { + return $e->getMessage().' (View: '.realpath(last($this->lastCompiled)).')'; + } + + /** + * Get the compiler implementation. + * + * @return \Illuminate\View\Compilers\CompilerInterface + */ + public function getCompiler() + { + return $this->compiler; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Engines/Engine.php b/core/vendor/laravel/framework/src/Illuminate/View/Engines/Engine.php new file mode 100644 index 0000000..bf5c748 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Engines/Engine.php @@ -0,0 +1,23 @@ +lastRendered; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Engines/EngineInterface.php b/core/vendor/laravel/framework/src/Illuminate/View/Engines/EngineInterface.php new file mode 100644 index 0000000..a6f71d2 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Engines/EngineInterface.php @@ -0,0 +1,15 @@ +resolved[$engine]); + + $this->resolvers[$engine] = $resolver; + } + + /** + * Resolver an engine instance by name. + * + * @param string $engine + * @return \Illuminate\View\Engines\EngineInterface + * @throws \InvalidArgumentException + */ + public function resolve($engine) + { + if (isset($this->resolved[$engine])) { + return $this->resolved[$engine]; + } + + if (isset($this->resolvers[$engine])) { + return $this->resolved[$engine] = call_user_func($this->resolvers[$engine]); + } + + throw new InvalidArgumentException("Engine $engine not found."); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php b/core/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php new file mode 100644 index 0000000..7bbf3fc --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php @@ -0,0 +1,69 @@ +evaluatePath($path, $data); + } + + /** + * Get the evaluated contents of the view at the given path. + * + * @param string $__path + * @param array $__data + * @return string + */ + protected function evaluatePath($__path, $__data) + { + $obLevel = ob_get_level(); + + ob_start(); + + extract($__data, EXTR_SKIP); + + // We'll evaluate the contents of the view inside a try/catch block so we can + // flush out any stray output that might get out before an error occurs or + // an exception is thrown. This prevents any partial views from leaking. + try { + include $__path; + } catch (Exception $e) { + $this->handleViewException($e, $obLevel); + } catch (Throwable $e) { + $this->handleViewException(new FatalThrowableError($e), $obLevel); + } + + return ltrim(ob_get_clean()); + } + + /** + * Handle a view exception. + * + * @param \Exception $e + * @param int $obLevel + * @return void + * + * @throws $e + */ + protected function handleViewException($e, $obLevel) + { + while (ob_get_level() > $obLevel) { + ob_end_clean(); + } + + throw $e; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Expression.php b/core/vendor/laravel/framework/src/Illuminate/View/Expression.php new file mode 100644 index 0000000..4749b23 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Expression.php @@ -0,0 +1,46 @@ +html = $html; + } + + /** + * Get the the HTML string. + * + * @return string + */ + public function toHtml() + { + return $this->html; + } + + /** + * Get the the HTML string. + * + * @return string + */ + public function __toString() + { + return $this->toHtml(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Factory.php b/core/vendor/laravel/framework/src/Illuminate/View/Factory.php new file mode 100644 index 0000000..5c7e7b5 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Factory.php @@ -0,0 +1,886 @@ + 'blade', 'php' => 'php']; + + /** + * The view composer events. + * + * @var array + */ + protected $composers = []; + + /** + * All of the finished, captured sections. + * + * @var array + */ + protected $sections = []; + + /** + * The stack of in-progress sections. + * + * @var array + */ + protected $sectionStack = []; + + /** + * The number of active rendering operations. + * + * @var int + */ + protected $renderCount = 0; + + /** + * Create a new view factory instance. + * + * @param \Illuminate\View\Engines\EngineResolver $engines + * @param \Illuminate\View\ViewFinderInterface $finder + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return void + */ + public function __construct(EngineResolver $engines, ViewFinderInterface $finder, Dispatcher $events) + { + $this->finder = $finder; + $this->events = $events; + $this->engines = $engines; + + $this->share('__env', $this); + } + + /** + * Get the evaluated view contents for the given view. + * + * @param string $path + * @param array $data + * @param array $mergeData + * @return \Illuminate\Contracts\View\View + */ + public function file($path, $data = [], $mergeData = []) + { + $data = array_merge($mergeData, $this->parseData($data)); + + $this->callCreator($view = new View($this, $this->getEngineFromPath($path), $path, $path, $data)); + + return $view; + } + + /** + * Get the evaluated view contents for the given view. + * + * @param string $view + * @param array $data + * @param array $mergeData + * @return \Illuminate\Contracts\View\View + */ + public function make($view, $data = [], $mergeData = []) + { + if (isset($this->aliases[$view])) { + $view = $this->aliases[$view]; + } + + $view = $this->normalizeName($view); + + $path = $this->finder->find($view); + + $data = array_merge($mergeData, $this->parseData($data)); + + $this->callCreator($view = new View($this, $this->getEngineFromPath($path), $view, $path, $data)); + + return $view; + } + + /** + * Normalize a view name. + * + * @param string $name + * @return string + */ + protected function normalizeName($name) + { + $delimiter = ViewFinderInterface::HINT_PATH_DELIMITER; + + if (strpos($name, $delimiter) === false) { + return str_replace('/', '.', $name); + } + + list($namespace, $name) = explode($delimiter, $name); + + return $namespace.$delimiter.str_replace('/', '.', $name); + } + + /** + * Parse the given data into a raw array. + * + * @param mixed $data + * @return array + */ + protected function parseData($data) + { + return $data instanceof Arrayable ? $data->toArray() : $data; + } + + /** + * Get the evaluated view contents for a named view. + * + * @param string $view + * @param mixed $data + * @return \Illuminate\Contracts\View\View + */ + public function of($view, $data = []) + { + return $this->make($this->names[$view], $data); + } + + /** + * Register a named view. + * + * @param string $view + * @param string $name + * @return void + */ + public function name($view, $name) + { + $this->names[$name] = $view; + } + + /** + * Add an alias for a view. + * + * @param string $view + * @param string $alias + * @return void + */ + public function alias($view, $alias) + { + $this->aliases[$alias] = $view; + } + + /** + * Determine if a given view exists. + * + * @param string $view + * @return bool + */ + public function exists($view) + { + try { + $this->finder->find($view); + } catch (InvalidArgumentException $e) { + return false; + } + + return true; + } + + /** + * Get the rendered contents of a partial from a loop. + * + * @param string $view + * @param array $data + * @param string $iterator + * @param string $empty + * @return string + */ + public function renderEach($view, $data, $iterator, $empty = 'raw|') + { + $result = ''; + + // If is actually data in the array, we will loop through the data and append + // an instance of the partial view to the final result HTML passing in the + // iterated value of this data array, allowing the views to access them. + if (count($data) > 0) { + foreach ($data as $key => $value) { + $data = ['key' => $key, $iterator => $value]; + + $result .= $this->make($view, $data)->render(); + } + } + + // If there is no data in the array, we will render the contents of the empty + // view. Alternatively, the "empty view" could be a raw string that begins + // with "raw|" for convenience and to let this know that it is a string. + else { + if (Str::startsWith($empty, 'raw|')) { + $result = substr($empty, 4); + } else { + $result = $this->make($empty)->render(); + } + } + + return $result; + } + + /** + * Get the appropriate view engine for the given path. + * + * @param string $path + * @return \Illuminate\View\Engines\EngineInterface + * + * @throws \InvalidArgumentException + */ + public function getEngineFromPath($path) + { + if (! $extension = $this->getExtension($path)) { + throw new InvalidArgumentException("Unrecognized extension in file: $path"); + } + + $engine = $this->extensions[$extension]; + + return $this->engines->resolve($engine); + } + + /** + * Get the extension used by the view file. + * + * @param string $path + * @return string + */ + protected function getExtension($path) + { + $extensions = array_keys($this->extensions); + + return Arr::first($extensions, function ($key, $value) use ($path) { + return Str::endsWith($path, '.'.$value); + }); + } + + /** + * Add a piece of shared data to the environment. + * + * @param array|string $key + * @param mixed $value + * @return mixed + */ + public function share($key, $value = null) + { + if (! is_array($key)) { + return $this->shared[$key] = $value; + } + + foreach ($key as $innerKey => $innerValue) { + $this->share($innerKey, $innerValue); + } + } + + /** + * Register a view creator event. + * + * @param array|string $views + * @param \Closure|string $callback + * @return array + */ + public function creator($views, $callback) + { + $creators = []; + + foreach ((array) $views as $view) { + $creators[] = $this->addViewEvent($view, $callback, 'creating: '); + } + + return $creators; + } + + /** + * Register multiple view composers via an array. + * + * @param array $composers + * @return array + */ + public function composers(array $composers) + { + $registered = []; + + foreach ($composers as $callback => $views) { + $registered = array_merge($registered, $this->composer($views, $callback)); + } + + return $registered; + } + + /** + * Register a view composer event. + * + * @param array|string $views + * @param \Closure|string $callback + * @param int|null $priority + * @return array + */ + public function composer($views, $callback, $priority = null) + { + $composers = []; + + foreach ((array) $views as $view) { + $composers[] = $this->addViewEvent($view, $callback, 'composing: ', $priority); + } + + return $composers; + } + + /** + * Add an event for a given view. + * + * @param string $view + * @param \Closure|string $callback + * @param string $prefix + * @param int|null $priority + * @return \Closure|null + */ + protected function addViewEvent($view, $callback, $prefix = 'composing: ', $priority = null) + { + $view = $this->normalizeName($view); + + if ($callback instanceof Closure) { + $this->addEventListener($prefix.$view, $callback, $priority); + + return $callback; + } elseif (is_string($callback)) { + return $this->addClassEvent($view, $callback, $prefix, $priority); + } + } + + /** + * Register a class based view composer. + * + * @param string $view + * @param string $class + * @param string $prefix + * @param int|null $priority + * @return \Closure + */ + protected function addClassEvent($view, $class, $prefix, $priority = null) + { + $name = $prefix.$view; + + // When registering a class based view "composer", we will simply resolve the + // classes from the application IoC container then call the compose method + // on the instance. This allows for convenient, testable view composers. + $callback = $this->buildClassEventCallback($class, $prefix); + + $this->addEventListener($name, $callback, $priority); + + return $callback; + } + + /** + * Add a listener to the event dispatcher. + * + * @param string $name + * @param \Closure $callback + * @param int|null $priority + * @return void + */ + protected function addEventListener($name, $callback, $priority = null) + { + if (is_null($priority)) { + $this->events->listen($name, $callback); + } else { + $this->events->listen($name, $callback, $priority); + } + } + + /** + * Build a class based container callback Closure. + * + * @param string $class + * @param string $prefix + * @return \Closure + */ + protected function buildClassEventCallback($class, $prefix) + { + list($class, $method) = $this->parseClassEvent($class, $prefix); + + // Once we have the class and method name, we can build the Closure to resolve + // the instance out of the IoC container and call the method on it with the + // given arguments that are passed to the Closure as the composer's data. + return function () use ($class, $method) { + $callable = [$this->container->make($class), $method]; + + return call_user_func_array($callable, func_get_args()); + }; + } + + /** + * Parse a class based composer name. + * + * @param string $class + * @param string $prefix + * @return array + */ + protected function parseClassEvent($class, $prefix) + { + if (Str::contains($class, '@')) { + return explode('@', $class); + } + + $method = Str::contains($prefix, 'composing') ? 'compose' : 'create'; + + return [$class, $method]; + } + + /** + * Call the composer for a given view. + * + * @param \Illuminate\Contracts\View\View $view + * @return void + */ + public function callComposer(View $view) + { + $this->events->fire('composing: '.$view->getName(), [$view]); + } + + /** + * Call the creator for a given view. + * + * @param \Illuminate\Contracts\View\View $view + * @return void + */ + public function callCreator(View $view) + { + $this->events->fire('creating: '.$view->getName(), [$view]); + } + + /** + * Start injecting content into a section. + * + * @param string $section + * @param string $content + * @return void + */ + public function startSection($section, $content = '') + { + if ($content === '') { + if (ob_start()) { + $this->sectionStack[] = $section; + } + } else { + $this->extendSection($section, $content); + } + } + + /** + * Inject inline content into a section. + * + * @param string $section + * @param string $content + * @return void + */ + public function inject($section, $content) + { + return $this->startSection($section, $content); + } + + /** + * Stop injecting content into a section and return its contents. + * + * @return string + */ + public function yieldSection() + { + if (empty($this->sectionStack)) { + return ''; + } + + return $this->yieldContent($this->stopSection()); + } + + /** + * Stop injecting content into a section. + * + * @param bool $overwrite + * @return string + * @throws \InvalidArgumentException + */ + public function stopSection($overwrite = false) + { + if (empty($this->sectionStack)) { + throw new InvalidArgumentException('Cannot end a section without first starting one.'); + } + + $last = array_pop($this->sectionStack); + + if ($overwrite) { + $this->sections[$last] = ob_get_clean(); + } else { + $this->extendSection($last, ob_get_clean()); + } + + return $last; + } + + /** + * Stop injecting content into a section and append it. + * + * @return string + * @throws \InvalidArgumentException + */ + public function appendSection() + { + if (empty($this->sectionStack)) { + throw new InvalidArgumentException('Cannot end a section without first starting one.'); + } + + $last = array_pop($this->sectionStack); + + if (isset($this->sections[$last])) { + $this->sections[$last] .= ob_get_clean(); + } else { + $this->sections[$last] = ob_get_clean(); + } + + return $last; + } + + /** + * Append content to a given section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendSection($section, $content) + { + if (isset($this->sections[$section])) { + $content = str_replace('@parent', $content, $this->sections[$section]); + } + + $this->sections[$section] = $content; + } + + /** + * Get the string contents of a section. + * + * @param string $section + * @param string $default + * @return string + */ + public function yieldContent($section, $default = '') + { + $sectionContent = $default; + + if (isset($this->sections[$section])) { + $sectionContent = $this->sections[$section]; + } + + $sectionContent = str_replace('@@parent', '--parent--holder--', $sectionContent); + + return str_replace( + '--parent--holder--', '@parent', str_replace('@parent', '', $sectionContent) + ); + } + + /** + * Flush all of the section contents. + * + * @return void + */ + public function flushSections() + { + $this->renderCount = 0; + + $this->sections = []; + + $this->sectionStack = []; + } + + /** + * Flush all of the section contents if done rendering. + * + * @return void + */ + public function flushSectionsIfDoneRendering() + { + if ($this->doneRendering()) { + $this->flushSections(); + } + } + + /** + * Increment the rendering counter. + * + * @return void + */ + public function incrementRender() + { + $this->renderCount++; + } + + /** + * Decrement the rendering counter. + * + * @return void + */ + public function decrementRender() + { + $this->renderCount--; + } + + /** + * Check if there are no active render operations. + * + * @return bool + */ + public function doneRendering() + { + return $this->renderCount == 0; + } + + /** + * Add a location to the array of view locations. + * + * @param string $location + * @return void + */ + public function addLocation($location) + { + $this->finder->addLocation($location); + } + + /** + * Add a new namespace to the loader. + * + * @param string $namespace + * @param string|array $hints + * @return void + */ + public function addNamespace($namespace, $hints) + { + $this->finder->addNamespace($namespace, $hints); + } + + /** + * Prepend a new namespace to the loader. + * + * @param string $namespace + * @param string|array $hints + * @return void + */ + public function prependNamespace($namespace, $hints) + { + $this->finder->prependNamespace($namespace, $hints); + } + + /** + * Register a valid view extension and its engine. + * + * @param string $extension + * @param string $engine + * @param \Closure $resolver + * @return void + */ + public function addExtension($extension, $engine, $resolver = null) + { + $this->finder->addExtension($extension); + + if (isset($resolver)) { + $this->engines->register($engine, $resolver); + } + + unset($this->extensions[$extension]); + + $this->extensions = array_merge([$extension => $engine], $this->extensions); + } + + /** + * Get the extension to engine bindings. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Get the engine resolver instance. + * + * @return \Illuminate\View\Engines\EngineResolver + */ + public function getEngineResolver() + { + return $this->engines; + } + + /** + * Get the view finder instance. + * + * @return \Illuminate\View\ViewFinderInterface + */ + public function getFinder() + { + return $this->finder; + } + + /** + * Set the view finder instance. + * + * @param \Illuminate\View\ViewFinderInterface $finder + * @return void + */ + public function setFinder(ViewFinderInterface $finder) + { + $this->finder = $finder; + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public function getDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return void + */ + public function setDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Contracts\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + + /** + * Get an item from the shared data. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function shared($key, $default = null) + { + return Arr::get($this->shared, $key, $default); + } + + /** + * Get all of the shared data for the environment. + * + * @return array + */ + public function getShared() + { + return $this->shared; + } + + /** + * Check if section exists. + * + * @param string $name + * @return bool + */ + public function hasSection($name) + { + return array_key_exists($name, $this->sections); + } + + /** + * Get the entire array of sections. + * + * @return array + */ + public function getSections() + { + return $this->sections; + } + + /** + * Get all of the registered named views in environment. + * + * @return array + */ + public function getNames() + { + return $this->names; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/FileViewFinder.php b/core/vendor/laravel/framework/src/Illuminate/View/FileViewFinder.php new file mode 100644 index 0000000..1c976b3 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/FileViewFinder.php @@ -0,0 +1,265 @@ +files = $files; + $this->paths = $paths; + + if (isset($extensions)) { + $this->extensions = $extensions; + } + } + + /** + * Get the fully qualified location of the view. + * + * @param string $name + * @return string + */ + public function find($name) + { + if (isset($this->views[$name])) { + return $this->views[$name]; + } + + if ($this->hasHintInformation($name = trim($name))) { + return $this->views[$name] = $this->findNamedPathView($name); + } + + return $this->views[$name] = $this->findInPaths($name, $this->paths); + } + + /** + * Get the path to a template with a named path. + * + * @param string $name + * @return string + */ + protected function findNamedPathView($name) + { + list($namespace, $view) = $this->getNamespaceSegments($name); + + return $this->findInPaths($view, $this->hints[$namespace]); + } + + /** + * Get the segments of a template with a named path. + * + * @param string $name + * @return array + * + * @throws \InvalidArgumentException + */ + protected function getNamespaceSegments($name) + { + $segments = explode(static::HINT_PATH_DELIMITER, $name); + + if (count($segments) != 2) { + throw new InvalidArgumentException("View [$name] has an invalid name."); + } + + if (! isset($this->hints[$segments[0]])) { + throw new InvalidArgumentException("No hint path defined for [{$segments[0]}]."); + } + + return $segments; + } + + /** + * Find the given view in the list of paths. + * + * @param string $name + * @param array $paths + * @return string + * + * @throws \InvalidArgumentException + */ + protected function findInPaths($name, $paths) + { + foreach ((array) $paths as $path) { + foreach ($this->getPossibleViewFiles($name) as $file) { + if ($this->files->exists($viewPath = $path.'/'.$file)) { + return $viewPath; + } + } + } + + throw new InvalidArgumentException("View [$name] not found."); + } + + /** + * Get an array of possible view files. + * + * @param string $name + * @return array + */ + protected function getPossibleViewFiles($name) + { + return array_map(function ($extension) use ($name) { + return str_replace('.', '/', $name).'.'.$extension; + }, $this->extensions); + } + + /** + * Add a location to the finder. + * + * @param string $location + * @return void + */ + public function addLocation($location) + { + $this->paths[] = $location; + } + + /** + * Add a namespace hint to the finder. + * + * @param string $namespace + * @param string|array $hints + * @return void + */ + public function addNamespace($namespace, $hints) + { + $hints = (array) $hints; + + if (isset($this->hints[$namespace])) { + $hints = array_merge($this->hints[$namespace], $hints); + } + + $this->hints[$namespace] = $hints; + } + + /** + * Prepend a namespace hint to the finder. + * + * @param string $namespace + * @param string|array $hints + * @return void + */ + public function prependNamespace($namespace, $hints) + { + $hints = (array) $hints; + + if (isset($this->hints[$namespace])) { + $hints = array_merge($hints, $this->hints[$namespace]); + } + + $this->hints[$namespace] = $hints; + } + + /** + * Register an extension with the view finder. + * + * @param string $extension + * @return void + */ + public function addExtension($extension) + { + if (($index = array_search($extension, $this->extensions)) !== false) { + unset($this->extensions[$index]); + } + + array_unshift($this->extensions, $extension); + } + + /** + * Returns whether or not the view specify a hint information. + * + * @param string $name + * @return bool + */ + public function hasHintInformation($name) + { + return strpos($name, static::HINT_PATH_DELIMITER) > 0; + } + + /** + * Get the filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + + /** + * Get the active view paths. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Get the namespace to file path hints. + * + * @return array + */ + public function getHints() + { + return $this->hints; + } + + /** + * Get registered extensions. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php b/core/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php new file mode 100644 index 0000000..812cf61 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php @@ -0,0 +1,51 @@ +view = $view; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + // If the current session has an "errors" variable bound to it, we will share + // its value with all view instances so the views can easily access errors + // without having to bind. An empty bag is set when there aren't errors. + $this->view->share( + 'errors', $request->session()->get('errors', new ViewErrorBag) + ); + + // Putting the errors in the view for every view allows the developer to just + // assume that some errors are always available, which is convenient since + // they don't have to continually run checks for the presence of errors. + + return $next($request); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/View.php b/core/vendor/laravel/framework/src/Illuminate/View/View.php new file mode 100644 index 0000000..c748ce4 --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/View.php @@ -0,0 +1,404 @@ +view = $view; + $this->path = $path; + $this->engine = $engine; + $this->factory = $factory; + + $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data; + } + + /** + * Get the string contents of the view. + * + * @param callable|null $callback + * @return string + */ + public function render(callable $callback = null) + { + try { + $contents = $this->renderContents(); + + $response = isset($callback) ? call_user_func($callback, $this, $contents) : null; + + // Once we have the contents of the view, we will flush the sections if we are + // done rendering all views so that there is nothing left hanging over when + // another view gets rendered in the future by the application developer. + $this->factory->flushSectionsIfDoneRendering(); + + return ! is_null($response) ? $response : $contents; + } catch (Exception $e) { + $this->factory->flushSections(); + + throw $e; + } catch (Throwable $e) { + $this->factory->flushSections(); + + throw $e; + } + } + + /** + * Get the contents of the view instance. + * + * @return string + */ + protected function renderContents() + { + // We will keep track of the amount of views being rendered so we can flush + // the section after the complete rendering operation is done. This will + // clear out the sections for any separate views that may be rendered. + $this->factory->incrementRender(); + + $this->factory->callComposer($this); + + $contents = $this->getContents(); + + // Once we've finished rendering the view, we'll decrement the render count + // so that each sections get flushed out next time a view is created and + // no old sections are staying around in the memory of an environment. + $this->factory->decrementRender(); + + return $contents; + } + + /** + * Get the sections of the rendered view. + * + * @return array + */ + public function renderSections() + { + return $this->render(function () { + return $this->factory->getSections(); + }); + } + + /** + * Get the evaluated contents of the view. + * + * @return string + */ + protected function getContents() + { + return $this->engine->get($this->path, $this->gatherData()); + } + + /** + * Get the data bound to the view instance. + * + * @return array + */ + protected function gatherData() + { + $data = array_merge($this->factory->getShared(), $this->data); + + foreach ($data as $key => $value) { + if ($value instanceof Renderable) { + $data[$key] = $value->render(); + } + } + + return $data; + } + + /** + * Add a piece of data to the view. + * + * @param string|array $key + * @param mixed $value + * @return $this + */ + public function with($key, $value = null) + { + if (is_array($key)) { + $this->data = array_merge($this->data, $key); + } else { + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Add a view instance to the view data. + * + * @param string $key + * @param string $view + * @param array $data + * @return $this + */ + public function nest($key, $view, array $data = []) + { + return $this->with($key, $this->factory->make($view, $data)); + } + + /** + * Add validation errors to the view. + * + * @param \Illuminate\Contracts\Support\MessageProvider|array $provider + * @return $this + */ + public function withErrors($provider) + { + if ($provider instanceof MessageProvider) { + $this->with('errors', $provider->getMessageBag()); + } else { + $this->with('errors', new MessageBag((array) $provider)); + } + + return $this; + } + + /** + * Get the view factory instance. + * + * @return \Illuminate\View\Factory + */ + public function getFactory() + { + return $this->factory; + } + + /** + * Get the view's rendering engine. + * + * @return \Illuminate\View\Engines\EngineInterface + */ + public function getEngine() + { + return $this->engine; + } + + /** + * Get the name of the view. + * + * @return string + */ + public function name() + { + return $this->getName(); + } + + /** + * Get the name of the view. + * + * @return string + */ + public function getName() + { + return $this->view; + } + + /** + * Get the array of view data. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Get the path to the view file. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set the path to the view. + * + * @param string $path + * @return void + */ + public function setPath($path) + { + $this->path = $path; + } + + /** + * Determine if a piece of data is bound. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return array_key_exists($key, $this->data); + } + + /** + * Get a piece of bound data to the view. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->data[$key]; + } + + /** + * Set a piece of data on the view. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->with($key, $value); + } + + /** + * Unset a piece of data from the view. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->data[$key]); + } + + /** + * Get a piece of data from the view. + * + * @param string $key + * @return mixed + */ + public function &__get($key) + { + return $this->data[$key]; + } + + /** + * Set a piece of data on the view. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->with($key, $value); + } + + /** + * Check if a piece of data is bound to the view. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->data[$key]); + } + + /** + * Remove a piece of bound data from the view. + * + * @param string $key + * @return bool + */ + public function __unset($key) + { + unset($this->data[$key]); + } + + /** + * Dynamically bind parameters to the view. + * + * @param string $method + * @param array $parameters + * @return \Illuminate\View\View + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (Str::startsWith($method, 'with')) { + return $this->with(Str::snake(substr($method, 4)), $parameters[0]); + } + + throw new BadMethodCallException("Method [$method] does not exist on view."); + } + + /** + * Get the string contents of the view. + * + * @return string + */ + public function __toString() + { + return $this->render(); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/ViewFinderInterface.php b/core/vendor/laravel/framework/src/Illuminate/View/ViewFinderInterface.php new file mode 100644 index 0000000..4af1f9d --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/ViewFinderInterface.php @@ -0,0 +1,55 @@ +registerEngineResolver(); + + $this->registerViewFinder(); + + $this->registerFactory(); + } + + /** + * Register the engine resolver instance. + * + * @return void + */ + public function registerEngineResolver() + { + $this->app->singleton('view.engine.resolver', function () { + $resolver = new EngineResolver; + + // Next we will register the various engines with the resolver so that the + // environment can resolve the engines it needs for various views based + // on the extension of view files. We call a method for each engines. + foreach (['php', 'blade'] as $engine) { + $this->{'register'.ucfirst($engine).'Engine'}($resolver); + } + + return $resolver; + }); + } + + /** + * Register the PHP engine implementation. + * + * @param \Illuminate\View\Engines\EngineResolver $resolver + * @return void + */ + public function registerPhpEngine($resolver) + { + $resolver->register('php', function () { + return new PhpEngine; + }); + } + + /** + * Register the Blade engine implementation. + * + * @param \Illuminate\View\Engines\EngineResolver $resolver + * @return void + */ + public function registerBladeEngine($resolver) + { + $app = $this->app; + + // The Compiler engine requires an instance of the CompilerInterface, which in + // this case will be the Blade compiler, so we'll first create the compiler + // instance to pass into the engine so it can compile the views properly. + $app->singleton('blade.compiler', function ($app) { + $cache = $app['config']['view.compiled']; + + return new BladeCompiler($app['files'], $cache); + }); + + $resolver->register('blade', function () use ($app) { + return new CompilerEngine($app['blade.compiler']); + }); + } + + /** + * Register the view finder implementation. + * + * @return void + */ + public function registerViewFinder() + { + $this->app->bind('view.finder', function ($app) { + $paths = $app['config']['view.paths']; + + return new FileViewFinder($app['files'], $paths); + }); + } + + /** + * Register the view environment. + * + * @return void + */ + public function registerFactory() + { + $this->app->singleton('view', function ($app) { + // Next we need to grab the engine resolver instance that will be used by the + // environment. The resolver will be used by an environment to get each of + // the various engine implementations such as plain PHP or Blade engine. + $resolver = $app['view.engine.resolver']; + + $finder = $app['view.finder']; + + $env = new Factory($resolver, $finder, $app['events']); + + // We will also set the container instance on this view environment since the + // view composers may be classes registered in the container, which allows + // for great testable, flexible composers for the application developer. + $env->setContainer($app); + + $env->share('app', $app); + + return $env; + }); + } +} diff --git a/core/vendor/laravel/framework/src/Illuminate/View/composer.json b/core/vendor/laravel/framework/src/Illuminate/View/composer.json new file mode 100644 index 0000000..4eb012b --- /dev/null +++ b/core/vendor/laravel/framework/src/Illuminate/View/composer.json @@ -0,0 +1,36 @@ +{ + "name": "illuminate/view", + "description": "The Illuminate View package.", + "license": "MIT", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/container": "5.1.*", + "illuminate/contracts": "5.1.*", + "illuminate/events": "5.1.*", + "illuminate/filesystem": "5.1.*", + "illuminate/support": "5.1.*", + "symfony/debug": "2.7.*" + }, + "autoload": { + "psr-4": { + "Illuminate\\View\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/league/flysystem/.php_cs.dist b/core/vendor/league/flysystem/.php_cs.dist new file mode 100644 index 0000000..dda6e19 --- /dev/null +++ b/core/vendor/league/flysystem/.php_cs.dist @@ -0,0 +1,32 @@ +in([__DIR__ . '/spec', __DIR__ . '/src', __DIR__ . '/stub', __DIR__ . '/tests']) +; + +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => true, + 'blank_line_before_return' => true, + 'cast_spaces' => true, + 'concat_space' => ['spacing' => 'one'], + 'no_singleline_whitespace_before_semicolons' => true, + 'not_operator_with_space' => true, + 'ordered_imports' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => true, + 'phpdoc_no_package' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_summary' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_trim' => true, + 'single_blank_line_at_eof' => true, + 'ternary_operator_spaces' => true, + ]) + ->setFinder($finder) + ; diff --git a/core/vendor/league/flysystem/LICENSE b/core/vendor/league/flysystem/LICENSE new file mode 100644 index 0000000..f2684c8 --- /dev/null +++ b/core/vendor/league/flysystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2019 Frank de Jonge + +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/core/vendor/league/flysystem/composer.json b/core/vendor/league/flysystem/composer.json new file mode 100644 index 0000000..696c26f --- /dev/null +++ b/core/vendor/league/flysystem/composer.json @@ -0,0 +1,64 @@ +{ + "name": "league/flysystem", + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "filesystem", "filesystems", "files", "storage", "dropbox", "aws", + "abstraction", "s3", "ftp", "sftp", "remote", "webdav", + "file systems", "cloud", "cloud files", "rackspace", "copy.com" + ], + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "require": { + "php": ">=5.5.9", + "ext-fileinfo": "*" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.10" + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\Flysystem\\Stub\\": "stub/" + }, + "files": [ + "tests/PHPUnitHacks.php" + ] + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "config": { + "bin-dir": "bin" + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/core/vendor/league/flysystem/deprecations.md b/core/vendor/league/flysystem/deprecations.md new file mode 100644 index 0000000..ef786d1 --- /dev/null +++ b/core/vendor/league/flysystem/deprecations.md @@ -0,0 +1,19 @@ +# Deprecations + +This document lists all the planned deprecations. + +## Handlers will be removed in 2.0 + +The `Handler` type and associated calls will be removed in version 2.0. + +### Upgrade path + +You should create your own implementation for handling OOP usage, +but it's recommended to move away from using an OOP-style wrapper entirely. + +The reason for this is that it's too easy for implementation details (for +your application this is Flysystem) to leak into the application. The most +important part for Flysystem is that it improves portability and creates a +solid boundary between your application core and the infrastructure you use. +The OOP-style handling breaks this principle, therefore I want to stop +promoting it. \ No newline at end of file diff --git a/core/vendor/league/flysystem/src/Adapter/AbstractAdapter.php b/core/vendor/league/flysystem/src/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..e577ac4 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/AbstractAdapter.php @@ -0,0 +1,72 @@ +pathPrefix = null; + + return; + } + + $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator; + } + + /** + * Get the path prefix. + * + * @return string|null path prefix or null if pathPrefix is empty + */ + public function getPathPrefix() + { + return $this->pathPrefix; + } + + /** + * Prefix a path. + * + * @param string $path + * + * @return string prefixed path + */ + public function applyPathPrefix($path) + { + return $this->getPathPrefix() . ltrim($path, '\\/'); + } + + /** + * Remove a path prefix. + * + * @param string $path + * + * @return string path without the prefix + */ + public function removePathPrefix($path) + { + return substr($path, strlen($this->getPathPrefix())); + } +} diff --git a/core/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php b/core/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php new file mode 100644 index 0000000..578b491 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php @@ -0,0 +1,693 @@ +safeStorage = new SafeStorage(); + $this->setConfig($config); + } + + /** + * Set the config. + * + * @param array $config + * + * @return $this + */ + public function setConfig(array $config) + { + foreach ($this->configurable as $setting) { + if ( ! isset($config[$setting])) { + continue; + } + + $method = 'set' . ucfirst($setting); + + if (method_exists($this, $method)) { + $this->$method($config[$setting]); + } + } + + return $this; + } + + /** + * Returns the host. + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the host. + * + * @param string $host + * + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + + /** + * Set the public permission value. + * + * @param int $permPublic + * + * @return $this + */ + public function setPermPublic($permPublic) + { + $this->permPublic = $permPublic; + + return $this; + } + + /** + * Set the private permission value. + * + * @param int $permPrivate + * + * @return $this + */ + public function setPermPrivate($permPrivate) + { + $this->permPrivate = $permPrivate; + + return $this; + } + + /** + * Returns the ftp port. + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Returns the root folder to work from. + * + * @return string + */ + public function getRoot() + { + return $this->root; + } + + /** + * Set the ftp port. + * + * @param int|string $port + * + * @return $this + */ + public function setPort($port) + { + $this->port = (int) $port; + + return $this; + } + + /** + * Set the root folder to work from. + * + * @param string $root + * + * @return $this + */ + public function setRoot($root) + { + $this->root = rtrim($root, '\\/') . $this->separator; + + return $this; + } + + /** + * Returns the ftp username. + * + * @return string username + */ + public function getUsername() + { + $username = $this->safeStorage->retrieveSafely('username'); + + return $username !== null ? $username : 'anonymous'; + } + + /** + * Set ftp username. + * + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->safeStorage->storeSafely('username', $username); + + return $this; + } + + /** + * Returns the password. + * + * @return string password + */ + public function getPassword() + { + return $this->safeStorage->retrieveSafely('password'); + } + + /** + * Set the ftp password. + * + * @param string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->safeStorage->storeSafely('password', $password); + + return $this; + } + + /** + * Returns the amount of seconds before the connection will timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Set the amount of seconds before the connection should timeout. + * + * @param int $timeout + * + * @return $this + */ + public function setTimeout($timeout) + { + $this->timeout = (int) $timeout; + + return $this; + } + + /** + * Return the FTP system type. + * + * @return string + */ + public function getSystemType() + { + return $this->systemType; + } + + /** + * Set the FTP system type (windows or unix). + * + * @param string $systemType + * + * @return $this + */ + public function setSystemType($systemType) + { + $this->systemType = strtolower($systemType); + + return $this; + } + + /** + * True to enable timestamps for FTP servers that return unix-style listings. + * + * @param bool $bool + * + * @return $this + */ + public function setEnableTimestampsOnUnixListings($bool = false) + { + $this->enableTimestampsOnUnixListings = $bool; + + return $this; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return $this->listDirectoryContents($directory, $recursive); + } + + abstract protected function listDirectoryContents($directory, $recursive = false); + + /** + * Normalize a directory listing. + * + * @param array $listing + * @param string $prefix + * + * @return array directory listing + */ + protected function normalizeListing(array $listing, $prefix = '') + { + $base = $prefix; + $result = []; + $listing = $this->removeDotDirectories($listing); + + while ($item = array_shift($listing)) { + if (preg_match('#^.*:$#', $item)) { + $base = preg_replace('~^\./*|:$~', '', $item); + continue; + } + + $result[] = $this->normalizeObject($item, $base); + } + + return $this->sortListing($result); + } + + /** + * Sort a directory listing. + * + * @param array $result + * + * @return array sorted listing + */ + protected function sortListing(array $result) + { + $compare = function ($one, $two) { + return strnatcmp($one['path'], $two['path']); + }; + + usort($result, $compare); + + return $result; + } + + /** + * Normalize a file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + * + * @throws NotSupportedException + */ + protected function normalizeObject($item, $base) + { + $systemType = $this->systemType ?: $this->detectSystemType($item); + + if ($systemType === 'unix') { + return $this->normalizeUnixObject($item, $base); + } elseif ($systemType === 'windows') { + return $this->normalizeWindowsObject($item, $base); + } + + throw NotSupportedException::forFtpSystemType($systemType); + } + + /** + * Normalize a Unix file entry. + * + * Given $item contains: + * '-rw-r--r-- 1 ftp ftp 409 Aug 19 09:01 file1.txt' + * + * This function will return: + * [ + * 'type' => 'file', + * 'path' => 'file1.txt', + * 'visibility' => 'public', + * 'size' => 409, + * 'timestamp' => 1566205260 + * ] + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeUnixObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 7); + + if (count(explode(' ', $item, 9)) !== 9) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($permissions, /* $number */, /* $owner */, /* $group */, $size, $month, $day, $timeOrYear, $name) = explode(' ', $item, 9); + $type = $this->detectType($permissions); + $path = $base === '' ? $name : $base . $this->separator . $name; + + if ($type === 'dir') { + return compact('type', 'path'); + } + + $permissions = $this->normalizePermissions($permissions); + $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + $size = (int) $size; + + $result = compact('type', 'path', 'visibility', 'size'); + if ($this->enableTimestampsOnUnixListings) { + $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear); + $result += compact('timestamp'); + } + + return $result; + } + + /** + * Only accurate to the minute (current year), or to the day. + * + * Inadequacies in timestamp accuracy are due to limitations of the FTP 'LIST' command + * + * Note: The 'MLSD' command is a machine-readable replacement for 'LIST' + * but many FTP servers do not support it :( + * + * @param string $month e.g. 'Aug' + * @param string $day e.g. '19' + * @param string $timeOrYear e.g. '09:01' OR '2015' + * + * @return int + */ + protected function normalizeUnixTimestamp($month, $day, $timeOrYear) + { + if (is_numeric($timeOrYear)) { + $year = $timeOrYear; + $hour = '00'; + $minute = '00'; + $seconds = '00'; + } else { + $year = date('Y'); + list($hour, $minute) = explode(':', $timeOrYear); + $seconds = '00'; + } + $dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "{$year}-{$month}-{$day}-{$hour}:{$minute}:{$seconds}"); + + return $dateTime->getTimestamp(); + } + + /** + * Normalize a Windows/DOS file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeWindowsObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 3); + + if (count(explode(' ', $item, 4)) !== 4) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($date, $time, $size, $name) = explode(' ', $item, 4); + $path = $base === '' ? $name : $base . $this->separator . $name; + + // Check for the correct date/time format + $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i'; + $dt = DateTime::createFromFormat($format, $date . $time); + $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time"); + + if ($size === '') { + $type = 'dir'; + + return compact('type', 'path', 'timestamp'); + } + + $type = 'file'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $size = (int) $size; + + return compact('type', 'path', 'visibility', 'size', 'timestamp'); + } + + /** + * Get the system type from a listing item. + * + * @param string $item + * + * @return string the system type + */ + protected function detectSystemType($item) + { + return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix'; + } + + /** + * Get the file type from the permissions. + * + * @param string $permissions + * + * @return string file type + */ + protected function detectType($permissions) + { + return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file'; + } + + /** + * Normalize a permissions string. + * + * @param string $permissions + * + * @return int + */ + protected function normalizePermissions($permissions) + { + // remove the type identifier + $permissions = substr($permissions, 1); + + // map the string rights to the numeric counterparts + $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1']; + $permissions = strtr($permissions, $map); + + // split up the permission groups + $parts = str_split($permissions, 3); + + // convert the groups + $mapper = function ($part) { + return array_sum(str_split($part)); + }; + + // converts to decimal number + return octdec(implode('', array_map($mapper, $parts))); + } + + /** + * Filter out dot-directories. + * + * @param array $list + * + * @return array + */ + public function removeDotDirectories(array $list) + { + $filter = function ($line) { + return $line !== '' && ! preg_match('#.* \.(\.)?$|^total#', $line); + }; + + return array_filter($list, $filter); + } + + /** + * @inheritdoc + */ + public function has($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return $this->getMetadata($path); + } + + /** + * Ensure a directory exists. + * + * @param string $dirname + */ + public function ensureDirectory($dirname) + { + $dirname = (string) $dirname; + + if ($dirname !== '' && ! $this->has($dirname)) { + $this->createDir($dirname, new Config()); + } + } + + /** + * @return mixed + */ + public function getConnection() + { + $tries = 0; + + while ( ! $this->isConnected() && $tries < 3) { + $tries++; + $this->disconnect(); + $this->connect(); + } + + return $this->connection; + } + + /** + * Get the public permission value. + * + * @return int + */ + public function getPermPublic() + { + return $this->permPublic; + } + + /** + * Get the private permission value. + * + * @return int + */ + public function getPermPrivate() + { + return $this->permPrivate; + } + + /** + * Disconnect on destruction. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Establish a connection. + */ + abstract public function connect(); + + /** + * Close the connection. + */ + abstract public function disconnect(); + + /** + * Check if a connection is active. + * + * @return bool + */ + abstract public function isConnected(); +} diff --git a/core/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php b/core/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php new file mode 100644 index 0000000..fd8d216 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php @@ -0,0 +1,12 @@ +transferMode = $mode; + + return $this; + } + + /** + * Set if Ssl is enabled. + * + * @param bool $ssl + * + * @return $this + */ + public function setSsl($ssl) + { + $this->ssl = (bool) $ssl; + + return $this; + } + + /** + * Set if passive mode should be used. + * + * @param bool $passive + */ + public function setPassive($passive = true) + { + $this->passive = $passive; + } + + /** + * @param bool $ignorePassiveAddress + */ + public function setIgnorePassiveAddress($ignorePassiveAddress) + { + $this->ignorePassiveAddress = $ignorePassiveAddress; + } + + /** + * @param bool $recurseManually + */ + public function setRecurseManually($recurseManually) + { + $this->recurseManually = $recurseManually; + } + + /** + * @param bool $utf8 + */ + public function setUtf8($utf8) + { + $this->utf8 = (bool) $utf8; + } + + /** + * Connect to the FTP server. + */ + public function connect() + { + if ($this->ssl) { + $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } else { + $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } + + if ( ! $this->connection) { + throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort()); + } + + $this->login(); + $this->setUtf8Mode(); + $this->setConnectionPassiveMode(); + $this->setConnectionRoot(); + $this->isPureFtpd = $this->isPureFtpdServer(); + } + + /** + * Set the connection to UTF-8 mode. + */ + protected function setUtf8Mode() + { + if ($this->utf8) { + $response = ftp_raw($this->connection, "OPTS UTF8 ON"); + if (substr($response[0], 0, 3) !== '200') { + throw new RuntimeException( + 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + } + + /** + * Set the connections to passive mode. + * + * @throws RuntimeException + */ + protected function setConnectionPassiveMode() + { + if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) { + ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress); + } + + if ( ! ftp_pasv($this->connection, $this->passive)) { + throw new RuntimeException( + 'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + + /** + * Set the connection root. + */ + protected function setConnectionRoot() + { + $root = $this->getRoot(); + $connection = $this->connection; + + if ($root && ! ftp_chdir($connection, $root)) { + throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot()); + } + + // Store absolute path for further reference. + // This is needed when creating directories and + // initial root was a relative path, else the root + // would be relative to the chdir'd path. + $this->root = ftp_pwd($connection); + } + + /** + * Login. + * + * @throws RuntimeException + */ + protected function login() + { + set_error_handler(function () { + }); + $isLoggedIn = ftp_login( + $this->connection, + $this->getUsername(), + $this->getPassword() + ); + restore_error_handler(); + + if ( ! $isLoggedIn) { + $this->disconnect(); + throw new RuntimeException( + 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort( + ) . ', username: ' . $this->getUsername() + ); + } + } + + /** + * Disconnect from the FTP server. + */ + public function disconnect() + { + if (is_resource($this->connection)) { + ftp_close($this->connection); + } + + $this->connection = null; + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $contents); + rewind($stream); + $result = $this->writeStream($path, $stream, $config); + fclose($stream); + + if ($result === false) { + return false; + } + + $result['contents'] = $contents; + $result['mimetype'] = Util::guessMimeType($path, $contents); + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $this->ensureDirectory(Util::dirname($path)); + + if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) { + return false; + } + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + } + + $type = 'file'; + + return compact('type', 'path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return $this->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return ftp_rename($this->getConnection(), $path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return ftp_delete($this->getConnection(), $path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $connection = $this->getConnection(); + $contents = array_reverse($this->listDirectoryContents($dirname, false)); + + foreach ($contents as $object) { + if ($object['type'] === 'file') { + if ( ! ftp_delete($connection, $object['path'])) { + return false; + } + } elseif ( ! $this->deleteDir($object['path'])) { + return false; + } + } + + return ftp_rmdir($connection, $dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $connection = $this->getConnection(); + $directories = explode('/', $dirname); + + foreach ($directories as $directory) { + if (false === $this->createActualDirectory($directory, $connection)) { + $this->setConnectionRoot(); + + return false; + } + + ftp_chdir($connection, $directory); + } + + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $dirname]; + } + + /** + * Create a directory. + * + * @param string $directory + * @param resource $connection + * + * @return bool + */ + protected function createActualDirectory($directory, $connection) + { + // List the current directory + $listing = ftp_nlist($connection, '.') ?: []; + + foreach ($listing as $key => $item) { + if (preg_match('~^\./.*~', $item)) { + $listing[$key] = substr($item, 2); + } + } + + if (in_array($directory, $listing, true)) { + return true; + } + + return (boolean) ftp_mkdir($connection, $directory); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + if ($path === '') { + return ['type' => 'dir', 'path' => '']; + } + + if (@ftp_chdir($this->getConnection(), $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path)); + + if (empty($listing) || in_array('total 0', $listing, true)) { + return false; + } + + if (preg_match('/.* not found/', $listing[0])) { + return false; + } + + if (preg_match('/^total [0-9]*$/', $listing[0])) { + array_shift($listing); + } + + return $this->normalizeObject($listing[0], ''); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + if ( ! $metadata = $this->getMetadata($path)) { + return false; + } + + $metadata['mimetype'] = MimeType::detectByFilename($path); + + return $metadata; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $timestamp = ftp_mdtm($this->getConnection(), $path); + + return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + if ( ! $object = $this->readStream($path)) { + return false; + } + + $object['contents'] = stream_get_contents($object['stream']); + fclose($object['stream']); + unset($object['stream']); + + return $object; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $stream = fopen('php://temp', 'w+b'); + $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode); + rewind($stream); + + if ( ! $result) { + fclose($stream); + + return false; + } + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate(); + + if ( ! ftp_chmod($this->getConnection(), $mode, $path)) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContents($directory, $recursive = true) + { + $directory = str_replace('*', '\\*', $directory); + + if ($recursive && $this->recurseManually) { + return $this->listDirectoryContentsRecursive($directory); + } + + $options = $recursive ? '-alnR' : '-aln'; + $listing = $this->ftpRawlist($options, $directory); + + return $listing ? $this->normalizeListing($listing, $directory) : []; + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContentsRecursive($directory) + { + $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory); + $output = []; + + foreach ($listing as $item) { + $output[] = $item; + if ($item['type'] !== 'dir') { + continue; + } + $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path'])); + } + + return $output; + } + + /** + * Check if the connection is open. + * + * @return bool + * + * @throws ErrorException + */ + public function isConnected() + { + try { + return is_resource($this->connection) && ftp_rawlist($this->connection, $this->getRoot()) !== false; + } catch (ErrorException $e) { + if (strpos($e->getMessage(), 'ftp_rawlist') === false) { + throw $e; + } + + return false; + } + } + + /** + * @return bool + */ + protected function isPureFtpdServer() + { + $response = ftp_raw($this->connection, 'HELP'); + + return stripos(implode(' ', $response), 'Pure-FTPd') !== false; + } + + /** + * The ftp_rawlist function with optional escaping. + * + * @param string $options + * @param string $path + * + * @return array + */ + protected function ftpRawlist($options, $path) + { + $connection = $this->getConnection(); + + if ($this->isPureFtpd) { + $path = str_replace(' ', '\ ', $path); + } + + return ftp_rawlist($connection, $options . ' ' . $path); + } +} diff --git a/core/vendor/league/flysystem/src/Adapter/Ftpd.php b/core/vendor/league/flysystem/src/Adapter/Ftpd.php new file mode 100644 index 0000000..7fcecd0 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/Ftpd.php @@ -0,0 +1,40 @@ + 'dir', 'path' => '']; + } + + if ( ! ($object = ftp_raw($this->getConnection(), 'STAT ' . $path)) || count($object) < 3) { + return false; + } + + if (substr($object[1], 0, 5) === "ftpd:") { + return false; + } + + return $this->normalizeObject($object[1], ''); + } + + /** + * @inheritdoc + */ + protected function listDirectoryContents($directory, $recursive = true) + { + $listing = ftp_rawlist($this->getConnection(), $directory, $recursive); + + if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) { + return []; + } + + return $this->normalizeListing($listing, $directory); + } +} diff --git a/core/vendor/league/flysystem/src/Adapter/Local.php b/core/vendor/league/flysystem/src/Adapter/Local.php new file mode 100644 index 0000000..543e2e2 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/Local.php @@ -0,0 +1,519 @@ + [ + 'public' => 0644, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0755, + 'private' => 0700, + ], + ]; + + /** + * @var string + */ + protected $pathSeparator = DIRECTORY_SEPARATOR; + + /** + * @var array + */ + protected $permissionMap; + + /** + * @var int + */ + protected $writeFlags; + + /** + * @var int + */ + private $linkHandling; + + /** + * Constructor. + * + * @param string $root + * @param int $writeFlags + * @param int $linkHandling + * @param array $permissions + * + * @throws LogicException + */ + public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = []) + { + $root = is_link($root) ? realpath($root) : $root; + $this->permissionMap = array_replace_recursive(static::$permissions, $permissions); + $this->ensureDirectory($root); + + if ( ! is_dir($root) || ! is_readable($root)) { + throw new LogicException('The root path ' . $root . ' is not readable.'); + } + + $this->setPathPrefix($root); + $this->writeFlags = $writeFlags; + $this->linkHandling = $linkHandling; + } + + /** + * Ensure the root directory exists. + * + * @param string $root root directory path + * + * @return void + * + * @throws Exception in case the root directory can not be created + */ + protected function ensureDirectory($root) + { + if ( ! is_dir($root)) { + $umask = umask(0); + + if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) { + $mkdirError = error_get_last(); + } + + umask($umask); + clearstatcache(false, $root); + + if ( ! is_dir($root)) { + $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : ''; + throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage)); + } + } + } + + /** + * @inheritdoc + */ + public function has($path) + { + $location = $this->applyPathPrefix($path); + + return file_exists($location); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + + if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) { + return false; + } + + $type = 'file'; + $result = compact('contents', 'type', 'size', 'path'); + + if ($visibility = $config->get('visibility')) { + $result['visibility'] = $visibility; + $this->setVisibility($path, $visibility); + } + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + $stream = fopen($location, 'w+b'); + + if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) { + return false; + } + + $type = 'file'; + $result = compact('type', 'path'); + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $location = $this->applyPathPrefix($path); + $stream = fopen($location, 'rb'); + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $size = file_put_contents($location, $contents, $this->writeFlags); + + if ($size === false) { + return false; + } + + $type = 'file'; + + $result = compact('type', 'path', 'size', 'contents'); + + if ($mimetype = Util::guessMimeType($path, $contents)) { + $result['mimetype'] = $mimetype; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function read($path) + { + $location = $this->applyPathPrefix($path); + $contents = @file_get_contents($location); + + if ($contents === false) { + return false; + } + + return ['type' => 'file', 'path' => $path, 'contents' => $contents]; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath)); + $this->ensureDirectory($parentDirectory); + + return rename($location, $destination); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $this->ensureDirectory(dirname($destination)); + + return copy($location, $destination); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $location = $this->applyPathPrefix($path); + + return @unlink($location); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $result = []; + $location = $this->applyPathPrefix($directory); + + if ( ! is_dir($location)) { + return []; + } + + $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location); + + foreach ($iterator as $file) { + $path = $this->getFilePath($file); + + if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) { + continue; + } + + $result[] = $this->normalizeFileInfo($file); + } + + return array_filter($result); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $info = new SplFileInfo($location); + + return $this->normalizeFileInfo($info); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $location = $this->applyPathPrefix($path); + $finfo = new Finfo(FILEINFO_MIME_TYPE); + $mimetype = $finfo->file($location); + + if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty'])) { + $mimetype = Util\MimeType::detectByFilename($location); + } + + return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype]; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4)); + $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $location = $this->applyPathPrefix($path); + $type = is_dir($location) ? 'dir' : 'file'; + $success = chmod($location, $this->permissionMap[$type][$visibility]); + + if ($success === false) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $location = $this->applyPathPrefix($dirname); + $umask = umask(0); + $visibility = $config->get('visibility', 'public'); + + if ( ! is_dir($location) && ! mkdir($location, $this->permissionMap['dir'][$visibility], true)) { + $return = false; + } else { + $return = ['path' => $dirname, 'type' => 'dir']; + } + + umask($umask); + + return $return; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $location = $this->applyPathPrefix($dirname); + + if ( ! is_dir($location)) { + return false; + } + + $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST); + + /** @var SplFileInfo $file */ + foreach ($contents as $file) { + $this->guardAgainstUnreadableFileInfo($file); + $this->deleteFileInfoObject($file); + } + + return rmdir($location); + } + + /** + * @param SplFileInfo $file + */ + protected function deleteFileInfoObject(SplFileInfo $file) + { + switch ($file->getType()) { + case 'dir': + rmdir($file->getRealPath()); + break; + case 'link': + unlink($file->getPathname()); + break; + default: + unlink($file->getRealPath()); + } + } + + /** + * Normalize the file info. + * + * @param SplFileInfo $file + * + * @return array|void + * + * @throws NotSupportedException + */ + protected function normalizeFileInfo(SplFileInfo $file) + { + if ( ! $file->isLink()) { + return $this->mapFileInfo($file); + } + + if ($this->linkHandling & self::DISALLOW_LINKS) { + throw NotSupportedException::forLink($file); + } + } + + /** + * Get the normalized path from a SplFileInfo object. + * + * @param SplFileInfo $file + * + * @return string + */ + protected function getFilePath(SplFileInfo $file) + { + $location = $file->getPathname(); + $path = $this->removePathPrefix($location); + + return trim(str_replace('\\', '/', $path), '/'); + } + + /** + * @param string $path + * @param int $mode + * + * @return RecursiveIteratorIterator + */ + protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST) + { + return new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + $mode + ); + } + + /** + * @param string $path + * + * @return DirectoryIterator + */ + protected function getDirectoryIterator($path) + { + $iterator = new DirectoryIterator($path); + + return $iterator; + } + + /** + * @param SplFileInfo $file + * + * @return array + */ + protected function mapFileInfo(SplFileInfo $file) + { + $normalized = [ + 'type' => $file->getType(), + 'path' => $this->getFilePath($file), + ]; + + $normalized['timestamp'] = $file->getMTime(); + + if ($normalized['type'] === 'file') { + $normalized['size'] = $file->getSize(); + } + + return $normalized; + } + + /** + * @param SplFileInfo $file + * + * @throws UnreadableFileException + */ + protected function guardAgainstUnreadableFileInfo(SplFileInfo $file) + { + if ( ! $file->isReadable()) { + throw UnreadableFileException::forFileInfo($file); + } + } +} diff --git a/core/vendor/league/flysystem/src/Adapter/NullAdapter.php b/core/vendor/league/flysystem/src/Adapter/NullAdapter.php new file mode 100644 index 0000000..2527087 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/NullAdapter.php @@ -0,0 +1,144 @@ +get('visibility')) { + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return false; + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return []; + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + return compact('visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + return ['path' => $dirname, 'type' => 'dir']; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + return false; + } +} diff --git a/core/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php b/core/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php new file mode 100644 index 0000000..fc0a747 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php @@ -0,0 +1,33 @@ +readStream($path); + + if ($response === false || ! is_resource($response['stream'])) { + return false; + } + + $result = $this->writeStream($newpath, $response['stream'], new Config()); + + if ($result !== false && is_resource($response['stream'])) { + fclose($response['stream']); + } + + return $result !== false; + } + + // Required abstract method + + /** + * @param string $path + * + * @return resource + */ + abstract public function readStream($path); + + /** + * @param string $path + * @param resource $resource + * @param Config $config + * + * @return resource + */ + abstract public function writeStream($path, $resource, Config $config); +} diff --git a/core/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php b/core/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php new file mode 100644 index 0000000..2b31c01 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php @@ -0,0 +1,44 @@ +read($path)) { + return false; + } + + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $data['contents']); + rewind($stream); + $data['stream'] = $stream; + unset($data['contents']); + + return $data; + } + + /** + * Reads a file. + * + * @param string $path + * + * @return array|false + * + * @see League\Flysystem\ReadInterface::read() + */ + abstract public function read($path); +} diff --git a/core/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php b/core/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php new file mode 100644 index 0000000..8042496 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php @@ -0,0 +1,9 @@ +stream($path, $resource, $config, 'write'); + } + + /** + * Update a file using a stream. + * + * @param string $path + * @param resource $resource + * @param Config $config Config object or visibility setting + * + * @return mixed false of file metadata + */ + public function updateStream($path, $resource, Config $config) + { + return $this->stream($path, $resource, $config, 'update'); + } + + // Required abstract methods + abstract public function write($pash, $contents, Config $config); + abstract public function update($pash, $contents, Config $config); +} diff --git a/core/vendor/league/flysystem/src/Adapter/SynologyFtp.php b/core/vendor/league/flysystem/src/Adapter/SynologyFtp.php new file mode 100644 index 0000000..fe0d344 --- /dev/null +++ b/core/vendor/league/flysystem/src/Adapter/SynologyFtp.php @@ -0,0 +1,8 @@ +settings = $settings; + } + + /** + * Get a setting. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + public function get($key, $default = null) + { + if ( ! array_key_exists($key, $this->settings)) { + return $this->getDefault($key, $default); + } + + return $this->settings[$key]; + } + + /** + * Check if an item exists by key. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + if (array_key_exists($key, $this->settings)) { + return true; + } + + return $this->fallback instanceof Config + ? $this->fallback->has($key) + : false; + } + + /** + * Try to retrieve a default setting from a config fallback. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + protected function getDefault($key, $default) + { + if ( ! $this->fallback) { + return $default; + } + + return $this->fallback->get($key, $default); + } + + /** + * Set a setting. + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function set($key, $value) + { + $this->settings[$key] = $value; + + return $this; + } + + /** + * Set the fallback. + * + * @param Config $fallback + * + * @return $this + */ + public function setFallback(Config $fallback) + { + $this->fallback = $fallback; + + return $this; + } +} diff --git a/core/vendor/league/flysystem/src/ConfigAwareTrait.php b/core/vendor/league/flysystem/src/ConfigAwareTrait.php new file mode 100644 index 0000000..202d605 --- /dev/null +++ b/core/vendor/league/flysystem/src/ConfigAwareTrait.php @@ -0,0 +1,49 @@ +config = $config ? Util::ensureConfig($config) : new Config; + } + + /** + * Get the Config. + * + * @return Config config object + */ + public function getConfig() + { + return $this->config; + } + + /** + * Convert a config array to a Config object with the correct fallback. + * + * @param array $config + * + * @return Config + */ + protected function prepareConfig(array $config) + { + $config = new Config($config); + $config->setFallback($this->getConfig()); + + return $config; + } +} diff --git a/core/vendor/league/flysystem/src/Directory.php b/core/vendor/league/flysystem/src/Directory.php new file mode 100644 index 0000000..d4f90a8 --- /dev/null +++ b/core/vendor/league/flysystem/src/Directory.php @@ -0,0 +1,31 @@ +filesystem->deleteDir($this->path); + } + + /** + * List the directory contents. + * + * @param bool $recursive + * + * @return array|bool directory contents or false + */ + public function getContents($recursive = false) + { + return $this->filesystem->listContents($this->path, $recursive); + } +} diff --git a/core/vendor/league/flysystem/src/Exception.php b/core/vendor/league/flysystem/src/Exception.php new file mode 100644 index 0000000..d4a9907 --- /dev/null +++ b/core/vendor/league/flysystem/src/Exception.php @@ -0,0 +1,8 @@ +filesystem->has($this->path); + } + + /** + * Read the file. + * + * @return string|false file contents + */ + public function read() + { + return $this->filesystem->read($this->path); + } + + /** + * Read the file as a stream. + * + * @return resource|false file stream + */ + public function readStream() + { + return $this->filesystem->readStream($this->path); + } + + /** + * Write the new file. + * + * @param string $content + * + * @return bool success boolean + */ + public function write($content) + { + return $this->filesystem->write($this->path, $content); + } + + /** + * Write the new file using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function writeStream($resource) + { + return $this->filesystem->writeStream($this->path, $resource); + } + + /** + * Update the file contents. + * + * @param string $content + * + * @return bool success boolean + */ + public function update($content) + { + return $this->filesystem->update($this->path, $content); + } + + /** + * Update the file contents with a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function updateStream($resource) + { + return $this->filesystem->updateStream($this->path, $resource); + } + + /** + * Create the file or update if exists. + * + * @param string $content + * + * @return bool success boolean + */ + public function put($content) + { + return $this->filesystem->put($this->path, $content); + } + + /** + * Create the file or update if exists using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function putStream($resource) + { + return $this->filesystem->putStream($this->path, $resource); + } + + /** + * Rename the file. + * + * @param string $newpath + * + * @return bool success boolean + */ + public function rename($newpath) + { + if ($this->filesystem->rename($this->path, $newpath)) { + $this->path = $newpath; + + return true; + } + + return false; + } + + /** + * Copy the file. + * + * @param string $newpath + * + * @return File|false new file or false + */ + public function copy($newpath) + { + if ($this->filesystem->copy($this->path, $newpath)) { + return new File($this->filesystem, $newpath); + } + + return false; + } + + /** + * Get the file's timestamp. + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp() + { + return $this->filesystem->getTimestamp($this->path); + } + + /** + * Get the file's mimetype. + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype() + { + return $this->filesystem->getMimetype($this->path); + } + + /** + * Get the file's visibility. + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility() + { + return $this->filesystem->getVisibility($this->path); + } + + /** + * Get the file's metadata. + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata() + { + return $this->filesystem->getMetadata($this->path); + } + + /** + * Get the file size. + * + * @return int|false The file size or false on failure. + */ + public function getSize() + { + return $this->filesystem->getSize($this->path); + } + + /** + * Delete the file. + * + * @return bool success boolean + */ + public function delete() + { + return $this->filesystem->delete($this->path); + } +} diff --git a/core/vendor/league/flysystem/src/FileExistsException.php b/core/vendor/league/flysystem/src/FileExistsException.php new file mode 100644 index 0000000..c82e20c --- /dev/null +++ b/core/vendor/league/flysystem/src/FileExistsException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/core/vendor/league/flysystem/src/FileNotFoundException.php b/core/vendor/league/flysystem/src/FileNotFoundException.php new file mode 100644 index 0000000..989df69 --- /dev/null +++ b/core/vendor/league/flysystem/src/FileNotFoundException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File not found at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was not found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/core/vendor/league/flysystem/src/Filesystem.php b/core/vendor/league/flysystem/src/Filesystem.php new file mode 100644 index 0000000..18b590e --- /dev/null +++ b/core/vendor/league/flysystem/src/Filesystem.php @@ -0,0 +1,408 @@ +adapter = $adapter; + $this->setConfig($config); + } + + /** + * Get the Adapter. + * + * @return AdapterInterface adapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * @inheritdoc + */ + public function has($path) + { + $path = Util::normalizePath($path); + + return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function put($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function putStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + Util::rewindStream($resource); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function readAndDelete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + $contents = $this->read($path); + + if ($contents === false) { + return false; + } + + $this->delete($path); + + return $contents; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + $this->assertPresent($path); + + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + $this->assertPresent($path); + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function read($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! ($object = $this->getAdapter()->read($path))) { + return false; + } + + return $object['contents']; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! $object = $this->getAdapter()->readStream($path)) { + return false; + } + + return $object['stream']; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return (bool) $this->getAdapter()->rename($path, $newpath); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return $this->getAdapter()->copy($path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->delete($path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $dirname = Util::normalizePath($dirname); + + if ($dirname === '') { + throw new RootViolationException('Root directories can not be deleted.'); + } + + return (bool) $this->getAdapter()->deleteDir($dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, array $config = []) + { + $dirname = Util::normalizePath($dirname); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->createDir($dirname, $config); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $directory = Util::normalizePath($directory); + $contents = $this->getAdapter()->listContents($directory, $recursive); + + return (new ContentListingFormatter($directory, $recursive, $this->config->get('case_sensitive', true))) + ->formatListing($contents); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) { + return false; + } + + return $object['mimetype']; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) { + return false; + } + + return $object['timestamp']; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) { + return false; + } + + return $object['visibility']; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) { + return false; + } + + return (int) $object['size']; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return (bool) $this->getAdapter()->setVisibility($path, $visibility); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function get($path, Handler $handler = null) + { + $path = Util::normalizePath($path); + + if ( ! $handler) { + $metadata = $this->getMetadata($path); + $handler = $metadata['type'] === 'file' ? new File($this, $path) : new Directory($this, $path); + } + + $handler->setPath($path); + $handler->setFilesystem($this); + + return $handler; + } + + /** + * Assert a file is present. + * + * @param string $path path to file + * + * @throws FileNotFoundException + * + * @return void + */ + public function assertPresent($path) + { + if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) { + throw new FileNotFoundException($path); + } + } + + /** + * Assert a file is absent. + * + * @param string $path path to file + * + * @throws FileExistsException + * + * @return void + */ + public function assertAbsent($path) + { + if ($this->config->get('disable_asserts', false) === false && $this->has($path)) { + throw new FileExistsException($path); + } + } +} diff --git a/core/vendor/league/flysystem/src/FilesystemInterface.php b/core/vendor/league/flysystem/src/FilesystemInterface.php new file mode 100644 index 0000000..09b811b --- /dev/null +++ b/core/vendor/league/flysystem/src/FilesystemInterface.php @@ -0,0 +1,284 @@ +path = $path; + $this->filesystem = $filesystem; + } + + /** + * Check whether the entree is a directory. + * + * @return bool + */ + public function isDir() + { + return $this->getType() === 'dir'; + } + + /** + * Check whether the entree is a file. + * + * @return bool + */ + public function isFile() + { + return $this->getType() === 'file'; + } + + /** + * Retrieve the entree type (file|dir). + * + * @return string file or dir + */ + public function getType() + { + $metadata = $this->filesystem->getMetadata($this->path); + + return $metadata['type']; + } + + /** + * Set the Filesystem object. + * + * @param FilesystemInterface $filesystem + * + * @return $this + */ + public function setFilesystem(FilesystemInterface $filesystem) + { + $this->filesystem = $filesystem; + + return $this; + } + + /** + * Retrieve the Filesystem object. + * + * @return FilesystemInterface + */ + public function getFilesystem() + { + return $this->filesystem; + } + + /** + * Set the entree path. + * + * @param string $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Retrieve the entree path. + * + * @return string path + */ + public function getPath() + { + return $this->path; + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, array $arguments) + { + array_unshift($arguments, $this->path); + $callback = [$this->filesystem, $method]; + + try { + return call_user_func_array($callback, $arguments); + } catch (BadMethodCallException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_called_class() + . '::' . $method + ); + } + } +} diff --git a/core/vendor/league/flysystem/src/MountManager.php b/core/vendor/league/flysystem/src/MountManager.php new file mode 100644 index 0000000..620f540 --- /dev/null +++ b/core/vendor/league/flysystem/src/MountManager.php @@ -0,0 +1,648 @@ + Filesystem,] + * + * @throws InvalidArgumentException + */ + public function __construct(array $filesystems = []) + { + $this->mountFilesystems($filesystems); + } + + /** + * Mount filesystems. + * + * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,] + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystems(array $filesystems) + { + foreach ($filesystems as $prefix => $filesystem) { + $this->mountFilesystem($prefix, $filesystem); + } + + return $this; + } + + /** + * Mount filesystems. + * + * @param string $prefix + * @param FilesystemInterface $filesystem + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystem($prefix, FilesystemInterface $filesystem) + { + if ( ! is_string($prefix)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.'); + } + + $this->filesystems[$prefix] = $filesystem; + + return $this; + } + + /** + * Get the filesystem with the corresponding prefix. + * + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return FilesystemInterface + */ + public function getFilesystem($prefix) + { + if ( ! isset($this->filesystems[$prefix])) { + throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix); + } + + return $this->filesystems[$prefix]; + } + + /** + * Retrieve the prefix from an arguments array. + * + * @param array $arguments + * + * @throws InvalidArgumentException + * + * @return array [:prefix, :arguments] + */ + public function filterPrefix(array $arguments) + { + if (empty($arguments)) { + throw new InvalidArgumentException('At least one argument needed'); + } + + $path = array_shift($arguments); + + if ( ! is_string($path)) { + throw new InvalidArgumentException('First argument should be a string'); + } + + list($prefix, $path) = $this->getPrefixAndPath($path); + array_unshift($arguments, $path); + + return [$prefix, $arguments]; + } + + /** + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listContents($directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $filesystem = $this->getFilesystem($prefix); + $result = $filesystem->listContents($directory, $recursive); + + foreach ($result as &$file) { + $file['filesystem'] = $prefix; + } + + return $result; + } + + /** + * Call forwarder. + * + * @param string $method + * @param array $arguments + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function __call($method, $arguments) + { + list($prefix, $arguments) = $this->filterPrefix($arguments); + + return $this->invokePluginOnFilesystem($method, $arguments, $prefix); + } + + /** + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * @throws FileExistsException + * + * @return bool + */ + public function copy($from, $to, array $config = []) + { + list($prefixFrom, $from) = $this->getPrefixAndPath($from); + + $buffer = $this->getFilesystem($prefixFrom)->readStream($from); + + if ($buffer === false) { + return false; + } + + list($prefixTo, $to) = $this->getPrefixAndPath($to); + + $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config); + + if (is_resource($buffer)) { + fclose($buffer); + } + + return $result; + } + + /** + * List with plugin adapter. + * + * @param array $keys + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listWith(array $keys = [], $directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $arguments = [$keys, $directory, $recursive]; + + return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix); + } + + /** + * Move a file. + * + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return bool + */ + public function move($from, $to, array $config = []) + { + list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from); + list($prefixTo, $pathTo) = $this->getPrefixAndPath($to); + + if ($prefixFrom === $prefixTo) { + $filesystem = $this->getFilesystem($prefixFrom); + $renamed = $filesystem->rename($pathFrom, $pathTo); + + if ($renamed && isset($config['visibility'])) { + return $filesystem->setVisibility($pathTo, $config['visibility']); + } + + return $renamed; + } + + $copied = $this->copy($from, $to, $config); + + if ($copied) { + return $this->delete($from); + } + + return false; + } + + /** + * Invoke a plugin on a filesystem mounted on a given prefix. + * + * @param string $method + * @param array $arguments + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function invokePluginOnFilesystem($method, $arguments, $prefix) + { + $filesystem = $this->getFilesystem($prefix); + + try { + return $this->invokePlugin($method, $arguments, $filesystem); + } catch (PluginNotFoundException $e) { + // Let it pass, it's ok, don't panic. + } + + $callback = [$filesystem, $method]; + + return call_user_func_array($callback, $arguments); + } + + /** + * @param string $path + * + * @throws InvalidArgumentException + * + * @return string[] [:prefix, :path] + */ + protected function getPrefixAndPath($path) + { + if (strpos($path, '://') < 1) { + throw new InvalidArgumentException('No prefix detected in path: ' . $path); + } + + return explode('://', $path, 2); + } + + /** + * Check whether a file exists. + * + * @param string $path + * + * @return bool + */ + public function has($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->has($path); + } + + /** + * Read a file. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file contents or false on failure. + */ + public function read($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->read($path); + } + + /** + * Retrieves a read-stream for a path. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return resource|false The path resource or false on failure. + */ + public function readStream($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->readStream($path); + } + + /** + * Get a file's metadata. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getMetadata($path); + } + + /** + * Get a file's size. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return int|false The file size or false on failure. + */ + public function getSize($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getSize($path); + } + + /** + * Get a file's mime-type. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getMimetype($path); + } + + /** + * Get a file's timestamp. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getTimestamp($path); + } + + /** + * Get a file's visibility. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getVisibility($path); + } + + /** + * Write a new file. + * + * @param string $path The path of the new file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @throws FileExistsException + * + * @return bool True on success, false on failure. + */ + public function write($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->write($path, $contents, $config); + } + + /** + * Write a new file using a stream. + * + * @param string $path The path of the new file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException If $resource is not a file handle. + * @throws FileExistsException + * + * @return bool True on success, false on failure. + */ + public function writeStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->writeStream($path, $resource, $config); + } + + /** + * Update an existing file. + * + * @param string $path The path of the existing file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function update($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->update($path, $contents, $config); + } + + /** + * Update an existing file using a stream. + * + * @param string $path The path of the existing file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException If $resource is not a file handle. + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function updateStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->updateStream($path, $resource, $config); + } + + /** + * Rename a file. + * + * @param string $path Path to the existing file. + * @param string $newpath The new path of the file. + * + * @throws FileExistsException Thrown if $newpath exists. + * @throws FileNotFoundException Thrown if $path does not exist. + * + * @return bool True on success, false on failure. + */ + public function rename($path, $newpath) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->rename($path, $newpath); + } + + /** + * Delete a file. + * + * @param string $path + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function delete($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->delete($path); + } + + /** + * Delete a directory. + * + * @param string $dirname + * + * @throws RootViolationException Thrown if $dirname is empty. + * + * @return bool True on success, false on failure. + */ + public function deleteDir($dirname) + { + list($prefix, $dirname) = $this->getPrefixAndPath($dirname); + + return $this->getFilesystem($prefix)->deleteDir($dirname); + } + + /** + * Create a directory. + * + * @param string $dirname The name of the new directory. + * @param array $config An optional configuration array. + * + * @return bool True on success, false on failure. + */ + public function createDir($dirname, array $config = []) + { + list($prefix, $dirname) = $this->getPrefixAndPath($dirname); + + return $this->getFilesystem($prefix)->createDir($dirname); + } + + /** + * Set the visibility for a file. + * + * @param string $path The path to the file. + * @param string $visibility One of 'public' or 'private'. + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function setVisibility($path, $visibility) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->setVisibility($path, $visibility); + } + + /** + * Create a file or update if exists. + * + * @param string $path The path to the file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @return bool True on success, false on failure. + */ + public function put($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->put($path, $contents, $config); + } + + /** + * Create a file or update if exists. + * + * @param string $path The path to the file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException Thrown if $resource is not a resource. + * + * @return bool True on success, false on failure. + */ + public function putStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->putStream($path, $resource, $config); + } + + /** + * Read and delete a file. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file contents, or false on failure. + */ + public function readAndDelete($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->readAndDelete($path); + } + + /** + * Get a file/directory handler. + * + * @deprecated + * + * @param string $path The path to the file. + * @param Handler $handler An optional existing handler to populate. + * + * @return Handler Either a file or directory handler. + */ + public function get($path, Handler $handler = null) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->get($path); + } +} diff --git a/core/vendor/league/flysystem/src/NotSupportedException.php b/core/vendor/league/flysystem/src/NotSupportedException.php new file mode 100644 index 0000000..08f47f7 --- /dev/null +++ b/core/vendor/league/flysystem/src/NotSupportedException.php @@ -0,0 +1,37 @@ +getPathname()); + } + + /** + * Create a new exception for a link. + * + * @param string $systemType + * + * @return static + */ + public static function forFtpSystemType($systemType) + { + $message = "The FTP system type '$systemType' is currently not supported."; + + return new static($message); + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/AbstractPlugin.php b/core/vendor/league/flysystem/src/Plugin/AbstractPlugin.php new file mode 100644 index 0000000..0d56789 --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/AbstractPlugin.php @@ -0,0 +1,24 @@ +filesystem = $filesystem; + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/EmptyDir.php b/core/vendor/league/flysystem/src/Plugin/EmptyDir.php new file mode 100644 index 0000000..b5ae7f5 --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/EmptyDir.php @@ -0,0 +1,34 @@ +filesystem->listContents($dirname, false); + + foreach ($listing as $item) { + if ($item['type'] === 'dir') { + $this->filesystem->deleteDir($item['path']); + } else { + $this->filesystem->delete($item['path']); + } + } + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/ForcedCopy.php b/core/vendor/league/flysystem/src/Plugin/ForcedCopy.php new file mode 100644 index 0000000..a41e9f3 --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/ForcedCopy.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->copy($path, $newpath); + } + + return false; + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/ForcedRename.php b/core/vendor/league/flysystem/src/Plugin/ForcedRename.php new file mode 100644 index 0000000..3f51cd6 --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/ForcedRename.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->rename($path, $newpath); + } + + return false; + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/GetWithMetadata.php b/core/vendor/league/flysystem/src/Plugin/GetWithMetadata.php new file mode 100644 index 0000000..6fe4f05 --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/GetWithMetadata.php @@ -0,0 +1,51 @@ +filesystem->getMetadata($path); + + if ( ! $object) { + return false; + } + + $keys = array_diff($metadata, array_keys($object)); + + foreach ($keys as $key) { + if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) { + throw new InvalidArgumentException('Could not fetch metadata: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($path); + } + + return $object; + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/ListFiles.php b/core/vendor/league/flysystem/src/Plugin/ListFiles.php new file mode 100644 index 0000000..9669fe7 --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/ListFiles.php @@ -0,0 +1,35 @@ +filesystem->listContents($directory, $recursive); + + $filter = function ($object) { + return $object['type'] === 'file'; + }; + + return array_values(array_filter($contents, $filter)); + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/ListPaths.php b/core/vendor/league/flysystem/src/Plugin/ListPaths.php new file mode 100644 index 0000000..514bdf0 --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/ListPaths.php @@ -0,0 +1,36 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $object) { + $result[] = $object['path']; + } + + return $result; + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/ListWith.php b/core/vendor/league/flysystem/src/Plugin/ListWith.php new file mode 100644 index 0000000..d90464e --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/ListWith.php @@ -0,0 +1,60 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $index => $object) { + if ($object['type'] === 'file') { + $missingKeys = array_diff($keys, array_keys($object)); + $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object); + } + } + + return $contents; + } + + /** + * Get a meta-data value by key name. + * + * @param array $object + * @param string $key + * + * @return array + */ + protected function getMetadataByName(array $object, $key) + { + $method = 'get' . ucfirst($key); + + if ( ! method_exists($this->filesystem, $method)) { + throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($object['path']); + + return $object; + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/PluggableTrait.php b/core/vendor/league/flysystem/src/Plugin/PluggableTrait.php new file mode 100644 index 0000000..922edfe --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/PluggableTrait.php @@ -0,0 +1,97 @@ +plugins[$plugin->getMethod()] = $plugin; + + return $this; + } + + /** + * Find a specific plugin. + * + * @param string $method + * + * @throws PluginNotFoundException + * + * @return PluginInterface + */ + protected function findPlugin($method) + { + if ( ! isset($this->plugins[$method])) { + throw new PluginNotFoundException('Plugin not found for method: ' . $method); + } + + return $this->plugins[$method]; + } + + /** + * Invoke a plugin by method name. + * + * @param string $method + * @param array $arguments + * @param FilesystemInterface $filesystem + * + * @throws PluginNotFoundException + * + * @return mixed + */ + protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem) + { + $plugin = $this->findPlugin($method); + $plugin->setFilesystem($filesystem); + $callback = [$plugin, 'handle']; + + return call_user_func_array($callback, $arguments); + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @throws BadMethodCallException + * + * @return mixed + */ + public function __call($method, array $arguments) + { + try { + return $this->invokePlugin($method, $arguments, $this); + } catch (PluginNotFoundException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_class($this) + . '::' . $method + ); + } + } +} diff --git a/core/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php b/core/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php new file mode 100644 index 0000000..fd1d7e7 --- /dev/null +++ b/core/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php @@ -0,0 +1,10 @@ +hash = spl_object_hash($this); + static::$safeStorage[$this->hash] = []; + } + + public function storeSafely($key, $value) + { + static::$safeStorage[$this->hash][$key] = $value; + } + + public function retrieveSafely($key) + { + if (array_key_exists($key, static::$safeStorage[$this->hash])) { + return static::$safeStorage[$this->hash][$key]; + } + } + + public function __destruct() + { + unset(static::$safeStorage[$this->hash]); + } +} diff --git a/core/vendor/league/flysystem/src/UnreadableFileException.php b/core/vendor/league/flysystem/src/UnreadableFileException.php new file mode 100644 index 0000000..e668033 --- /dev/null +++ b/core/vendor/league/flysystem/src/UnreadableFileException.php @@ -0,0 +1,18 @@ +getRealPath() + ) + ); + } +} diff --git a/core/vendor/league/flysystem/src/Util.php b/core/vendor/league/flysystem/src/Util.php new file mode 100644 index 0000000..2c77540 --- /dev/null +++ b/core/vendor/league/flysystem/src/Util.php @@ -0,0 +1,349 @@ + '']; + } + + /** + * Normalize a dirname return value. + * + * @param string $dirname + * + * @return string normalized dirname + */ + public static function normalizeDirname($dirname) + { + return $dirname === '.' ? '' : $dirname; + } + + /** + * Get a normalized dirname from a path. + * + * @param string $path + * + * @return string dirname + */ + public static function dirname($path) + { + return static::normalizeDirname(dirname($path)); + } + + /** + * Map result arrays. + * + * @param array $object + * @param array $map + * + * @return array mapped result + */ + public static function map(array $object, array $map) + { + $result = []; + + foreach ($map as $from => $to) { + if ( ! isset($object[$from])) { + continue; + } + + $result[$to] = $object[$from]; + } + + return $result; + } + + /** + * Normalize path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizePath($path) + { + return static::normalizeRelativePath($path); + } + + /** + * Normalize relative directories in a path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizeRelativePath($path) + { + $path = str_replace('\\', '/', $path); + $path = static::removeFunkyWhiteSpace($path); + + $parts = []; + + foreach (explode('/', $path) as $part) { + switch ($part) { + case '': + case '.': + break; + + case '..': + if (empty($parts)) { + throw new LogicException( + 'Path is outside of the defined root, path: [' . $path . ']' + ); + } + array_pop($parts); + break; + + default: + $parts[] = $part; + break; + } + } + + return implode('/', $parts); + } + + /** + * Removes unprintable characters and invalid unicode characters. + * + * @param string $path + * + * @return string $path + */ + protected static function removeFunkyWhiteSpace($path) + { + // We do this check in a loop, since removing invalid unicode characters + // can lead to new characters being created. + while (preg_match('#\p{C}+|^\./#u', $path)) { + $path = preg_replace('#\p{C}+|^\./#u', '', $path); + } + + return $path; + } + + /** + * Normalize prefix. + * + * @param string $prefix + * @param string $separator + * + * @return string normalized path + */ + public static function normalizePrefix($prefix, $separator) + { + return rtrim($prefix, $separator) . $separator; + } + + /** + * Get content size. + * + * @param string $contents + * + * @return int content size + */ + public static function contentSize($contents) + { + return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents); + } + + /** + * Guess MIME Type based on the path of the file and it's content. + * + * @param string $path + * @param string|resource $content + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function guessMimeType($path, $content) + { + $mimeType = MimeType::detectByContent($content); + + if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) { + return $mimeType; + } + + return MimeType::detectByFilename($path); + } + + /** + * Emulate directories. + * + * @param array $listing + * + * @return array listing with emulated directories + */ + public static function emulateDirectories(array $listing) + { + $directories = []; + $listedDirectories = []; + + foreach ($listing as $object) { + list($directories, $listedDirectories) = static::emulateObjectDirectories($object, $directories, $listedDirectories); + } + + $directories = array_diff(array_unique($directories), array_unique($listedDirectories)); + + foreach ($directories as $directory) { + $listing[] = static::pathinfo($directory) + ['type' => 'dir']; + } + + return $listing; + } + + /** + * Ensure a Config instance. + * + * @param null|array|Config $config + * + * @return Config config instance + * + * @throw LogicException + */ + public static function ensureConfig($config) + { + if ($config === null) { + return new Config(); + } + + if ($config instanceof Config) { + return $config; + } + + if (is_array($config)) { + return new Config($config); + } + + throw new LogicException('A config should either be an array or a Flysystem\Config object.'); + } + + /** + * Rewind a stream. + * + * @param resource $resource + */ + public static function rewindStream($resource) + { + if (ftell($resource) !== 0 && static::isSeekableStream($resource)) { + rewind($resource); + } + } + + public static function isSeekableStream($resource) + { + $metadata = stream_get_meta_data($resource); + + return $metadata['seekable']; + } + + /** + * Get the size of a stream. + * + * @param resource $resource + * + * @return int stream size + */ + public static function getStreamSize($resource) + { + $stat = fstat($resource); + + return $stat['size']; + } + + /** + * Emulate the directories of a single object. + * + * @param array $object + * @param array $directories + * @param array $listedDirectories + * + * @return array + */ + protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories) + { + if ($object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + } + + if (empty($object['dirname'])) { + return [$directories, $listedDirectories]; + } + + $parent = $object['dirname']; + + while ( ! empty($parent) && ! in_array($parent, $directories)) { + $directories[] = $parent; + $parent = static::dirname($parent); + } + + if (isset($object['type']) && $object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + + return [$directories, $listedDirectories]; + } + + return [$directories, $listedDirectories]; + } + + /** + * Returns the trailing name component of the path. + * + * @param string $path + * + * @return string + */ + private static function basename($path) + { + $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/'; + + $path = rtrim($path, $separators); + + $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path); + + if (DIRECTORY_SEPARATOR === '/') { + return $basename; + } + // @codeCoverageIgnoreStart + // Extra Windows path munging. This is tested via AppVeyor, but code + // coverage is not reported. + + // Handle relative paths with drive letters. c:file.txt. + while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) { + $basename = substr($basename, 2); + } + + // Remove colon for standalone drive letter names. + if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) { + $basename = rtrim($basename, ':'); + } + + return $basename; + // @codeCoverageIgnoreEnd + } +} diff --git a/core/vendor/league/flysystem/src/Util/ContentListingFormatter.php b/core/vendor/league/flysystem/src/Util/ContentListingFormatter.php new file mode 100644 index 0000000..ae0d3b9 --- /dev/null +++ b/core/vendor/league/flysystem/src/Util/ContentListingFormatter.php @@ -0,0 +1,122 @@ +directory = rtrim($directory, '/'); + $this->recursive = $recursive; + $this->caseSensitive = $caseSensitive; + } + + /** + * Format contents listing. + * + * @param array $listing + * + * @return array + */ + public function formatListing(array $listing) + { + $listing = array_filter(array_map([$this, 'addPathInfo'], $listing), [$this, 'isEntryOutOfScope']); + + return $this->sortListing(array_values($listing)); + } + + private function addPathInfo(array $entry) + { + return $entry + Util::pathinfo($entry['path']); + } + + /** + * Determine if the entry is out of scope. + * + * @param array $entry + * + * @return bool + */ + private function isEntryOutOfScope(array $entry) + { + if (empty($entry['path']) && $entry['path'] !== '0') { + return false; + } + + if ($this->recursive) { + return $this->residesInDirectory($entry); + } + + return $this->isDirectChild($entry); + } + + /** + * Check if the entry resides within the parent directory. + * + * @param array $entry + * + * @return bool + */ + private function residesInDirectory(array $entry) + { + if ($this->directory === '') { + return true; + } + + return $this->caseSensitive + ? strpos($entry['path'], $this->directory . '/') === 0 + : stripos($entry['path'], $this->directory . '/') === 0; + } + + /** + * Check if the entry is a direct child of the directory. + * + * @param array $entry + * + * @return bool + */ + private function isDirectChild(array $entry) + { + return $this->caseSensitive + ? $entry['dirname'] === $this->directory + : strcasecmp($this->directory, $entry['dirname']) === 0; + } + + /** + * @param array $listing + * + * @return array + */ + private function sortListing(array $listing) + { + usort($listing, function ($a, $b) { + return strcasecmp($a['path'], $b['path']); + }); + + return $listing; + } +} diff --git a/core/vendor/league/flysystem/src/Util/MimeType.php b/core/vendor/league/flysystem/src/Util/MimeType.php new file mode 100644 index 0000000..0dee6a5 --- /dev/null +++ b/core/vendor/league/flysystem/src/Util/MimeType.php @@ -0,0 +1,228 @@ + 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'csv' => 'text/csv', + 'bin' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'class' => 'application/octet-stream', + 'psd' => 'application/x-photoshop', + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/pdf', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'wbxml' => 'application/wbxml', + 'wmlc' => 'application/wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'php' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'z' => 'application/x-compress', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'rdf' => 'application/rdf+xml', + 'zip' => 'application/x-zip', + 'rar' => 'application/x-rar', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => 'audio/x-wav', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'gif' => 'image/gif', + 'bmp' => 'image/bmp', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'svg' => 'image/svg+xml', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'dmn' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dot' => 'application/msword', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'word' => 'application/msword', + 'xl' => 'application/excel', + 'eml' => 'message/rfc822', + 'json' => 'application/json', + 'pem' => 'application/x-x509-user-cert', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7a' => 'application/x-pkcs7-signature', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'crt' => 'application/x-x509-ca-cert', + 'crl' => 'application/pkix-crl', + 'der' => 'application/x-x509-ca-cert', + 'kdb' => 'application/octet-stream', + 'pgp' => 'application/pgp', + 'gpg' => 'application/gpg-keys', + 'sst' => 'application/octet-stream', + 'csr' => 'application/octet-stream', + 'rsa' => 'application/x-pkcs7', + 'cer' => 'application/pkix-cert', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + 'mp4' => 'video/mp4', + 'm4a' => 'audio/x-m4a', + 'f4v' => 'video/mp4', + 'webm' => 'video/webm', + 'aac' => 'audio/x-acc', + 'm4u' => 'application/vnd.mpegurl', + 'm3u' => 'text/plain', + 'xspf' => 'application/xspf+xml', + 'vlc' => 'application/videolan', + 'wmv' => 'video/x-ms-wmv', + 'au' => 'audio/x-au', + 'ac3' => 'audio/ac3', + 'flac' => 'audio/x-flac', + 'ogg' => 'audio/ogg', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'ics' => 'text/calendar', + 'zsh' => 'text/x-scriptzsh', + '7zip' => 'application/x-7z-compressed', + 'cdr' => 'application/cdr', + 'wma' => 'audio/x-ms-wma', + 'jar' => 'application/java-archive', + 'tex' => 'application/x-tex', + 'latex' => 'application/x-latex', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + ]; + + /** + * Detects MIME Type based on given content. + * + * @param mixed $content + * + * @return string|null MIME Type or NULL if no mime type detected + */ + public static function detectByContent($content) + { + if ( ! class_exists('finfo') || ! is_string($content)) { + return null; + } + try { + $finfo = new finfo(FILEINFO_MIME_TYPE); + + return $finfo->buffer($content) ?: null; + // @codeCoverageIgnoreStart + } catch (ErrorException $e) { + // This is caused by an array to string conversion error. + } + } // @codeCoverageIgnoreEnd + + /** + * Detects MIME Type based on file extension. + * + * @param string $extension + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function detectByFileExtension($extension) + { + return isset(static::$extensionToMimeTypeMap[$extension]) + ? static::$extensionToMimeTypeMap[$extension] + : 'text/plain'; + } + + /** + * @param string $filename + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function detectByFilename($filename) + { + $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + + return empty($extension) ? 'text/plain' : static::detectByFileExtension($extension); + } + + /** + * @return array Map of file extension to MIME Type + */ + public static function getExtensionToMimeTypeMap() + { + return static::$extensionToMimeTypeMap; + } +} diff --git a/core/vendor/league/flysystem/src/Util/StreamHasher.php b/core/vendor/league/flysystem/src/Util/StreamHasher.php new file mode 100644 index 0000000..938ec5d --- /dev/null +++ b/core/vendor/league/flysystem/src/Util/StreamHasher.php @@ -0,0 +1,36 @@ +algo = $algo; + } + + /** + * @param resource $resource + * + * @return string + */ + public function hash($resource) + { + rewind($resource); + $context = hash_init($this->algo); + hash_update_stream($context, $resource); + fclose($resource); + + return hash_final($context); + } +} diff --git a/core/vendor/monolog/monolog/.php_cs b/core/vendor/monolog/monolog/.php_cs new file mode 100644 index 0000000..366ccd0 --- /dev/null +++ b/core/vendor/monolog/monolog/.php_cs @@ -0,0 +1,59 @@ + + +For the full copyright and license information, please view the LICENSE +file that was distributed with this source code. +EOF; + +$finder = Symfony\CS\Finder::create() + ->files() + ->name('*.php') + ->exclude('Fixtures') + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') +; + +return Symfony\CS\Config::create() + ->setUsingCache(true) + //->setUsingLinter(false) + ->setRiskyAllowed(true) + ->setRules(array( + '@PSR2' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_return' => true, + 'header_comment' => array('header' => $header), + 'include' => true, + 'long_array_syntax' => true, + 'method_separation' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_between_uses' => true, + 'no_duplicate_semicolons' => true, + 'no_extra_consecutive_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unused_imports' => true, + 'object_operator_without_whitespace' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_package' => true, + 'phpdoc_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_type_to_var' => true, + 'psr0' => true, + 'single_blank_line_before_namespace' => true, + 'spaces_cast' => true, + 'standardize_not_equals' => true, + 'ternary_operator_spaces' => true, + 'trailing_comma_in_multiline_array' => true, + 'whitespacy_lines' => true, + )) + ->finder($finder) +; diff --git a/core/vendor/monolog/monolog/CHANGELOG.md b/core/vendor/monolog/monolog/CHANGELOG.md new file mode 100644 index 0000000..bcf679c --- /dev/null +++ b/core/vendor/monolog/monolog/CHANGELOG.md @@ -0,0 +1,370 @@ +### 1.24.0 (2018-11-05) + + * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors + * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers) + * Added a way to log signals being received using Monolog\SignalHandler + * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler + * Added InsightOpsHandler to migrate users of the LogEntriesHandler + * Added protection to NormalizerHandler against circular and very deep structures, it now stops normalizing at a depth of 9 + * Added capture of stack traces to ErrorHandler when logging PHP errors + * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts + * Added forwarding of context info to FluentdFormatter + * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example + * Added ability to extend/override BrowserConsoleHandler + * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility + * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility + * Dropped official support for HHVM in test builds + * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain + * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases + * Fixed HipChatHandler bug where slack dropped messages randomly + * Fixed normalization of objects in Slack handlers + * Fixed support for PHP7's Throwable in NewRelicHandler + * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory + * Fixed table row styling issues in HtmlFormatter + * Fixed RavenHandler dropping the message when logging exception + * Fixed WhatFailureGroupHandler skipping processors when using handleBatch + and implement it where possible + * Fixed display of anonymous class names + +### 1.23.0 (2017-06-19) + + * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument + * Fixed GelfHandler truncation to be per field and not per message + * Fixed compatibility issue with PHP <5.3.6 + * Fixed support for headless Chrome in ChromePHPHandler + * Fixed support for latest Aws SDK in DynamoDbHandler + * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler + +### 1.22.1 (2017-03-13) + + * Fixed lots of minor issues in the new Slack integrations + * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces + +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occuring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputing API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/core/vendor/monolog/monolog/LICENSE b/core/vendor/monolog/monolog/LICENSE new file mode 100644 index 0000000..1647321 --- /dev/null +++ b/core/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2016 Jordi Boggiano + +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/core/vendor/monolog/monolog/README.md b/core/vendor/monolog/monolog/README.md new file mode 100644 index 0000000..d756944 --- /dev/null +++ b/core/vendor/monolog/monolog/README.md @@ -0,0 +1,94 @@ +# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + +## Installation + +Install the latest version with + +```bash +$ composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->addWarning('Foo'); +$log->addError('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony2](http://symfony.com) comes out of the box with Monolog. +- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog. +- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](http://www.ppi.io/) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. + +### Author + +Jordi Boggiano - -
    +See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project. + +### License + +Monolog is licensed under the MIT License - see the `LICENSE` file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/core/vendor/monolog/monolog/composer.json b/core/vendor/monolog/monolog/composer.json new file mode 100644 index 0000000..3b0c880 --- /dev/null +++ b/core/vendor/monolog/monolog/composer.json @@ -0,0 +1,66 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "http://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "graylog2/gelf-php": "~1.0", + "sentry/sentry": "^0.13", + "ruflin/elastica": ">=0.90 <3.0", + "doctrine/couchdb": "~1.0@dev", + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "php-amqplib/php-amqplib": "~2.4", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit-mock-objects": "2.3.0", + "jakub-onderka/php-parallel-lint": "0.9" + }, + "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis", + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "sentry/sentry": "Allow sending log messages to a Sentry server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "php-console/php-console": "Allow sending log messages to Google Chrome" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "scripts": { + "test": [ + "parallel-lint . --exclude vendor", + "phpunit" + ] + } +} diff --git a/core/vendor/monolog/monolog/doc/01-usage.md b/core/vendor/monolog/monolog/doc/01-usage.md new file mode 100644 index 0000000..8e2551f --- /dev/null +++ b/core/vendor/monolog/monolog/doc/01-usage.md @@ -0,0 +1,231 @@ +# Using Monolog + +- [Installation](#installation) +- [Core Concepts](#core-concepts) +- [Log Levels](#log-levels) +- [Configuring a logger](#configuring-a-logger) +- [Adding extra data in the records](#adding-extra-data-in-the-records) +- [Leveraging channels](#leveraging-channels) +- [Customizing the log format](#customizing-the-log-format) + +## Installation + +Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog)) +and as such installable via [Composer](http://getcomposer.org/). + +```bash +composer require monolog/monolog +``` + +If you do not use Composer, you can grab the code from GitHub, and use any +PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader)) +to load Monolog classes. + +## Core Concepts + +Every `Logger` instance has a channel (name) and a stack of handlers. Whenever +you add a record to the logger, it traverses the handler stack. Each handler +decides whether it fully handled the record, and if so, the propagation of the +record ends there. + +This allows for flexible logging setups, for example having a `StreamHandler` at +the bottom of the stack that will log anything to disk, and on top of that add +a `MailHandler` that will send emails only when an error message is logged. +Handlers also have a `$bubble` property which defines whether they block the +record or not if they handled it. In this example, setting the `MailHandler`'s +`$bubble` argument to false means that records handled by the `MailHandler` will +not propagate to the `StreamHandler` anymore. + +You can create many `Logger`s, each defining a channel (e.g.: db, request, +router, ..) and each of them combining various handlers, which can be shared +or not. The channel is reflected in the logs and allows you to easily see or +filter records. + +Each Handler also has a Formatter, a default one with settings that make sense +will be created if you don't set one. The formatters normalize and format +incoming records so that they can be used by the handlers to output useful +information. + +Custom severity levels are not available. Only the eight +[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice, +warning, error, critical, alert, emergency) are present for basic filtering +purposes, but for sorting and other use cases that would require +flexibility, you should add Processors to the Logger that can add extra +information (tags, user ip, ..) to the records before they are handled. + +## Log Levels + +Monolog supports the logging levels described by [RFC 5424](http://tools.ietf.org/html/rfc5424). + +- **DEBUG** (100): Detailed debug information. + +- **INFO** (200): Interesting events. Examples: User logs in, SQL logs. + +- **NOTICE** (250): Normal but significant events. + +- **WARNING** (300): Exceptional occurrences that are not errors. Examples: + Use of deprecated APIs, poor use of an API, undesirable things that are not + necessarily wrong. + +- **ERROR** (400): Runtime errors that do not require immediate action but + should typically be logged and monitored. + +- **CRITICAL** (500): Critical conditions. Example: Application component + unavailable, unexpected exception. + +- **ALERT** (550): Action must be taken immediately. Example: Entire website + down, database unavailable, etc. This should trigger the SMS alerts and wake + you up. + +- **EMERGENCY** (600): Emergency: system is unusable. + +## Configuring a logger + +Here is a basic setup to log to a file and to firephp on the DEBUG level: + +```php +pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG)); +$logger->pushHandler(new FirePHPHandler()); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +Let's explain it. The first step is to create the logger instance which will +be used in your code. The argument is a channel name, which is useful when +you use several loggers (see below for more details about it). + +The logger itself does not know how to handle a record. It delegates it to +some handlers. The code above registers two handlers in the stack to allow +handling records in two different ways. + +Note that the FirePHPHandler is called first as it is added on top of the +stack. This allows you to temporarily add a logger with bubbling disabled if +you want to override other configured loggers. + +> If you use Monolog standalone and are looking for an easy way to +> configure many handlers, the [theorchard/monolog-cascade](https://github.com/theorchard/monolog-cascade) +> can help you build complex logging configs via PHP arrays, yaml or json configs. + +## Adding extra data in the records + +Monolog provides two different ways to add extra informations along the simple +textual message. + +### Using the logging context + +The first way is the context, allowing to pass an array of data along the +record: + +```php +addInfo('Adding a new user', array('username' => 'Seldaek')); +``` + +Simple handlers (like the StreamHandler for instance) will simply format +the array to a string but richer handlers can take advantage of the context +(FirePHP is able to display arrays in pretty way for instance). + +### Using processors + +The second way is to add extra data for all records by using a processor. +Processors can be any callable. They will get the record as parameter and +must return it after having eventually changed the `extra` part of it. Let's +write a processor adding some dummy data in the record: + +```php +pushProcessor(function ($record) { + $record['extra']['dummy'] = 'Hello world!'; + + return $record; +}); +``` + +Monolog provides some built-in processors that can be used in your project. +Look at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#processors) for the list. + +> Tip: processors can also be registered on a specific handler instead of + the logger to apply only for this handler. + +## Leveraging channels + +Channels are a great way to identify to which part of the application a record +is related. This is useful in big applications (and is leveraged by +MonologBundle in Symfony2). + +Picture two loggers sharing a handler that writes to a single log file. +Channels would allow you to identify the logger that issued every record. +You can easily grep through the log files filtering this or that channel. + +```php +pushHandler($stream); +$logger->pushHandler($firephp); + +// Create a logger for the security-related stuff with a different channel +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +$securityLogger->pushHandler($firephp); + +// Or clone the first one to only change the channel +$securityLogger = $logger->withName('security'); +``` + +## Customizing the log format + +In Monolog it's easy to customize the format of the logs written into files, +sockets, mails, databases and other handlers. Most of the handlers use the + +```php +$record['formatted'] +``` + +value to be automatically put into the log device. This value depends on the +formatter settings. You can choose between predefined formatter classes or +write your own (e.g. a multiline text file for human-readable output). + +To configure a predefined formatter class, just set it as the handler's field: + +```php +// the default date format is "Y-m-d H:i:s" +$dateFormat = "Y n j, g:i a"; +// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n" +$output = "%datetime% > %level_name% > %message% %context% %extra%\n"; +// finally, create a formatter +$formatter = new LineFormatter($output, $dateFormat); + +// Create a handler +$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG); +$stream->setFormatter($formatter); +// bind it to a logger object +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +``` + +You may also reuse the same formatter between multiple handlers and share those +handlers between multiple loggers. + +[Handlers, Formatters and Processors](02-handlers-formatters-processors.md) → diff --git a/core/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md b/core/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md new file mode 100644 index 0000000..af45913 --- /dev/null +++ b/core/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md @@ -0,0 +1,158 @@ +# Handlers, Formatters and Processors + +- [Handlers](#handlers) + - [Log to files and syslog](#log-to-files-and-syslog) + - [Send alerts and emails](#send-alerts-and-emails) + - [Log specific servers and networked logging](#log-specific-servers-and-networked-logging) + - [Logging in development](#logging-in-development) + - [Log to databases](#log-to-databases) + - [Wrappers / Special Handlers](#wrappers--special-handlers) +- [Formatters](#formatters) +- [Processors](#processors) +- [Third Party Packages](#third-party-packages) + +## Handlers + +### Log to files and syslog + +- _StreamHandler_: Logs records into any PHP stream, use this for log files. +- _RotatingFileHandler_: Logs records to a file and creates one logfile per day. + It will also delete files older than `$maxFiles`. You should use + [logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile + setups though, this is just meant as a quick and dirty solution. +- _SyslogHandler_: Logs records to the syslog. +- _ErrorLogHandler_: Logs records to PHP's + [`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function. + +### Send alerts and emails + +- _NativeMailerHandler_: Sends emails using PHP's + [`mail()`](http://php.net/manual/en/function.mail.php) function. +- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance. +- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API. +- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API. +- _FlowdockHandler_: Logs records to a [Flowdock](https://www.flowdock.com/) account. +- _SlackHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slack API. +- _SlackbotHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slackbot incoming hook. +- _SlackWebhookHandler_: Logs records to a [Slack](https://www.slack.com/) account using Slack Webhooks. +- _MandrillHandler_: Sends emails via the Mandrill API using a [`Swift_Message`](http://swiftmailer.org/) instance. +- _FleepHookHandler_: Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks. +- _IFTTTHandler_: Notifies an [IFTTT](https://ifttt.com/maker) trigger with the log channel, level name and message. + +### Log specific servers and networked logging + +- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this + for UNIX and TCP sockets. See an [example](sockets.md). +- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible + server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+). +- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server. +- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server. +- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using + [raven](https://packagist.org/packages/raven/raven). +- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server. +- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application. +- _LogglyHandler_: Logs records to a [Loggly](http://www.loggly.com/) account. +- _RollbarHandler_: Logs records to a [Rollbar](https://rollbar.com/) account. +- _SyslogUdpHandler_: Logs records to a remote [Syslogd](http://www.rsyslog.com/) server. +- _LogEntriesHandler_: Logs records to a [LogEntries](http://logentries.com/) account. +- _InsightOpsHandler_: Logs records to a [InsightOps](https://www.rapid7.com/products/insightops/) account. + +### Logging in development + +- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing + inline `console` messages within [FireBug](http://getfirebug.com/). +- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing + inline `console` messages within Chrome. +- _BrowserConsoleHandler_: Handler to send logs to browser's Javascript `console` with + no browser extension required. Most browsers supporting `console` API are supported. +- _PHPConsoleHandler_: Handler for [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef), providing + inline `console` and notification popup messages within Chrome. + +### Log to databases + +- _RedisHandler_: Logs records to a [redis](http://redis.io) server. +- _MongoDBHandler_: Handler to write records in MongoDB via a + [Mongo](http://pecl.php.net/package/mongo) extension connection. +- _CouchDBHandler_: Logs records to a CouchDB server. +- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM. +- _ElasticSearchHandler_: Logs records to an Elastic Search server. +- _DynamoDbHandler_: Logs records to a DynamoDB table with the [AWS SDK](https://github.com/aws/aws-sdk-php). + +### Wrappers / Special Handlers + +- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as + parameter and will accumulate log records of all levels until a record + exceeds the defined severity level. At which point it delivers all records, + including those of lower severity, to the handler it wraps. This means that + until an error actually happens you will not see anything in your logs, but + when it happens you will have the full information, including debug and info + records. This provides you with all the information you need, but only when + you need it. +- _DeduplicationHandler_: Useful if you are sending notifications or emails + when critical errors occur. It takes a logger as parameter and will + accumulate log records of all levels until the end of the request (or + `flush()` is called). At that point it delivers all records to the handler + it wraps, but only if the records are unique over a given time period + (60seconds by default). If the records are duplicates they are simply + discarded. The main use of this is in case of critical failure like if your + database is unreachable for example all your requests will fail and that + can result in a lot of notifications being sent. Adding this handler reduces + the amount of notifications to a manageable level. +- _WhatFailureGroupHandler_: This handler extends the _GroupHandler_ ignoring + exceptions raised by each child handler. This allows you to ignore issues + where a remote tcp connection may have died but you do not want your entire + application to crash and may wish to continue to log to other handlers. +- _BufferHandler_: This handler will buffer all the log records it receives + until `close()` is called at which point it will call `handleBatch()` on the + handler it wraps with all the log messages at once. This is very useful to + send an email with all records at once for example instead of having one mail + for every log record. +- _GroupHandler_: This handler groups other handlers. Every record received is + sent to all the handlers it is configured with. +- _FilterHandler_: This handler only lets records of the given levels through + to the wrapped handler. +- _SamplingHandler_: Wraps around another handler and lets you sample records + if you only want to store some of them. +- _NullHandler_: Any record it can handle will be thrown away. This can be used + to put on top of an existing handler stack to disable it temporarily. +- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger +- _TestHandler_: Used for testing, it records everything that is sent to it and + has accessors to read out the information. +- _HandlerWrapper_: A simple handler wrapper you can inherit from to create + your own wrappers easily. + +## Formatters + +- _LineFormatter_: Formats a log record into a one-line string. +- _HtmlFormatter_: Used to format log records into a human readable html table, mainly suitable for emails. +- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded. +- _ScalarFormatter_: Used to format log records into an associative array of scalar values. +- _JsonFormatter_: Encodes a log record into json. +- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler. +- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler. +- _GelfMessageFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler. +- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/latest). +- _ElasticaFormatter_: Used to format log records into an Elastica\Document object, only useful for the ElasticSearchHandler. +- _LogglyFormatter_: Used to format log records into Loggly messages, only useful for the LogglyHandler. +- _FlowdockFormatter_: Used to format log records into Flowdock messages, only useful for the FlowdockHandler. +- _MongoDBFormatter_: Converts \DateTime instances to \MongoDate and objects recursively to arrays, only useful with the MongoDBHandler. + +## Processors + +- _PsrLogMessageProcessor_: Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`. +- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated. +- _WebProcessor_: Adds the current request URI, request method and client IP to a log record. +- _MemoryUsageProcessor_: Adds the current memory usage to a log record. +- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record. +- _ProcessIdProcessor_: Adds the process id to a log record. +- _UidProcessor_: Adds a unique identifier to a log record. +- _GitProcessor_: Adds the current git branch and commit to a log record. +- _TagProcessor_: Adds an array of predefined tags to a log record. + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +← [Usage](01-usage.md) | [Utility classes](03-utilities.md) → diff --git a/core/vendor/monolog/monolog/doc/03-utilities.md b/core/vendor/monolog/monolog/doc/03-utilities.md new file mode 100644 index 0000000..fd3fd0e --- /dev/null +++ b/core/vendor/monolog/monolog/doc/03-utilities.md @@ -0,0 +1,15 @@ +# Utilities + +- _Registry_: The `Monolog\Registry` class lets you configure global loggers that you + can then statically access from anywhere. It is not really a best practice but can + help in some older codebases or for ease of use. +- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register + a Logger instance as an exception handler, error handler or fatal error handler. +- _SignalHandler_: The `Monolog\SignalHandler` class allows you to easily register + a Logger instance as a POSIX signal handler. +- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log + level is reached. +- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain + log level is reached, depending on which channel received the log record. + +← [Handlers, Formatters and Processors](02-handlers-formatters-processors.md) | [Extending Monolog](04-extending.md) → diff --git a/core/vendor/monolog/monolog/doc/04-extending.md b/core/vendor/monolog/monolog/doc/04-extending.md new file mode 100644 index 0000000..ebd9104 --- /dev/null +++ b/core/vendor/monolog/monolog/doc/04-extending.md @@ -0,0 +1,76 @@ +# Extending Monolog + +Monolog is fully extensible, allowing you to adapt your logger to your needs. + +## Writing your own handler + +Monolog provides many built-in handlers. But if the one you need does not +exist, you can write it and use it in your logger. The only requirement is +to implement `Monolog\Handler\HandlerInterface`. + +Let's write a PDOHandler to log records to a database. We will extend the +abstract class provided by Monolog to keep things DRY. + +```php +pdo = $pdo; + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if (!$this->initialized) { + $this->initialize(); + } + + $this->statement->execute(array( + 'channel' => $record['channel'], + 'level' => $record['level'], + 'message' => $record['formatted'], + 'time' => $record['datetime']->format('U'), + )); + } + + private function initialize() + { + $this->pdo->exec( + 'CREATE TABLE IF NOT EXISTS monolog ' + .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)' + ); + $this->statement = $this->pdo->prepare( + 'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)' + ); + + $this->initialized = true; + } +} +``` + +You can now use this handler in your logger: + +```php +pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite'))); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +The `Monolog\Handler\AbstractProcessingHandler` class provides most of the +logic needed for the handler, including the use of processors and the formatting +of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``). + +← [Utility classes](03-utilities.md) diff --git a/core/vendor/monolog/monolog/doc/sockets.md b/core/vendor/monolog/monolog/doc/sockets.md new file mode 100644 index 0000000..ea9cf0e --- /dev/null +++ b/core/vendor/monolog/monolog/doc/sockets.md @@ -0,0 +1,39 @@ +Sockets Handler +=============== + +This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen) +or [pfsockopen](http://php.net/pfsockopen). + +Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening +the connections between requests. + +You can use a `unix://` prefix to access unix sockets and `udp://` to open UDP sockets instead of the default TCP. + +Basic Example +------------- + +```php +setPersistent(true); + +// Now add the handler +$logger->pushHandler($handler, Logger::DEBUG); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); + +``` + +In this example, using syslog-ng, you should see the log on the log server: + + cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] [] + diff --git a/core/vendor/monolog/monolog/phpunit.xml.dist b/core/vendor/monolog/monolog/phpunit.xml.dist new file mode 100644 index 0000000..20d82b6 --- /dev/null +++ b/core/vendor/monolog/monolog/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + tests/Monolog/ + + + + + + src/Monolog/ + + + + + + + diff --git a/core/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/core/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 0000000..adc55bd --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Monolog\Handler\AbstractHandler; +use Monolog\Registry; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private $logger; + + private $previousExceptionHandler; + private $uncaughtExceptionLevel; + + private $previousErrorHandler; + private $errorLevelMap; + private $handleOnlyReportedErrors; + + private $hasFatalErrorHandler; + private $fatalLevel; + private $reservedMemory; + private $lastFatalTrace; + private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + { + //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 + class_exists('\\Psr\\Log\\LogLevel', true); + + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevel !== false) { + $handler->registerExceptionHandler($exceptionLevel); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + public function registerExceptionHandler($level = null, $callPrevious = true) + { + $prev = set_exception_handler(array($this, 'handleException')); + $this->uncaughtExceptionLevel = $level; + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + } + + public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) + { + $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + } + + public function registerFatalHandler($level = null, $reservedMemorySize = 20) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = $level; + $this->hasFatalErrorHandler = true; + } + + protected function defaultErrorLevelMap() + { + return array( + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ); + } + + /** + * @private + */ + public function handleException($e) + { + $this->logger->log( + $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, + sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + + if ($this->previousExceptionHandler) { + call_user_func($this->previousExceptionHandler, $e); + } + + exit(255); + } + + /** + * @private + */ + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { + return; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { + $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); + } else { + // http://php.net/manual/en/function.debug-backtrace.php + // As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. + // Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. + $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + array_shift($trace); // Exclude handleError from trace + $this->lastFatalTrace = $trace; + } + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + } + } + + /** + * @private + */ + public function handleFatalError() + { + $this->reservedMemory = null; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $this->logger->log( + $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace) + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + if ($handler instanceof AbstractHandler) { + $handler->close(); + } + } + } + } + } + + private static function codeToString($code) + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 0000000..9beda1e --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file'], $record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file'], $record['extra']['line']); + } + + $message = array('message' => $record['message']); + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return array( + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ); + } + + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 0000000..4c556cf --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Elastica\Document; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected $index; + + /** + * @var string Elastic search document type + */ + protected $type; + + /** + * @param string $index Elastic Search index name + * @param string $type Elastic Search document type + */ + public function __construct($index, $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + * @return string + */ + public function getIndex() + { + return $this->index; + } + + /** + * Getter type + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @param array $record Log message + * @return Document + */ + protected function getDocument($record) + { + $document = new Document(); + $document->setData($record); + $document->setType($this->type); + $document->setIndex($this->index); + + return $document; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 0000000..5094af3 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + */ +class FlowdockFormatter implements FormatterInterface +{ + /** + * @var string + */ + private $source; + + /** + * @var string + */ + private $sourceEmail; + + /** + * @param string $source + * @param string $sourceEmail + */ + public function __construct($source, $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $tags = array( + '#logs', + '#' . strtolower($record['level_name']), + '#' . $record['channel'], + ); + + foreach ($record['extra'] as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record['level_name'], + $this->getShortMessage($record['message']) + ); + + $record['flowdock'] = array( + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record['message'], + 'tags' => $tags, + 'project' => $this->source, + ); + + return $record; + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + /** + * @param string $message + * + * @return string + */ + public function getShortMessage($message) + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 0000000..46a91ff --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected $levelTag = false; + + public function __construct($levelTag = false) + { + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); + } + + $this->levelTag = (bool) $levelTag; + } + + public function isUsingLevelsInTag() + { + return $this->levelTag; + } + + public function format(array $record) + { + $tag = $record['channel']; + if ($this->levelTag) { + $tag .= '.' . strtolower($record['level_name']); + } + + $message = array( + 'message' => $record['message'], + 'context' => $record['context'], + 'extra' => $record['extra'], + ); + + if (!$this->levelTag) { + $message['level'] = $record['level']; + $message['level_name'] = $record['level_name']; + } + + return json_encode(array($tag, $record['datetime']->getTimestamp(), $message)); + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000..b5de751 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 0000000..2c1b0e8 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; + +/** + * Serializes a log message to GELF + * @see http://www.graylog2.org/about/gelf + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + const DEFAULT_MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int max length per field + */ + protected $maxLength; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private $logLevels = array( + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ); + + public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null) + { + parent::__construct('U.u'); + + $this->systemName = $systemName ?: gethostname(); + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if (!isset($record['datetime'], $record['message'], $record['level'])) { + throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); + } + + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setHost($this->systemName) + ->setLevel($this->logLevels[$record['level']]); + + // message length + system name length + 200 for padding / metadata + $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); + + if ($len > $this->maxLength) { + $message->setShortMessage(substr($record['message'], 0, $this->maxLength)); + } + + if (isset($record['channel'])) { + $message->setFacility($record['channel']); + } + if (isset($record['extra']['line'])) { + $message->setLine($record['extra']['line']); + unset($record['extra']['line']); + } + if (isset($record['extra']['file'])) { + $message->setFile($record['extra']['file']); + unset($record['extra']['file']); + } + + foreach ($record['extra'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->extraPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->extraPrefix . $key, substr($val, 0, $this->maxLength)); + break; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($record['context'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->contextPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->contextPrefix . $key, substr($val, 0, $this->maxLength)); + break; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + if (null === $message->getFile() && isset($record['context']['exception']['file'])) { + if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) { + $message->setFile($matches[1]); + $message->setLine($matches[2]); + } + } + + return $message; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 0000000..dfc0b4a --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + */ + protected $logLevels = array( + Logger::DEBUG => '#cccccc', + Logger::INFO => '#468847', + Logger::NOTICE => '#3a87ad', + Logger::WARNING => '#c09853', + Logger::ERROR => '#f0ad4e', + Logger::CRITICAL => '#FF7708', + Logger::ALERT => '#C12A19', + Logger::EMERGENCY => '#000000', + ); + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + * @return string + */ + protected function addRow($th, $td = ' ', $escapeTd = true) + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
    '.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
    '; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + * @param int $level Error level + * @return string + */ + protected function addTitle($title, $level) + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

    '.$title.'

    '; + } + + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record) + { + $output = $this->addTitle($record['level_name'], $record['level']); + $output .= ''; + + $output .= $this->addRow('Message', (string) $record['message']); + $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); + $output .= $this->addRow('Channel', $record['channel']); + if ($record['context']) { + $embeddedTable = '
    '; + foreach ($record['context'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
    '; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if ($record['extra']) { + $embeddedTable = ''; + foreach ($record['extra'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
    '; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return str_replace('\\/', '/', json_encode($data)); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000..9bd305f --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; +use Monolog\Utils; +use Throwable; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter extends NormalizerFormatter +{ + const BATCH_MODE_JSON = 1; + const BATCH_MODE_NEWLINES = 2; + + protected $batchMode; + protected $appendNewline; + + /** + * @var bool + */ + protected $includeStacktraces = false; + + /** + * @param int $batchMode + * @param bool $appendNewline + */ + public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true) + { + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + * + * @return int + */ + public function getBatchMode() + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + * + * @return bool + */ + public function isAppendingNewlines() + { + return $this->appendNewline; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + switch ($this->batchMode) { + case static::BATCH_MODE_NEWLINES: + return $this->formatBatchNewlines($records); + + case static::BATCH_MODE_JSON: + default: + return $this->formatBatchJson($records); + } + } + + /** + * @param bool $include + */ + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + } + + /** + * Return a JSON-encoded array of records. + * + * @param array $records + * @return string + */ + protected function formatBatchJson(array $records) + { + return $this->toJson($this->normalize($records), true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @param array $records + * @return string + */ + protected function formatBatchNewlines(array $records) + { + $instance = $this; + + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + array_walk($records, function (&$value, $key) use ($instance) { + $value = $instance->format($value); + }); + $this->appendNewline = $oldNewline; + + return implode("\n", $records); + } + + /** + * Normalizes given $data. + * + * @param mixed $data + * + * @return mixed + */ + protected function normalize($data, $depth = 0) + { + if ($depth > 9) { + return 'Over 9 levels deep, aborting normalization'; + } + + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > 1000) { + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth+1); + } + + return $normalized; + } + + if ($data instanceof Exception || $data instanceof Throwable) { + return $this->normalizeException($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * @param Exception|Throwable $e + * + * @return array + */ + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + } + + $data = array( + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($this->includeStacktraces) { + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } elseif (isset($frame['function']) && $frame['function'] === '{closure}') { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $frame['function']; + } else { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->normalize($frame); + } + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000..f98e1a6 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected $format; + protected $allowInlineLineBreaks; + protected $ignoreEmptyContextAndExtra; + protected $includeStacktraces; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + parent::__construct($dateFormat); + } + + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + } + } + + public function allowInlineLineBreaks($allow = true) + { + $this->allowInlineLineBreaks = $allow; + } + + public function ignoreEmptyContextAndExtra($ignore = true) + { + $this->ignoreEmptyContextAndExtra = $ignore; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = parent::format($record); + + $output = $this->format; + + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + public function stringify($value) + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof \Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + } + + $previousText = ''; + if ($previous = $e->getPrevious()) { + do { + $previousText .= ', '.Utils::getClass($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + + $str = '[object] ('.Utils::getClass($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; + if ($this->includeStacktraces) { + $str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n"; + } + + return $str; + } + + protected function convertToString($data) + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data, true); + } + + return str_replace('\\/', '/', @json_encode($data)); + } + + protected function replaceNewlines($str) + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + return str_replace(array('\r', '\n'), array("\r", "\n"), $str); + } + + return $str; + } + + return str_replace(array("\r\n", "\r", "\n"), ' ', $str); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 0000000..401859b --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + * + * @param int $batchMode + */ + public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record) + { + if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) { + $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); + // TODO 2.0 unset the 'datetime' parameter, retained for BC + } + + return parent::format($record); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 0000000..8f83bec --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see http://logstash.net/ + * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + const V0 = 0; + const V1 = 1; + + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int logstash format version to use + */ + protected $version; + + /** + * @param string $applicationName the application that sends the data, used as the "type" field of logstash + * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraPrefix prefix for extra keys inside logstash "fields" + * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ + * @param int $version the logstash format version to use, defaults to 0 + */ + public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0) + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName ?: gethostname(); + $this->applicationName = $applicationName; + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->version = $version; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if ($this->version === self::V1) { + $message = $this->formatV1($record); + } else { + $message = $this->formatV0($record); + } + + return $this->toJson($message) . "\n"; + } + + protected function formatV0(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@source' => $this->systemName, + '@fields' => array(), + ); + if (isset($record['message'])) { + $message['@message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['@tags'] = array($record['channel']); + $message['@fields']['channel'] = $record['channel']; + } + if (isset($record['level'])) { + $message['@fields']['level'] = $record['level']; + } + if ($this->applicationName) { + $message['@type'] = $this->applicationName; + } + if (isset($record['extra']['server'])) { + $message['@source_host'] = $record['extra']['server']; + } + if (isset($record['extra']['url'])) { + $message['@source_path'] = $record['extra']['url']; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message['@fields'][$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message['@fields'][$this->contextPrefix . $key] = $val; + } + } + + return $message; + } + + protected function formatV1(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ); + if (isset($record['message'])) { + $message['message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['type'] = $record['channel']; + $message['channel'] = $record['channel']; + } + if (isset($record['level_name'])) { + $message['level'] = $record['level_name']; + } + if ($this->applicationName) { + $message['type'] = $this->applicationName; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message[$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message[$this->contextPrefix . $key] = $val; + } + } + + return $message; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 0000000..eb7be84 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + private $exceptionTraceAsString; + private $maxNestingLevel; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = (bool) $exceptionTraceAsString; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + return $this->formatArray($record); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function formatArray(array $record, $nestingLevel = 0) + { + if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) { + foreach ($record as $name => $value) { + if ($value instanceof \DateTime) { + $record[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Exception) { + $record[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $record[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value)) { + $record[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + } else { + $record = '[...]'; + } + + return $record; + } + + protected function formatObject($value, $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = Utils::getClass($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + protected function formatException(\Exception $exception, $nestingLevel) + { + $formattedException = array( + 'class' => Utils::getClass($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ); + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTime $value, $nestingLevel) + { + return new \MongoDate($value->getTimestamp()); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000..6686657 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,314 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; +use Monolog\Utils; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function normalize($data, $depth = 0) + { + if ($depth > 9) { + return 'Over 9 levels deep, aborting normalization'; + } + + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > 1000) { + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth+1); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_object($data)) { + // TODO 2.0 only check for Throwable + if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { + return $this->normalizeException($data); + } + + // non-serializable objects that implement __toString stringified + if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + $value = $data->__toString(); + } else { + // the rest is json-serialized in some way + $value = $this->toJson($data, true); + } + + return sprintf("[object] (%s: %s)", Utils::getClass($data), $value); + } + + if (is_resource($data)) { + return sprintf('[resource] (%s)', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + } + + $data = array( + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + $data['detail'] = $e->detail; + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } elseif (isset($frame['function']) && $frame['function'] === '{closure}') { + // Simplify closures handling + $data['trace'][] = $frame['function']; + } else { + if (isset($frame['args'])) { + // Make sure that objects present as arguments are not serialized nicely but rather only + // as a class name to avoid any unexpected leak of sensitive information + $frame['args'] = array_map(function ($arg) { + if (is_object($arg) && !($arg instanceof \DateTime || $arg instanceof \DateTimeInterface)) { + return sprintf("[object] (%s)", Utils::getClass($arg)); + } + + return $arg; + }, $frame['args']); + } + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->toJson($this->normalize($frame), true); + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param bool $ignoreErrors + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string + */ + protected function toJson($data, $ignoreErrors = false) + { + // suppress json_encode errors since it's twitchy with some inputs + if ($ignoreErrors) { + return @$this->jsonEncode($data); + } + + $json = $this->jsonEncode($data); + + if ($json === false) { + $json = $this->handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * @param mixed $data + * @return string JSON encoded data or null on failure + */ + private function jsonEncode($data) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * inital error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + private function handleJsonError($code, $data) + { + if ($code !== JSON_ERROR_UTF8) { + $this->throwEncodeError($code, $data); + } + + if (is_string($data)) { + $this->detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array($this, 'detectAndCleanUtf8')); + } else { + $this->throwEncodeError($code, $data); + } + + $json = $this->jsonEncode($data); + + if ($json === false) { + $this->throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + */ + private function throwEncodeError($code, $data) + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed &$data Input to check and convert if needed + * @private + */ + public function detectAndCleanUtf8(&$data) + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { return utf8_encode($m[0]); }, + $data + ); + $data = str_replace( + array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), + array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + $data + ); + } + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 0000000..5d345d5 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats data into an associative array of scalar values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * {@inheritdoc} + */ + public function format(array $record) + { + foreach ($record as $key => $value) { + $record[$key] = $this->normalizeValue($value); + } + + return $record; + } + + /** + * @param mixed $value + * @return mixed + */ + protected function normalizeValue($value) + { + $normalized = $this->normalize($value); + + if (is_array($normalized) || is_object($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/core/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000..65dba99 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + const TABLE = 'table'; + + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + $record = $this->normalize($record); + $message = array('message' => $record['message']); + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + if (isset($record['context'][self::TABLE])) { + $type = 'TABLE'; + $label = $record['channel'] .': '. $record['message']; + $message = $record['context'][self::TABLE]; + } else { + $type = $this->logLevels[$record['level']]; + $label = $record['channel']; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson(array( + array( + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ), + $message, + ), $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%s|%s|', + strlen($json), + $json + ); + } + + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + protected function normalize($data, $depth = 0) + { + if (is_object($data) && !$data instanceof \DateTime) { + return $data; + } + + return parent::normalize($data, $depth); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000..92b9d45 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\ResettableInterface; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface, ResettableInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = true; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param int|string $level Level or level name + * @return self + */ + public function setLevel($level) + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param bool $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return bool true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } catch (\Throwable $e) { + // do nothing + } + } + + public function reset() + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000..e1e8953 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 0000000..8c76aca --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + protected $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ); + + /** + * List of valid log facility names. + */ + protected $facilities = array( + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ); + + /** + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 0000000..e5a46bc --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; + +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var AMQPExchange|AMQPChannel $exchange + */ + protected $exchange; + + /** + * @var string + */ + protected $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string $exchangeName + * @param int $level + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + { + if ($exchange instanceof AMQPExchange) { + $exchange->setName($exchangeName); + } elseif ($exchange instanceof AMQPChannel) { + $this->exchangeName = $exchangeName; + } else { + throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $data = $record["formatted"]; + $routingKey = $this->getRoutingKey($record); + + if ($this->exchange instanceof AMQPExchange) { + $this->exchange->publish( + $data, + $routingKey, + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records) + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + * + * @param array $record + * @return string + */ + protected function getRoutingKey(array $record) + { + $routingKey = sprintf( + '%s.%s', + // TODO 2.0 remove substr call + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + return strtolower($routingKey); + } + + /** + * @param string $data + * @return AMQPMessage + */ + private function createAmqpMessage($data) + { + return new AMQPMessage( + (string) $data, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 0000000..23cf23b --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + protected static $initialized = false; + protected static $records = array(); + + /** + * {@inheritDoc} + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + // Accumulate records + static::$records[] = $record; + + // Register shutdown handler if not already done + if (!static::$initialized) { + static::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send() + { + $format = static::getResponseFormat(); + if ($format === 'unknown') { + return; + } + + if (count(static::$records)) { + if ($format === 'html') { + static::writeOutput(''); + } elseif ($format === 'js') { + static::writeOutput(static::generateScript()); + } + static::resetStatic(); + } + } + + public function close() + { + self::resetStatic(); + } + + public function reset() + { + self::resetStatic(); + } + + /** + * Forget all logged records + */ + public static function resetStatic() + { + static::$records = array(); + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction() + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + } + } + + /** + * Wrapper for echo to allow overriding + * + * @param string $str + */ + protected static function writeOutput($str) + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + */ + protected static function getResponseFormat() + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { + return 'js'; + } + if (stripos($header, 'text/html') === false) { + return 'unknown'; + } + break; + } + } + + return 'html'; + } + + private static function generateScript() + { + $script = array(); + foreach (static::$records as $record) { + $context = static::dump('Context', $record['context']); + $extra = static::dump('Extra', $record['extra']); + + if (empty($context) && empty($extra)) { + $script[] = static::call_array('log', static::handleStyles($record['formatted'])); + } else { + $script = array_merge($script, + array(static::call_array('groupCollapsed', static::handleStyles($record['formatted']))), + $context, + $extra, + array(static::call('groupEnd')) + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + private static function handleStyles($formatted) + { + $args = array(static::quote('font-weight: normal')); + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); + $args[] = '"font-weight: normal"'; + + $pos = $match[0][1]; + $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); + } + + array_unshift($args, static::quote($format)); + + return $args; + } + + private static function handleCustomStyles($style, $string) + { + static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); + static $labels = array(); + + return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[count($labels) % count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + } + + private static function dump($title, array $dict) + { + $script = array(); + $dict = array_filter($dict); + if (empty($dict)) { + return $script; + } + $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (empty($value)) { + $value = static::quote(''); + } + $script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value); + } + + return $script; + } + + private static function quote($arg) + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + private static function call() + { + $args = func_get_args(); + $method = array_shift($args); + + return static::call_array($method, $args); + } + + private static function call_array($method, array $args) + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000..61d1b50 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\ResettableInterface; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler +{ + protected $handler; + protected $bufferSize = 0; + protected $bufferLimit; + protected $flushOnOverflow; + protected $buffer = array(); + protected $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = (int) $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear() + { + $this->bufferSize = 0; + $this->buffer = array(); + } + + public function reset() + { + $this->flush(); + + parent::reset(); + + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 0000000..37419a0 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + /** + * Version of the extension + */ + const VERSION = '4.0'; + + /** + * Header name + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending + * + * @var bool + */ + protected static $overflowed = false; + + protected static $json = array( + 'version' => self::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array(), + ); + + protected static $sendHeaders = true; + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); + } + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + * @param array $record + */ + protected function write(array $record) + { + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send() + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + } + + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + if (strlen($data) > 240 * 1024) { + self::$overflowed = true; + + $record = array( + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + } + + if (trim($data) !== '') { + $this->sendHeader(self::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return bool + */ + protected function headersAccepted() + { + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 0000000..cc98697 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + private $options; + + public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + $this->options = array_merge(array( + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ), $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ), + )); + + if (false === @file_get_contents($url, null, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 0000000..96b3ca0 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to Cube. + * + * @link http://square.github.com/cube/ + * @author Wan Chen + */ +class CubeHandler extends AbstractProcessingHandler +{ + private $udpConnection; + private $httpConnection; + private $scheme; + private $host; + private $port; + private $acceptedSchemes = array('http', 'udp'); + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct($url, $level = Logger::DEBUG, $bubble = true) + { + $urlInfo = parse_url($url); + + if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp() + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (!$this->udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to a http server + * @throws \LogicException when no curl extension + */ + protected function connectHttp() + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + } + + $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + + if (!$this->httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $date = $record['datetime']; + + $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + if ($this->scheme === 'http') { + $this->writeHttp(json_encode($data)); + } else { + $this->writeUdp(json_encode($data)); + } + } + + private function writeUdp($data) + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp($data) + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']'), + )); + + Curl\Util::execute($this->httpConnection, 5, false); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/core/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 0000000..48d30b3 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Curl; + +class Util +{ + private static $retriableErrorCodes = array( + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ); + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param resource $ch curl handler + * @throws \RuntimeException + */ + public static function execute($ch, $retries = 5, $closeAfterDone = true) + { + while ($retries--) { + if (curl_exec($ch) === false) { + $curlErrno = curl_errno($ch); + + if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { + $curlError = curl_error($ch); + + if ($closeAfterDone) { + curl_close($ch); + } + + throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError)); + } + + continue; + } + + if ($closeAfterDone) { + curl_close($ch); + } + break; + } + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 0000000..35b55cb --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + */ +class DeduplicationHandler extends BufferHandler +{ + /** + * @var string + */ + protected $deduplicationStore; + + /** + * @var int + */ + protected $deduplicationLevel; + + /** + * @var int + */ + protected $time; + + /** + * @var bool + */ + private $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string $deduplicationStore The file/path where the deduplication log should be kept + * @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true) + { + parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record['level'] >= $this->deduplicationLevel) { + + $passthru = $passthru || !$this->isDuplicate($record); + if ($passthru) { + $this->appendRecord($record); + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + private function isDuplicate(array $record) + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($store)) { + return false; + } + + $yesterday = time() - 86400; + $timestampValidity = $record['datetime']->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']); + + for ($i = count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + private function collectLogs() + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + flock($handle, LOCK_EX); + $validLogs = array(); + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if (substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } + + private function appendRecord(array $record) + { + file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 0000000..b91ffec --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter() + { + return new NormalizerFormatter; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 0000000..237b71f --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sdk; +use Aws\DynamoDb\DynamoDbClient; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Logger; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + /** + * @var DynamoDbClient + */ + protected $client; + + /** + * @var string + */ + protected $table; + + /** + * @var int + */ + protected $version; + + /** + * @var Marshaler + */ + protected $marshaler; + + /** + * @param DynamoDbClient $client + * @param string $table + * @param int $level + * @param bool $bubble + */ + public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true) + { + if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) { + $this->version = 3; + $this->marshaler = new Marshaler; + } else { + $this->version = 2; + } + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $filtered = $this->filterEmptyFields($record['formatted']); + if ($this->version === 3) { + $formatted = $this->marshaler->marshalItem($filtered); + } else { + $formatted = $this->client->formatAttributes($filtered); + } + + $this->client->putItem(array( + 'TableName' => $this->table, + 'Item' => $formatted, + )); + } + + /** + * @param array $record + * @return array + */ + protected function filterEmptyFields(array $record) + { + return array_filter($record, function ($value) { + return !empty($value) || false === $value || 0 === $value; + }); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php new file mode 100644 index 0000000..bb0f83e --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticSearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticSearchHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var array Handler config options + */ + protected $options = array(); + + /** + * @param Client $client Elastica Client object + * @param array $options Handler configuration + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + array( + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ), + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->bulkSend(array($record['formatted'])); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter'); + } + + /** + * Getter options + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * @param array $documents + * @throws \RuntimeException + */ + protected function bulkSend(array $documents) + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 0000000..b2986b0 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + const OPERATING_SYSTEM = 0; + const SAPI = 4; + + protected $messageType; + protected $expandNewlines; + + /** + * @param int $messageType Says where the error should go. + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + */ + public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes())) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return array With all available types + */ + public static function getAvailableTypes() + { + return array( + self::OPERATING_SYSTEM, + self::SAPI, + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if ($this->expandNewlines) { + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } else { + error_log((string) $record['formatted'], $this->messageType); + } + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 0000000..938c1a7 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + */ +class FilterHandler extends AbstractHandler +{ + /** + * Handler or factory callable($record, $this) + * + * @var callable|\Monolog\Handler\HandlerInterface + */ + protected $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var int[] + */ + protected $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + * + * @var bool + */ + protected $bubble; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $this). + * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * @return array + */ + public function getAcceptedLevels() + { + return array_flip($this->acceptedLevels); + } + + /** + * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + */ + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) + { + if (is_array($minLevelOrList)) { + $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) { + return $level >= $minLevelOrList && $level <= $maxLevel; + })); + } + $this->acceptedLevels = array_flip($acceptedLevels); + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return isset($this->acceptedLevels[$record['level']]); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + // The same logic as in FingersCrossedHandler + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $filtered = array(); + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + $this->handler->handleBatch($filtered); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 0000000..aaca12c --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @param array $record + * @return bool + */ + public function isHandlerActivated(array $record); +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 0000000..2a2a64d --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private $defaultActionLevel; + private $channelToActionLevel; + + /** + * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + */ + public function __construct($defaultActionLevel, $channelToActionLevel = array()) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); + } + + public function isHandlerActivated(array $record) + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 0000000..6e63085 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private $actionLevel; + + public function __construct($actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(array $record) + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000..275fd51 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; +use Monolog\ResettableInterface; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends AbstractHandler +{ + protected $handler; + protected $activationStrategy; + protected $buffering = true; + protected $bufferSize; + protected $buffer = array(); + protected $stopBuffering; + protected $passthruLevel; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + */ + public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate() + { + if ($this->stopBuffering) { + $this->buffering = false; + } + if (!$this->handler instanceof HandlerInterface) { + $record = end($this->buffer) ?: null; + + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flushBuffer(); + } + + public function reset() + { + $this->flushBuffer(); + + parent::reset(); + + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear() + { + $this->buffer = array(); + $this->reset(); + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + private function flushBuffer() + { + if (null !== $this->passthruLevel) { + $level = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, function ($record) use ($level) { + return $record['level'] >= $level; + }); + if (count($this->buffer) > 0) { + $this->handler->handleBatch($this->buffer); + } + } + + $this->buffer = array(); + $this->buffering = true; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000..c30b184 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + /** + * WildFire JSON header message format + */ + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return array Complete header string ready for the client as key and message as value + */ + protected function createHeader(array $meta, $message) + { + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + + return array($header => $message); + } + + /** + * Creates message header from record + * + * @see createHeader() + * @param array $record + * @return string + */ + protected function createRecordHeader(array $record) + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @param array $record + */ + protected function write(array $record) + { + if (!self::$sendHeaders) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return bool + */ + protected function headersAccepted() + { + if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 0000000..c43c013 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + */ +class FleepHookHandler extends SocketHandler +{ + const FLEEP_HOST = 'fleep.io'; + + const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @throws MissingExtensionException + */ + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . self::FLEEP_HOST . ':443'; + parent::__construct($connectionString, $level, $bubble); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter() + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + * + * @param array $record + */ + public function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . self::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'message' => $record['formatted'], + ); + + return http_build_query($dataArray); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 0000000..dd9a361 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + */ +class FlowdockHandler extends SocketHandler +{ + /** + * @var string + */ + protected $apiToken; + + /** + * @param string $apiToken + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + $this->apiToken = $apiToken; + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + return json_encode($record['formatted']['flowdock']); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 0000000..71e4669 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\IMessagePublisher; +use Gelf\PublisherInterface; +use Gelf\Publisher; +use InvalidArgumentException; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var Publisher the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($publisher, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) { + throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance'); + } + + $this->publisher = $publisher; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new GelfMessageFormatter(); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000..28e5c56 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\ResettableInterface; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends AbstractHandler +{ + protected $handlers; + + /** + * @param array $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $processed[] = call_user_func($processor, $record); + } + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } + + public function reset() + { + parent::reset(); + + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + foreach ($this->handlers as $handler) { + $handler->setFormatter($formatter); + } + + return $this; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/core/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000..8d5a4a0 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record Partial log record containing only a level key + * + * @return bool + */ + public function isHandling(array $record); + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + public function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return self + */ + public function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/core/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 0000000..55e6498 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A custom filtering that can be applied to any handler. + * + * Inherit from this class and override handle() like this: + * + * public function handle(array $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->handle($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface, ResettableInterface +{ + /** + * @var HandlerInterface + */ + protected $handler; + + /** + * HandlerWrapper constructor. + * @param HandlerInterface $handler + */ + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + return $this->handler->handle($record); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + return $this->handler->handleBatch($records); + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + $this->handler->pushProcessor($callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + return $this->handler->popProcessor(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->handler->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->handler->getFormatter(); + } + + public function reset() + { + if ($this->handler instanceof ResettableInterface) { + return $this->handler->reset(); + } + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php new file mode 100644 index 0000000..73233c9 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php @@ -0,0 +1,365 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the hipchat api to a hipchat room + * + * Notes: + * API token - HipChat API token + * Room - HipChat Room Id or name, where messages are sent + * Name - Name used to send the message (from) + * notify - Should the message trigger a notification in the clients + * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) + * + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandler extends SocketHandler +{ + /** + * Use API version 1 + */ + const API_V1 = 'v1'; + + /** + * Use API version v2 + */ + const API_V2 = 'v2'; + + /** + * The maximum allowed length for the name used in the "from" field. + */ + const MAXIMUM_NAME_LENGTH = 15; + + /** + * The maximum allowed length for the message. + */ + const MAXIMUM_MESSAGE_LENGTH = 9500; + + /** + * @var string + */ + private $token; + + /** + * @var string + */ + private $room; + + /** + * @var string + */ + private $name; + + /** + * @var bool + */ + private $notify; + + /** + * @var string + */ + private $format; + + /** + * @var string + */ + private $host; + + /** + * @var string + */ + private $version; + + /** + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field. + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. + * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) + * @param string $host The HipChat server hostname. + * @param string $version The HipChat API version (default HipChatHandler::API_V1) + */ + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) + { + if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { + throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); + } + + $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->name = $name; + $this->notify = $notify; + $this->room = $room; + $this->format = $format; + $this->host = $host; + $this->version = $version; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'notify' => $this->version == self::API_V1 ? + ($this->notify ? 1 : 0) : + ($this->notify ? 'true' : 'false'), + 'message' => $record['formatted'], + 'message_format' => $this->format, + 'color' => $this->getAlertColor($record['level']), + ); + + if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { + if (function_exists('mb_substr')) { + $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } else { + $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } + } + + // if we are using the legacy API then we need to send some additional information + if ($this->version == self::API_V1) { + $dataArray['room_id'] = $this->room; + } + + // append the sender name if it is set + // always append it if we use the v1 api (it is required in v1) + if ($this->version == self::API_V1 || $this->name !== null) { + $dataArray['from'] = (string) $this->name; + } + + return http_build_query($dataArray); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + if ($this->version == self::API_V1) { + $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; + } else { + // needed for rooms with special (spaces, etc) characters in the name + $room = rawurlencode($this->room); + $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; + } + + $header .= "Host: {$this->host}\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Assigns a color to each level of log records. + * + * @param int $level + * @return string + */ + protected function getAlertColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return 'red'; + case $level >= Logger::WARNING: + return 'yellow'; + case $level >= Logger::INFO: + return 'green'; + case $level == Logger::DEBUG: + return 'gray'; + default: + return 'yellow'; + } + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, hipchat sometimes + * drops the request entirely. + */ + protected function finalizeWrite() + { + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if (count($records) == 0) { + return true; + } + + $batchRecords = $this->combineRecords($records); + + $handled = false; + foreach ($batchRecords as $batchRecord) { + if ($this->isHandling($batchRecord)) { + $this->write($batchRecord); + $handled = true; + } + } + + if (!$handled) { + return false; + } + + return false === $this->bubble; + } + + /** + * Combines multiple records into one. Error level of the combined record + * will be the highest level from the given records. Datetime will be taken + * from the first record. + * + * @param $records + * @return array + */ + private function combineRecords($records) + { + $batchRecord = null; + $batchRecords = array(); + $messages = array(); + $formattedMessages = array(); + $level = 0; + $levelName = null; + $datetime = null; + + foreach ($records as $record) { + $record = $this->processRecord($record); + + if ($record['level'] > $level) { + $level = $record['level']; + $levelName = $record['level_name']; + } + + if (null === $datetime) { + $datetime = $record['datetime']; + } + + $messages[] = $record['message']; + $messageStr = implode(PHP_EOL, $messages); + $formattedMessages[] = $this->getFormatter()->format($record); + $formattedMessageStr = implode('', $formattedMessages); + + $batchRecord = array( + 'message' => $messageStr, + 'formatted' => $formattedMessageStr, + 'context' => array(), + 'extra' => array(), + ); + + if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { + // Pop the last message and implode the remaining messages + $lastMessage = array_pop($messages); + $lastFormattedMessage = array_pop($formattedMessages); + $batchRecord['message'] = implode(PHP_EOL, $messages); + $batchRecord['formatted'] = implode('', $formattedMessages); + + $batchRecords[] = $batchRecord; + $messages = array($lastMessage); + $formattedMessages = array($lastFormattedMessage); + + $batchRecord = null; + } + } + + if (null !== $batchRecord) { + $batchRecords[] = $batchRecord; + } + + // Set the max level and datetime for all records + foreach ($batchRecords as &$batchRecord) { + $batchRecord = array_merge( + $batchRecord, + array( + 'level' => $level, + 'level_name' => $levelName, + 'datetime' => $datetime, + ) + ); + } + + return $batchRecords; + } + + /** + * Validates the length of a string. + * + * If the `mb_strlen()` function is available, it will use that, as HipChat + * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. + * + * Note that this might cause false failures in the specific case of using + * a valid name with less than 16 characters, but 16 or more bytes, on a + * system where `mb_strlen()` is unavailable. + * + * @param string $str + * @param int $length + * + * @return bool + */ + private function validateStringLength($str, $length) + { + if (function_exists('mb_strlen')) { + return (mb_strlen($str) <= $length); + } + + return (strlen($str) <= $length); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 0000000..7f22622 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + private $eventName; + private $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true) + { + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + public function write(array $record) + { + $postData = array( + "value1" => $record["channel"], + "value2" => $record["level_name"], + "value3" => $record["message"], + ); + $postString = json_encode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Content-Type: application/json", + )); + + Curl\Util::execute($ch); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php new file mode 100644 index 0000000..a12e3de --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + namespace Monolog\Handler; + + use Monolog\Logger; + +/** + * Inspired on LogEntriesHandler. + * + * @author Robert Kaufmann III + * @author Gabriel Machado + */ +class InsightOpsHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by InsightOps + * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. + * @param bool $useSSL Whether or not SSL encryption should be used + * @param int $level The minimum logging level to trigger this handler + * @param bool $bubble Whether or not messages that are handled should bubble up the stack. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct($token, $region = 'us', $useSSL = true, $level = Logger::DEBUG, $bubble = true) + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL + ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' + : $region . '.data.logs.insight.rapid7.com:80'; + + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 0000000..ea89fb3 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param int $level The minimum logging level to trigger this handler + * @param bool $bubble Whether or not messages that are handled should bubble up the stack. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true, $host = 'data.logentries.com') + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80'; + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 0000000..bcd62e1 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LogglyFormatter; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + const HOST = 'logs-01.loggly.com'; + const ENDPOINT_SINGLE = 'inputs'; + const ENDPOINT_BATCH = 'bulk'; + + protected $token; + + protected $tag = array(); + + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + public function setTag($tag) + { + $tag = !empty($tag) ? $tag : array(); + $this->tag = is_array($tag) ? $tag : array($tag); + } + + public function addTag($tag) + { + if (!empty($tag)) { + $tag = is_array($tag) ? $tag : array($tag); + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + } + + protected function write(array $record) + { + $this->send($record["formatted"], self::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records) + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record['level'] >= $level); + }); + + if ($records) { + $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH); + } + } + + protected function send($data, $endpoint) + { + $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); + + $headers = array('Content-Type: application/json'); + + if (!empty($this->tag)) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + Curl\Util::execute($ch); + } + + protected function getDefaultFormatter() + { + return new LogglyFormatter(); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000..9e23283 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + */ + abstract protected function send($content, array $records); + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->send((string) $record['formatted'], array($record)); + } + + protected function getHighestRecord(array $records) + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 0000000..3f0956a --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + protected $message; + protected $apiKey; + + /** + * @param string $apiKey A valid Mandrill API key + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof \Swift_Message && is_callable($message)) { + $message = call_user_func($message); + } + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $message = clone $this->message; + $message->setBody($content); + $message->setDate(time()); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ))); + + Curl\Util::execute($ch); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/core/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 0000000..4724a7e --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for an handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 0000000..56fe755 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Logs to a MongoDB database. + * + * usage example: + * + * $log = new Logger('application'); + * $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod"); + * $log->pushHandler($mongodb); + * + * @author Thomas Tourlourat + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + protected $mongoCollection; + + public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + { + if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { + throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + } + + $this->mongoCollection = $mongo->selectCollection($database, $collection); + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if ($this->mongoCollection instanceof \MongoDB\Collection) { + $this->mongoCollection->insertOne($record["formatted"]); + } else { + $this->mongoCollection->save($record["formatted"]); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000..d7807fd --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var array + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * Optional headers for the message + * @var array + */ + protected $headers = array(); + + /** + * Optional parameters for the message + * @var array + */ + protected $parameters = array(); + + /** + * The wordwrap length for the message + * @var int + */ + protected $maxColumnWidth; + + /** + * The Content-type for the message + * @var string + */ + protected $contentType = 'text/plain'; + + /** + * The encoding for the message + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * @param string|array $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = is_array($to) ? $to : array($to); + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|array $headers Custom added headers + * @return self + */ + public function addHeader($headers) + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|array $parameters Custom added parameters + * @return self + */ + public function addParameter($parameters) + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $content = wordwrap($content, $this->maxColumnWidth); + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; + if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subject = $this->subject; + if ($records) { + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + } + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + mail($to, $subject, $content, $headers, $parameters); + } + } + + /** + * @return string $contentType + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * @return string $encoding + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML + * messages. + * @return self + */ + public function setContentType($contentType) + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + /** + * @param string $encoding + * @return self + */ + public function setEncoding($encoding) + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 0000000..f911997 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * This handler requires a NormalizerFormatter to function and expects an array in $record['formatted'] + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * Name of the New Relic application that will receive logs from this handler. + * + * @var string + */ + protected $appName; + + /** + * Name of the current transaction + * + * @var string + */ + protected $transactionName; + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + * + * @var bool + */ + protected $explodeArrays; + + /** + * {@inheritDoc} + * + * @param string $appName + * @param bool $explodeArrays + * @param string $transactionName + */ + public function __construct( + $level = Logger::ERROR, + $bubble = true, + $appName = null, + $explodeArrays = false, + $transactionName = null + ) { + parent::__construct($level, $bubble); + + $this->appName = $appName; + $this->explodeArrays = $explodeArrays; + $this->transactionName = $transactionName; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if ($appName = $this->getAppName($record['context'])) { + $this->setNewRelicAppName($appName); + } + + if ($transactionName = $this->getTransactionName($record['context'])) { + $this->setNewRelicTransactionName($transactionName); + unset($record['formatted']['context']['transaction_name']); + } + + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['formatted']['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) { + foreach ($record['formatted']['context'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) { + foreach ($record['formatted']['extra'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled() + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param array $context + * @return null|string + */ + protected function getAppName(array $context) + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param array $context + * + * @return null|string + */ + protected function getTransactionName(array $context) + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + * + * @param string $appName + */ + protected function setNewRelicAppName($appName) + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + * + * @param string $transactionName + */ + protected function setNewRelicTransactionName($transactionName) + { + newrelic_name_transaction($transactionName); + } + + /** + * @param string $key + * @param mixed $value + */ + protected function setNewRelicParameter($key, $value) + { + if (null === $value || is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, @json_encode($value)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000..4b84588 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends AbstractHandler +{ + /** + * @param int $level The minimum logging level at which this handler will be triggered + */ + public function __construct($level = Logger::DEBUG) + { + parent::__construct($level, false); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + return true; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 0000000..1f2076a --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use PhpConsole\Connector; +use PhpConsole\Handler; +use PhpConsole\Helper; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + private $options = array( + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... + 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ); + + /** @var Connector */ + private $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @param int $level + * @param bool $bubble + * @throws Exception + */ + public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + private function initOptions(array $options) + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if ($wrongOptions) { + throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(Connector $connector = null) + { + if (!$connector) { + if ($this->options['dataStorage']) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = Handler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if ($this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if ($this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if ($this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if ($this->options['ipMasks']) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if ($this->options['headersLimit']) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector() + { + return $this->connector; + } + + public function getOptions() + { + return $this->options; + } + + public function handle(array $record) + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + protected function write(array $record) + { + if ($record['level'] < Logger::NOTICE) { + $this->handleDebugRecord($record); + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + private function handleDebugRecord(array $record) + { + $tags = $this->getRecordTags($record); + $message = $record['message']; + if ($record['context']) { + $message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context']))); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + private function handleExceptionRecord(array $record) + { + $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); + } + + private function handleErrorRecord(array $record) + { + $context = $record['context']; + + $this->connector->getErrorsDispatcher()->dispatchError( + isset($context['code']) ? $context['code'] : null, + isset($context['message']) ? $context['message'] : $record['message'], + isset($context['file']) ? $context['file'] : null, + isset($context['line']) ? $context['line'] : null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + private function getRecordTags(array &$record) + { + $tags = null; + if (!empty($record['context'])) { + $context = & $record['context']; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (!empty($context[$key])) { + $tags = $context[$key]; + if ($key === 0) { + array_shift($context); + } else { + unset($context[$key]); + } + break; + } + } + } + + return $tags ?: strtolower($record['level_name']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%message%'); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 0000000..a99e6ab --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler +{ + /** + * PSR-3 compliant logger + * + * @var LoggerInterface + */ + protected $logger; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + + return false === $this->bubble; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 0000000..f27bb3d --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private $token; + private $users; + private $title; + private $user; + private $retry; + private $expire; + + private $highPriorityLevel; + private $emergencyLevel; + private $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private $parameterNames = array( + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ); + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var array + */ + private $sounds = array( + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ); + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string $title Title sent to the Pushover API + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param int $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param int $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + */ + public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) + { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent($record) + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; + $message = substr($message, 0, $maxMessageLength); + + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = array( + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ); + + if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record['context'], $this->parameterNames); + $extra = array_intersect_key($record['extra'], $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader($content) + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(array $record) + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + public function setHighPriorityLevel($value) + { + $this->highPriorityLevel = $value; + } + + public function setEmergencyLevel($value) + { + $this->emergencyLevel = $value; + } + + /** + * Use the formatted message? + * @param bool $value + */ + public function useFormattedMessage($value) + { + $this->useFormattedMessage = (bool) $value; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php new file mode 100644 index 0000000..10d7f43 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Raven_Client; + +/** + * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server + * using sentry-php (https://github.com/getsentry/sentry-php) + * + * @author Marc Abramowitz + */ +class RavenHandler extends AbstractProcessingHandler +{ + /** + * Translates Monolog log levels to Raven log levels. + */ + protected $logLevels = array( + Logger::DEBUG => Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + /** + * @var string should represent the current version of the calling + * software. Can be any string (git commit, version number) + */ + protected $release; + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @var LineFormatter The formatter to use for the logs generated via handleBatch() + */ + protected $batchFormatter; + + /** + * @param Raven_Client $ravenClient + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $level = $this->level; + + // filter records based on their level + $records = array_filter($records, function ($record) use ($level) { + return $record['level'] >= $level; + }); + + if (!$records) { + return; + } + + // the record with the highest severity is the "main" one + $record = array_reduce($records, function ($highest, $record) { + if ($record['level'] > $highest['level']) { + return $record; + } + + return $highest; + }); + + // the other ones are added as a context item + $logs = array(); + foreach ($records as $r) { + $logs[] = $this->processRecord($r); + } + + if ($logs) { + $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); + } + + $this->handle($record); + } + + /** + * Sets the formatter for the logs generated by handleBatch(). + * + * @param FormatterInterface $formatter + */ + public function setBatchFormatter(FormatterInterface $formatter) + { + $this->batchFormatter = $formatter; + } + + /** + * Gets the formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + public function getBatchFormatter() + { + if (!$this->batchFormatter) { + $this->batchFormatter = $this->getDefaultBatchFormatter(); + } + + return $this->batchFormatter; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $previousUserContext = false; + $options = array(); + $options['level'] = $this->logLevels[$record['level']]; + $options['tags'] = array(); + if (!empty($record['extra']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); + unset($record['extra']['tags']); + } + if (!empty($record['context']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['context']['tags']); + unset($record['context']['tags']); + } + if (!empty($record['context']['fingerprint'])) { + $options['fingerprint'] = $record['context']['fingerprint']; + unset($record['context']['fingerprint']); + } + if (!empty($record['context']['logger'])) { + $options['logger'] = $record['context']['logger']; + unset($record['context']['logger']); + } else { + $options['logger'] = $record['channel']; + } + foreach ($this->getExtraParameters() as $key) { + foreach (array('extra', 'context') as $source) { + if (!empty($record[$source][$key])) { + $options[$key] = $record[$source][$key]; + unset($record[$source][$key]); + } + } + } + if (!empty($record['context'])) { + $options['extra']['context'] = $record['context']; + if (!empty($record['context']['user'])) { + $previousUserContext = $this->ravenClient->context->user; + $this->ravenClient->user_context($record['context']['user']); + unset($options['extra']['context']['user']); + } + } + if (!empty($record['extra'])) { + $options['extra']['extra'] = $record['extra']; + } + + if (!empty($this->release) && !isset($options['release'])) { + $options['release'] = $this->release; + } + + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + $options['message'] = $record['formatted']; + $this->ravenClient->captureException($record['context']['exception'], $options); + } else { + $this->ravenClient->captureMessage($record['formatted'], array(), $options); + } + + if ($previousUserContext !== false) { + $this->ravenClient->user_context($previousUserContext); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%channel%] %message%'); + } + + /** + * Gets the default formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + protected function getDefaultBatchFormatter() + { + return new LineFormatter(); + } + + /** + * Gets extra parameters supported by Raven that can be found in "extra" and "context" + * + * @return array + */ + protected function getExtraParameters() + { + return array('contexts', 'checksum', 'release', 'event_id'); + } + + /** + * @param string $value + * @return self + */ + public function setRelease($value) + { + $this->release = $value; + + return $this; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 0000000..590f996 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + private $redisClient; + private $redisKey; + protected $capSize; + + /** + * @param \Predis\Client|\Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $capSize Number of entries to limit list size to + */ + public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if ($this->capSize) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + * + * @param array $record associative record array + * @return void + */ + protected function writeCapped(array $record) + { + if ($this->redisClient instanceof \Redis) { + $this->redisClient->multi() + ->rpush($this->redisKey, $record["formatted"]) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record["formatted"]); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 0000000..65073ff --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RollbarNotifier; +use Exception; +use Monolog\Logger; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarNotifier's report_message/report_exception methods. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + /** + * Rollbar notifier + * + * @var RollbarNotifier + */ + protected $rollbarNotifier; + + protected $levelMap = array( + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warning', + Logger::ERROR => 'error', + Logger::CRITICAL => 'critical', + Logger::ALERT => 'critical', + Logger::EMERGENCY => 'critical', + ); + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + * + * @var bool + */ + private $hasRecords = false; + + protected $initialized = false; + + /** + * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) + { + $this->rollbarNotifier = $rollbarNotifier; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + $context = $record['context']; + $payload = array(); + if (isset($context['payload'])) { + $payload = $context['payload']; + unset($context['payload']); + } + $context = array_merge($context, $record['extra'], array( + 'level' => $this->levelMap[$record['level']], + 'monolog_level' => $record['level_name'], + 'channel' => $record['channel'], + 'datetime' => $record['datetime']->format('U'), + )); + + if (isset($context['exception']) && $context['exception'] instanceof Exception) { + $payload['level'] = $context['level']; + $exception = $context['exception']; + unset($context['exception']); + + $this->rollbarNotifier->report_exception($exception, $context, $payload); + } else { + $this->rollbarNotifier->report_message( + $record['message'], + $context['level'], + $context, + $payload + ); + } + + $this->hasRecords = true; + } + + public function flush() + { + if ($this->hasRecords) { + $this->rollbarNotifier->flush(); + $this->hasRecords = false; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->flush(); + + parent::reset(); + } + + +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000..ae2309f --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + const FILE_PER_DAY = 'Y-m-d'; + const FILE_PER_MONTH = 'Y-m'; + const FILE_PER_YEAR = 'Y'; + + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + protected $filenameFormat; + protected $dateFormat; + + /** + * @param string $filename + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + $this->filename = $filename; + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = 'Y-m-d'; + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * {@inheritdoc} + */ + public function reset() + { + parent::reset(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat($filenameFormat, $dateFormat) + { + if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + trigger_error( + 'Invalid date format - format must be one of '. + 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. + 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. + 'date formats using slashes, underscores and/or dots instead of dashes.', + E_USER_DEPRECATED + ); + } + if (substr_count($filenameFormat, '{date}') === 0) { + trigger_error( + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', + E_USER_DEPRECATED + ); + } + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate() + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + unlink($file); + restore_error_handler(); + } + } + + $this->mustRotate = false; + } + + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], date($this->dateFormat)), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern() + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + if (!empty($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 0000000..9509ae3 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + */ +class SamplingHandler extends AbstractHandler +{ + /** + * @var callable|HandlerInterface $handler + */ + protected $handler; + + /** + * @var int $factor + */ + protected $factor; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int $factor Sample factor + */ + public function __construct($handler, $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + public function handle(array $record) + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + // The same logic as in FingersCrossedHandler + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->handler->handle($record); + } + + return false === $this->bubble; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/core/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 0000000..e55e0e2 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,294 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + */ +class SlackRecord +{ + const COLOR_DANGER = 'danger'; + + const COLOR_WARNING = 'warning'; + + const COLOR_GOOD = 'good'; + + const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + * @var string|null + */ + private $channel; + + /** + * Name of a bot + * @var string|null + */ + private $username; + + /** + * User icon e.g. 'ghost', 'http://example.com/user.png' + * @var string + */ + private $userIcon; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + * @var bool + */ + private $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + * @var bool + */ + private $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + * @var bool + */ + private $includeContextAndExtra; + + /** + * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @var array + */ + private $excludeFields; + + /** + * @var FormatterInterface + */ + private $formatter; + + /** + * @var NormalizerFormatter + */ + private $normalizerFormatter; + + public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null) + { + $this->channel = $channel; + $this->username = $username; + $this->userIcon = trim($userIcon, ':'); + $this->useAttachment = $useAttachment; + $this->useShortAttachment = $useShortAttachment; + $this->includeContextAndExtra = $includeContextAndExtra; + $this->excludeFields = $excludeFields; + $this->formatter = $formatter; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + } + + public function getSlackData(array $record) + { + $dataArray = array(); + $record = $this->excludeFields($record); + + if ($this->username) { + $dataArray['username'] = $this->username; + } + + if ($this->channel) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter && !$this->useAttachment) { + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + + if ($this->useAttachment) { + $attachment = array( + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp() + ); + + if ($this->useShortAttachment) { + $attachment['title'] = $record['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); + } + + + if ($this->includeContextAndExtra) { + foreach (array('extra', 'context') as $key) { + if (empty($record[$key])) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + $key, + $record[$key] + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($record[$key]) + ); + } + } + } + + $dataArray['attachments'] = array($attachment); + } else { + $dataArray['text'] = $message; + } + + if ($this->userIcon) { + if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) { + $dataArray['icon_url'] = $this->userIcon; + } else { + $dataArray['icon_emoji'] = ":{$this->userIcon}:"; + } + } + + return $dataArray; + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + */ + public function getAttachmentColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return self::COLOR_DANGER; + case $level >= Logger::WARNING: + return self::COLOR_WARNING; + case $level >= Logger::INFO: + return self::COLOR_GOOD; + default: + return self::COLOR_DEFAULT; + } + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * + * @return string + */ + public function stringify($fields) + { + $normalized = $this->normalizerFormatter->format($fields); + $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + + $hasSecondDimension = count(array_filter($normalized, 'is_array')); + $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); + + return $hasSecondDimension || $hasNonNumericKeys + ? json_encode($normalized, $prettyPrintFlag) + : json_encode($normalized); + } + + /** + * Sets the formatter + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * Generates attachment field + * + * @param string $title + * @param string|array $value + * + * @return array + */ + private function generateAttachmentField($title, $value) + { + $value = is_array($value) + ? sprintf('```%s```', $this->stringify($value)) + : $value; + + return array( + 'title' => ucfirst($title), + 'value' => $value, + 'short' => false + ); + } + + /** + * Generates a collection of attachment fields from array + * + * @param array $data + * + * @return array + */ + private function generateAttachmentFields(array $data) + { + $fields = array(); + foreach ($this->normalizerFormatter->format($data) as $key => $value) { + $fields[] = $this->generateAttachmentField($key, $value); + } + + return $fields; + } + + /** + * Get a copy of record with fields excluded according to $this->excludeFields + * + * @param array $record + * + * @return array + */ + private function excludeFields(array $record) + { + foreach ($this->excludeFields as $field) { + $keys = explode('.', $field); + $node = &$record; + $lastKey = end($keys); + foreach ($keys as $key) { + if (!isset($node[$key])) { + break; + } + if ($lastKey === $key) { + unset($node[$key]); + break; + } + $node = &$node[$key]; + } + } + + return $record; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 0000000..45d634f --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,220 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + * @var string + */ + private $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array()) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct('ssl://slack.com:443', $level, $bubble); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields, + $this->formatter + ); + + $this->token = $token; + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + public function getToken() + { + return $this->token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * Prepares content data + * + * @param array $record + * @return array + */ + protected function prepareContentData($record) + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (!empty($dataArray['attachments'])) { + $dataArray['attachments'] = json_encode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, slack sometimes + * drops the request entirely. + */ + protected function finalizeWrite() + { + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function getAttachmentColor($level) + { + trigger_error( + 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->getAttachmentColor($level); + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function stringify($fields) + { + trigger_error( + 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->stringify($fields); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 0000000..1ef85fa --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * @var string + */ + private $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + */ + public function __construct($webhookUrl, $channel = null, $username = null, $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true, array $excludeFields = array()) + { + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields, + $this->formatter + ); + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + public function getWebhookUrl() + { + return $this->webhookUrl; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $postData = $this->slackRecord->getSlackData($record); + $postString = json_encode($postData); + + $ch = curl_init(); + $options = array( + CURLOPT_URL => $this->webhookUrl, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Content-type: application/json'), + CURLOPT_POSTFIELDS => $postString + ); + if (defined('CURLOPT_SAFE_UPLOAD')) { + $options[CURLOPT_SAFE_UPLOAD] = true; + } + + curl_setopt_array($ch, $options); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php new file mode 100644 index 0000000..baead52 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through Slack's Slackbot + * + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + */ +class SlackbotHandler extends AbstractProcessingHandler +{ + /** + * The slug of the Slack team + * @var string + */ + private $slackTeam; + + /** + * Slackbot token + * @var string + */ + private $token; + + /** + * Slack channel name + * @var string + */ + private $channel; + + /** + * @param string $slackTeam Slack team slug + * @param string $token Slackbot token + * @param string $channel Slack channel (encoded ID or name) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->slackTeam = $slackTeam; + $this->token = $token; + $this->channel = $channel; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $slackbotUrl = sprintf( + 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', + $this->slackTeam, + $this->token, + $this->channel + ); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $slackbotUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); + + Curl\Util::execute($ch); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 0000000..db50d97 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,385 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private $connectionString; + private $connectionTimeout; + private $resource; + private $timeout = 0; + private $writingTimeout = 10; + private $lastSentBytes = null; + private $chunkSize = null; + private $persistent = false; + private $errno; + private $errstr; + private $lastWritingAt; + + /** + * @param string $connectionString Socket connection string + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + } + + /** + * Connect (if necessary) and write to the socket + * + * @param array $record + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(array $record) + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close() + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket() + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to nbe persistent. It only has effect before the connection is initiated. + * + * @param bool $persistent + */ + public function setPersistent($persistent) + { + $this->persistent = (bool) $persistent; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->connectionTimeout = (float) $seconds; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->timeout = (float) $seconds; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + */ + public function setWritingTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->writingTimeout = (float) $seconds; + } + + /** + * Set chunk size. Only has effect during connection in the writing cycle. + * + * @param float $bytes + */ + public function setChunkSize($bytes) + { + $this->chunkSize = $bytes; + } + + /** + * Get current connection string + * + * @return string + */ + public function getConnectionString() + { + return $this->connectionString; + } + + /** + * Get persistent setting + * + * @return bool + */ + public function isPersistent() + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + * + * @return float + */ + public function getConnectionTimeout() + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + * + * @return float + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Get current local writing timeout + * + * @return float + */ + public function getWritingTimeout() + { + return $this->writingTimeout; + } + + /** + * Get current chunk size + * + * @return float + */ + public function getChunkSize() + { + return $this->chunkSize; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + * + * @return bool + */ + public function isConnected() + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + return stream_set_timeout($this->resource, $seconds, $microseconds); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-chunk-size.php + */ + protected function streamSetChunkSize() + { + return stream_set_chunk_size($this->resource, $this->chunkSize); + } + + /** + * Wrapper to allow mocking + */ + protected function fwrite($data) + { + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + */ + protected function streamGetMetadata() + { + return stream_get_meta_data($this->resource); + } + + private function validateTimeout($value) + { + $ok = filter_var($value, FILTER_VALIDATE_FLOAT); + if ($ok === false || $value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected() + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream($record) + { + return (string) $record['formatted']; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect() + { + $this->createSocketResource(); + $this->setSocketTimeout(); + $this->setStreamChunkSize(); + } + + private function createSocketResource() + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (!$resource) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout() + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function setStreamChunkSize() + { + if ($this->chunkSize && !$this->streamSetChunkSize()) { + throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); + } + } + + private function writeToSocket($data) + { + $length = strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if ($socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut($sent) + { + $writingTimeout = (int) floor($this->writingTimeout); + if (0 === $writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = time(); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((time() - $this->lastWritingAt) >= $writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000..a35b7e4 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + private $errorMessage; + protected $filePermission; + protected $useLocking; + private $dirCreated; + + /** + * @param resource|string $stream + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + * + * @throws \Exception If a missing directory is not buildable + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } elseif (is_string($stream)) { + $this->url = $stream; + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + * + * @return string|null + */ + public function getUrl() + { + return $this->url; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!is_resource($this->stream)) { + if (null === $this->url || '' === $this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->createDir(); + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $this->stream = fopen($this->url, 'a'); + if ($this->filePermission !== null) { + @chmod($this->url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url)); + } + } + + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($this->stream, LOCK_EX); + } + + $this->streamWrite($this->stream, $record); + + if ($this->useLocking) { + flock($this->stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + * @param array $record + */ + protected function streamWrite($stream, array $record) + { + fwrite($stream, (string) $record['formatted']); + } + + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + } + + /** + * @param string $stream + * + * @return null|string + */ + private function getDirFromStream($stream) + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return; + } + + private function createDir() + { + // Do not try to create dir if it has already been tried. + if ($this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($this->url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status && !is_dir($dir)) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000..ac7b16f --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Swift; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + */ +class SwiftMailerHandler extends MailHandler +{ + protected $mailer; + private $messageTemplate; + + /** + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->messageTemplate = $message; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string $format The format of the subject + * @return FormatterInterface + */ + protected function getSubjectFormatter($format) + { + return new LineFormatter($format); + } + + /** + * Creates instance of Swift_Message to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return \Swift_Message + */ + protected function buildMessage($content, array $records) + { + $message = null; + if ($this->messageTemplate instanceof \Swift_Message) { + $message = clone $this->messageTemplate; + $message->generateId(); + } elseif (is_callable($this->messageTemplate)) { + $message = call_user_func($this->messageTemplate, $content, $records); + } + + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); + } + + $message->setBody($content); + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + $message->setDate(time()); + } + + return $message; + } + + /** + * BC getter, to be removed in 2.0 + */ + public function __get($name) + { + if ($name === 'message') { + trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); + + return $this->buildMessage(null, array()); + } + + throw new \InvalidArgumentException('Invalid property '.$name); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000..f770c80 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + protected $ident; + protected $logopts; + + /** + * @param string $ident + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * {@inheritdoc} + */ + public function close() + { + closelog(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 0000000..3bff085 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\SyslogUdp; + +class UdpSocket +{ + const DATAGRAM_MAX_LENGTH = 65023; + + protected $ip; + protected $port; + protected $socket; + + public function __construct($ip, $port = 514) + { + $this->ip = $ip; + $this->port = $port; + $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + } + + public function write($line, $header = "") + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close() + { + if (is_resource($this->socket)) { + socket_close($this->socket); + $this->socket = null; + } + } + + protected function send($chunk) + { + if (!is_resource($this->socket)) { + throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + } + socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage($line, $header) + { + $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header); + + return $header . substr($line, 0, $chunkSize); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 0000000..e14b378 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + protected $socket; + protected $ident; + + /** + * @param string $host + * @param int $port + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + */ + public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php') + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + + $this->socket = new UdpSocket($host, $port ?: 514); + } + + protected function write(array $record) + { + $lines = $this->splitMessageIntoLines($record['formatted']); + + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close() + { + $this->socket->close(); + } + + private function splitMessageIntoLines($message) + { + if (is_array($message)) { + $message = implode("\n", $message); + } + + return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * Make common syslog header (see rfc5424) + */ + protected function makeCommonSyslogHeader($severity) + { + $priority = $severity + $this->facility; + + if (!$pid = getmypid()) { + $pid = '-'; + } + + if (!$hostname = gethostname()) { + $hostname = '-'; + } + + return "<$priority>1 " . + $this->getDateTime() . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; + } + + protected function getDateTime() + { + return date(\DateTime::RFC3339); + } + + /** + * Inject your own socket, mainly used for testing + */ + public function setSocket($socket) + { + $this->socket = $socket; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000..b6b1343 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @method bool hasEmergency($record) + * @method bool hasAlert($record) + * @method bool hasCritical($record) + * @method bool hasError($record) + * @method bool hasWarning($record) + * @method bool hasNotice($record) + * @method bool hasInfo($record) + * @method bool hasDebug($record) + * + * @method bool hasEmergencyRecords() + * @method bool hasAlertRecords() + * @method bool hasCriticalRecords() + * @method bool hasErrorRecords() + * @method bool hasWarningRecords() + * @method bool hasNoticeRecords() + * @method bool hasInfoRecords() + * @method bool hasDebugRecords() + * + * @method bool hasEmergencyThatContains($message) + * @method bool hasAlertThatContains($message) + * @method bool hasCriticalThatContains($message) + * @method bool hasErrorThatContains($message) + * @method bool hasWarningThatContains($message) + * @method bool hasNoticeThatContains($message) + * @method bool hasInfoThatContains($message) + * @method bool hasDebugThatContains($message) + * + * @method bool hasEmergencyThatMatches($message) + * @method bool hasAlertThatMatches($message) + * @method bool hasCriticalThatMatches($message) + * @method bool hasErrorThatMatches($message) + * @method bool hasWarningThatMatches($message) + * @method bool hasNoticeThatMatches($message) + * @method bool hasInfoThatMatches($message) + * @method bool hasDebugThatMatches($message) + * + * @method bool hasEmergencyThatPasses($message) + * @method bool hasAlertThatPasses($message) + * @method bool hasCriticalThatPasses($message) + * @method bool hasErrorThatPasses($message) + * @method bool hasWarningThatPasses($message) + * @method bool hasNoticeThatPasses($message) + * @method bool hasInfoThatPasses($message) + * @method bool hasDebugThatPasses($message) + */ +class TestHandler extends AbstractProcessingHandler +{ + protected $records = array(); + protected $recordsByLevel = array(); + + public function getRecords() + { + return $this->records; + } + + public function clear() + { + $this->records = array(); + $this->recordsByLevel = array(); + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + /** + * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records + * @param int $level Logger::LEVEL constant value + */ + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = array('message' => $record); + } + + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses($predicate, $level) + { + if (!is_callable($predicate)) { + throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); + } + + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = constant('Monolog\Logger::' . strtoupper($matches[2])); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + + return call_user_func_array(array($this, $genericMethod), $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 0000000..6bc4671 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $processed[] = call_user_func($processor, $record); + } + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/core/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 0000000..f22cf21 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = array( + Logger::DEBUG => 1, + Logger::INFO => 2, + Logger::NOTICE => 3, + Logger::WARNING => 4, + Logger::ERROR => 5, + Logger::CRITICAL => 6, + Logger::ALERT => 7, + Logger::EMERGENCY => 0, + ); + + /** + * Construct + * + * @param int $level + * @param bool $bubble + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException('You must have Zend Server installed in order to use this handler'); + } + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->writeZendMonitorCustomEvent( + $this->levelMap[$record['level']], + $record['message'], + $record['formatted'] + ); + } + + /** + * Write a record to Zend Monitor + * + * @param int $level + * @param string $message + * @param array $formatted + */ + protected function writeZendMonitorCustomEvent($level, $message, $formatted) + { + zend_monitor_custom_event($level, $message, $formatted); + } + + /** + * {@inheritdoc} + */ + public function getDefaultFormatter() + { + return new NormalizerFormatter(); + } + + /** + * Get the level map + * + * @return array + */ + public function getLevelMap() + { + return $this->levelMap; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Logger.php b/core/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 0000000..05dfc81 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,791 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; +use Exception; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger implements LoggerInterface, ResettableInterface +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Uncommon events + */ + const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + /** + * Urgent alert. + */ + const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + const API = 1; + + /** + * Logging levels from syslog protocol defined in RFC 5424 + * + * @var array $levels Logging levels + */ + protected static $levels = array( + self::DEBUG => 'DEBUG', + self::INFO => 'INFO', + self::NOTICE => 'NOTICE', + self::WARNING => 'WARNING', + self::ERROR => 'ERROR', + self::CRITICAL => 'CRITICAL', + self::ALERT => 'ALERT', + self::EMERGENCY => 'EMERGENCY', + ); + + /** + * @var \DateTimeZone + */ + protected static $timezone; + + /** + * @var string + */ + protected $name; + + /** + * The handler stack + * + * @var HandlerInterface[] + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var callable[] + */ + protected $processors; + + /** + * @var bool + */ + protected $microsecondTimestamps = true; + + /** + * @var callable + */ + protected $exceptionHandler; + + /** + * @param string $name The logging channel + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->setHandlers($handlers); + $this->processors = $processors; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + * + * @return static + */ + public function withName($name) + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + * + * @param HandlerInterface $handler + * @return $this + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param HandlerInterface[] $handlers + * @return $this + */ + public function setHandlers(array $handlers) + { + $this->handlers = array(); + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return HandlerInterface[] + */ + public function getHandlers() + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + * + * @param callable $callback + * @return $this + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * Generating microsecond resolution timestamps by calling + * microtime(true), formatting the result via sprintf() and then parsing + * the resulting string via \DateTime::createFromFormat() can incur + * a measurable runtime overhead vs simple usage of DateTime to capture + * a second resolution timestamp in systems which generate a large number + * of log events. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps($micro) + { + $this->microsecondTimestamps = (bool) $micro; + } + + /** + * Adds a log record. + * + * @param int $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + + $levelName = static::getLevelName($level); + + // check if any handler will handle this message so we can return early and save cycles + $handlerKey = null; + reset($this->handlers); + while ($handler = current($this->handlers)) { + if ($handler->isHandling(array('level' => $level))) { + $handlerKey = key($this->handlers); + break; + } + + next($this->handlers); + } + + if (null === $handlerKey) { + return false; + } + + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + // php7.1+ always has microseconds enabled, so we do not need this hack + if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { + $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); + } else { + $ts = new \DateTime(null, static::$timezone); + } + $ts->setTimezone(static::$timezone); + + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $ts, + 'extra' => array(), + ); + + try { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + + while ($handler = current($this->handlers)) { + if (true === $handler->handle($record)) { + break; + } + + next($this->handlers); + } + } catch (Exception $e) { + $this->handleException($e, $record); + } + + return true; + } + + /** + * Ends a log cycle and frees all resources used by handlers. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * Handlers that have been closed should be able to accept log records again and re-open + * themselves on demand, but this may not always be possible depending on implementation. + * + * This is useful at the end of a request and will be called automatically on every handler + * when they get destructed. + */ + public function close() + { + foreach ($this->handlers as $handler) { + if (method_exists($handler, 'close')) { + $handler->close(); + } + } + } + + /** + * Ends a log cycle and resets all handlers and processors to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + */ + public function reset() + { + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @param int $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param string|int Level number (monolog) or name (PSR-3) + * @return int + */ + public static function toMonologLevel($level) + { + if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { + return constant(__CLASS__.'::'.strtoupper($level)); + } + + return $level; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @param int $level + * @return bool + */ + public function isHandling($level) + { + $record = array( + 'level' => $level, + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Set a custom exception handler + * + * @param callable $callback + * @return $this + */ + public function setExceptionHandler($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + $this->exceptionHandler = $callback; + + return $this; + } + + /** + * @return callable + */ + public function getExceptionHandler() + { + return $this->exceptionHandler; + } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + */ + protected function handleException(Exception $e, array $record) + { + if (!$this->exceptionHandler) { + throw $e; + } + + call_user_func($this->exceptionHandler, $e, $record); + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function log($level, $message, array $context = array()) + { + $level = static::toMonologLevel($level); + + return $this->addRecord($level, $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Set the timezone to be used for the timestamp of log records. + * + * This is stored globally for all Logger instances + * + * @param \DateTimeZone $tz Timezone object + */ + public static function setTimezone(\DateTimeZone $tz) + { + self::$timezone = $tz; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 0000000..9fc3f50 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + */ +class GitProcessor implements ProcessorInterface +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['git'] = self::getGitInfo(); + + return $record; + } + + private static function getGitInfo() + { + if (self::$cache) { + return self::$cache; + } + + $branches = `git branch -v --no-abbrev`; + if (preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = array( + 'branch' => $matches[1], + 'commit' => $matches[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000..6ae192a --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor implements ProcessorInterface +{ + private $level; + + private $skipClassesPartials; + + private $skipStackFramesCount; + + private $skipFunctions = array( + 'call_user_func', + 'call_user_func_array', + ); + + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + /* + * http://php.net/manual/en/function.debug-backtrace.php + * As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. + * Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. + */ + $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { + $i++; + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, + 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } + + private function isTraceClassOrSkippedFunction(array $trace, $index) + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000..0543e92 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_peak_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_peak_usage'] = $formatted; + + return $record; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000..2a379a3 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor implements ProcessorInterface +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct($realUsage = true, $useFormatting = true) + { + $this->realUsage = (bool) $realUsage; + $this->useFormatting = (bool) $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @param int $bytes + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + */ + protected function formatBytes($bytes) + { + $bytes = (int) $bytes; + + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000..2783d65 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_usage'] = $formatted; + + return $record; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 0000000..2f5b326 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + */ +class MercurialProcessor implements ProcessorInterface +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['hg'] = self::getMercurialInfo(); + + return $record; + } + + private static function getMercurialInfo() + { + if (self::$cache) { + return self::$cache; + } + + $result = explode(' ', trim(`hg id -nb`)); + if (count($result) >= 3) { + return self::$cache = array( + 'branch' => $result[1], + 'revision' => $result[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 0000000..66b80fb --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor implements ProcessorInterface +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $record['extra']['process_id'] = getmypid(); + + return $record; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 0000000..7e64d4d --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * An optional interface to allow labelling Monolog processors. + * + * @author Nicolas Grekas + */ +interface ProcessorInterface +{ + /** + * @return array The processed records + */ + public function __invoke(array $records); +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 0000000..0088505 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Utils; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor implements ProcessorInterface +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = array(); + foreach ($record['context'] as $key => $val) { + if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { + $replacements['{'.$key.'}'] = $val; + } elseif (is_object($val)) { + $replacements['{'.$key.'}'] = '[object '.Utils::getClass($val).']'; + } else { + $replacements['{'.$key.'}'] = '['.gettype($val).']'; + } + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 0000000..615a4d9 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor implements ProcessorInterface +{ + private $tags; + + public function __construct(array $tags = array()) + { + $this->setTags($tags); + } + + public function addTags(array $tags = array()) + { + $this->tags = array_merge($this->tags, $tags); + } + + public function setTags(array $tags = array()) + { + $this->tags = $tags; + } + + public function __invoke(array $record) + { + $record['extra']['tags'] = $this->tags; + + return $record; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 0000000..d1f708c --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\ResettableInterface; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor implements ProcessorInterface, ResettableInterface +{ + private $uid; + + public function __construct($length = 7) + { + if (!is_int($length) || $length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + + $this->uid = $this->generateUid($length); + } + + public function __invoke(array $record) + { + $record['extra']['uid'] = $this->uid; + + return $record; + } + + /** + * @return string + */ + public function getUid() + { + return $this->uid; + } + + public function reset() + { + $this->uid = $this->generateUid(strlen($this->uid)); + } + + private function generateUid($length) + { + return substr(hash('md5', uniqid('', true)), 0, $length); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000..684188f --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor implements ProcessorInterface +{ + /** + * @var array|\ArrayAccess + */ + protected $serverData; + + /** + * Default fields + * + * Array is structured as [key in record.extra => key in $serverData] + * + * @var array + */ + protected $extraFields = array( + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + ); + + /** + * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + */ + public function __construct($serverData = null, array $extraFields = null) + { + if (null === $serverData) { + $this->serverData = &$_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + + if (null !== $extraFields) { + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = $this->appendExtraFields($record['extra']); + + return $record; + } + + /** + * @param string $extraName + * @param string $serverName + * @return $this + */ + public function addExtraField($extraName, $serverName) + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param array $extra + * @return array + */ + private function appendExtraFields(array $extra) + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; + } + + if (isset($this->serverData['UNIQUE_ID'])) { + $extra['unique_id'] = $this->serverData['UNIQUE_ID']; + } + + return $extra; + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Registry.php b/core/vendor/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 0000000..159b751 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Registry.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use InvalidArgumentException; + +/** + * Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new Monolog\Logger('application'); + * $api = new Monolog\Logger('api'); + * + * Monolog\Registry::addLogger($application); + * Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * Monolog\Registry::api()->addError('Sent to $api Logger instance'); + * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static $loggers = array(); + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + */ + public static function addLogger(Logger $logger, $name = null, $overwrite = false) + { + $name = $name ?: $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger) + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } else { + return isset(self::$loggers[$logger]); + } + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger) + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear() + { + self::$loggers = array(); + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function getInstance($name) + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param array $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic($name, $arguments) + { + return self::getInstance($name); + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/core/vendor/monolog/monolog/src/Monolog/ResettableInterface.php new file mode 100644 index 0000000..635bc77 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +/** + * Handler or Processor implementing this interface will be reset when Logger::reset() is called. + * + * Resetting ends a log cycle gets them back to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + * + * @author Grégoire Pineau + */ +interface ResettableInterface +{ + public function reset(); +} diff --git a/core/vendor/monolog/monolog/src/Monolog/SignalHandler.php b/core/vendor/monolog/monolog/src/Monolog/SignalHandler.php new file mode 100644 index 0000000..d590780 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/SignalHandler.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + */ +class SignalHandler +{ + private $logger; + + private $previousSignalHandler = array(); + private $signalLevelMap = array(); + private $signalRestartSyscalls = array(); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true) + { + if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { + return $this; + } + + if ($callPrevious) { + if (function_exists('pcntl_signal_get_handler')) { + $handler = pcntl_signal_get_handler($signo); + if ($handler === false) { + return $this; + } + $this->previousSignalHandler[$signo] = $handler; + } else { + $this->previousSignalHandler[$signo] = true; + } + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if (function_exists('pcntl_async_signals') && $async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + + return $this; + } + + public function handleSignal($signo, array $siginfo = null) + { + static $signals = array(); + + if (!$signals && extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + $constants = $pcntl->getConstants(); + if (!$constants) { + // HHVM 3.24.2 returns an empty array. + $constants = get_defined_constants(true); + $constants = $constants['Core']; + } + foreach ($constants as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { + $signals[$value] = $name; + } + } + unset($constants); + } + + $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL; + $signal = isset($signals[$signo]) ? $signals[$signo] : $signo; + $context = isset($siginfo) ? $siginfo : array(); + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) { + if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) { + $restartSyscalls = isset($this->restartSyscalls[$signo]) ? $this->restartSyscalls[$signo] : true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + if (PHP_VERSION_ID >= 70100) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } else { + $this->previousSignalHandler[$signo]($signo); + } + } + } +} diff --git a/core/vendor/monolog/monolog/src/Monolog/Utils.php b/core/vendor/monolog/monolog/src/Monolog/Utils.php new file mode 100644 index 0000000..eb9be86 --- /dev/null +++ b/core/vendor/monolog/monolog/src/Monolog/Utils.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class Utils +{ + /** + * @internal + */ + public static function getClass($object) + { + $class = \get_class($object); + + return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class; + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php new file mode 100644 index 0000000..a9a3f30 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; + +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testHandleError() + { + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new ErrorHandler($logger); + + $errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false); + trigger_error('Foo', E_USER_ERROR); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasErrorRecords()); + trigger_error('Foo', E_USER_NOTICE); + $this->assertCount(2, $handler->getRecords()); + $this->assertTrue($handler->hasEmergencyRecords()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php new file mode 100644 index 0000000..71c4204 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testDefaultFormat() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'unknown', + 'error', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::CRITICAL, + 'level_name' => 'CRITICAL', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'test : 14', + 'error', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithoutContext() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::DEBUG, + 'level_name' => 'DEBUG', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + 'log', + 'unknown', + 'log', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::formatBatch + */ + public function testBatchFormatThrowException() + { + $formatter = new ChromePHPFormatter(); + $records = array( + array( + 'level' => Logger::INFO, + 'level_name' => 'INFO', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ), + array( + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log2', + ), + ); + + $this->assertEquals( + array( + array( + 'meh', + 'log', + 'unknown', + 'info', + ), + array( + 'foo', + 'log2', + 'unknown', + 'warn', + ), + ), + $formatter->formatBatch($records) + ); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php new file mode 100644 index 0000000..90cc48d --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ElasticaFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists("Elastica\Document")) { + $this->markTestSkipped("ruflin/elastica not installed"); + } + } + + /** + * @covers Monolog\Formatter\ElasticaFormatter::__construct + * @covers Monolog\Formatter\ElasticaFormatter::format + * @covers Monolog\Formatter\ElasticaFormatter::getDocument + */ + public function testFormat() + { + // test log message + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + // expected values + $expected = $msg; + $expected['datetime'] = '1970-01-01T00:00:00.000000+00:00'; + $expected['context'] = array( + 'class' => '[object] (stdClass: {})', + 'foo' => 7, + 0 => 'bar', + ); + + // format log message + $formatter = new ElasticaFormatter('my_index', 'doc_type'); + $doc = $formatter->format($msg); + $this->assertInstanceOf('Elastica\Document', $doc); + + // Document parameters + $params = $doc->getParams(); + $this->assertEquals('my_index', $params['_index']); + $this->assertEquals('doc_type', $params['_type']); + + // Document data values + $data = $doc->getData(); + foreach (array_keys($expected) as $key) { + $this->assertEquals($expected[$key], $data[$key]); + } + } + + /** + * @covers Monolog\Formatter\ElasticaFormatter::getIndex + * @covers Monolog\Formatter\ElasticaFormatter::getType + */ + public function testGetters() + { + $formatter = new ElasticaFormatter('my_index', 'doc_type'); + $this->assertEquals('my_index', $formatter->getIndex()); + $this->assertEquals('doc_type', $formatter->getType()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php new file mode 100644 index 0000000..1b2fd97 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class FlowdockFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\FlowdockFormatter::format + */ + public function testFormat() + { + $formatter = new FlowdockFormatter('test_source', 'source@test.com'); + $record = $this->getRecord(); + + $expected = array( + 'source' => 'test_source', + 'from_address' => 'source@test.com', + 'subject' => 'in test_source: WARNING - test', + 'content' => 'test', + 'tags' => array('#logs', '#warning', '#test'), + 'project' => 'test_source', + ); + $formatted = $formatter->format($record); + + $this->assertEquals($expected, $formatted['flowdock']); + } + + /** + * @ covers Monolog\Formatter\FlowdockFormatter::formatBatch + */ + public function testFormatBatch() + { + $formatter = new FlowdockFormatter('test_source', 'source@test.com'); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $formatted = $formatter->formatBatch($records); + + $this->assertArrayHasKey('flowdock', $formatted[0]); + $this->assertArrayHasKey('flowdock', $formatted[1]); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php new file mode 100644 index 0000000..fd36dbc --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class FluentdFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\FluentdFormatter::__construct + * @covers Monolog\Formatter\FluentdFormatter::isUsingLevelsInTag + */ + public function testConstruct() + { + $formatter = new FluentdFormatter(); + $this->assertEquals(false, $formatter->isUsingLevelsInTag()); + $formatter = new FluentdFormatter(false); + $this->assertEquals(false, $formatter->isUsingLevelsInTag()); + $formatter = new FluentdFormatter(true); + $this->assertEquals(true, $formatter->isUsingLevelsInTag()); + } + + /** + * @covers Monolog\Formatter\FluentdFormatter::format + */ + public function testFormat() + { + $record = $this->getRecord(Logger::WARNING); + $record['datetime'] = new \DateTime("@0"); + + $formatter = new FluentdFormatter(); + $this->assertEquals( + '["test",0,{"message":"test","context":[],"extra":[],"level":300,"level_name":"WARNING"}]', + $formatter->format($record) + ); + } + + /** + * @covers Monolog\Formatter\FluentdFormatter::format + */ + public function testFormatWithTag() + { + $record = $this->getRecord(Logger::ERROR); + $record['datetime'] = new \DateTime("@0"); + + $formatter = new FluentdFormatter(true); + $this->assertEquals( + '["test.error",0,{"message":"test","context":[],"extra":[]}]', + $formatter->format($record) + ); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php new file mode 100644 index 0000000..4a24761 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists('\Gelf\Message')) { + $this->markTestSkipped("graylog2/gelf-php or mlehner/gelf-php is not installed"); + } + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals(0, $message->getTimestamp()); + $this->assertEquals('log', $message->getShortMessage()); + $this->assertEquals('meh', $message->getFacility()); + $this->assertEquals(null, $message->getLine()); + $this->assertEquals(null, $message->getFile()); + $this->assertEquals($this->isLegacy() ? 3 : 'error', $message->getLevel()); + $this->assertNotEmpty($message->getHost()); + + $formatter = new GelfMessageFormatter('mysystem'); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('mysystem', $message->getHost()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('test', $message->getFile()); + $this->assertEquals(14, $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + * @expectedException InvalidArgumentException + */ + public function testFormatInvalidFails() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + ); + + $formatter->format($record); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['_ctxt_from']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, null, 'CTX'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['_CTXfrom']); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContextContainingException() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger', 'exception' => array( + 'class' => '\Exception', + 'file' => '/some/file/in/dir.php:56', + 'trace' => array('/some/file/1.php:23', '/some/file/2.php:3'), + )), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $this->assertEquals("/some/file/in/dir.php", $message->getFile()); + $this->assertEquals("56", $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_key', $message_array); + $this->assertEquals('pair', $message_array['_key']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, 'EXT'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_EXTkey', $message_array); + $this->assertEquals('pair', $message_array['_EXTkey']); + } + + public function testFormatWithLargeData() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('exception' => str_repeat(' ', 32767)), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => str_repeat(' ', 32767)), + 'message' => 'log' + ); + $message = $formatter->format($record); + $messageArray = $message->toArray(); + + // 200 for padding + metadata + $length = 200; + + foreach ($messageArray as $key => $value) { + if (!in_array($key, array('level', 'timestamp'))) { + $length += strlen($value); + } + } + + $this->assertLessThanOrEqual(65792, $length, 'The message length is no longer than the maximum allowed length'); + } + + public function testFormatWithUnlimitedLength() + { + $formatter = new GelfMessageFormatter('LONG_SYSTEM_NAME', null, 'ctxt_', PHP_INT_MAX); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('exception' => str_repeat(' ', 32767 * 2)), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => str_repeat(' ', 32767 * 2)), + 'message' => 'log' + ); + $message = $formatter->format($record); + $messageArray = $message->toArray(); + + // 200 for padding + metadata + $length = 200; + + foreach ($messageArray as $key => $value) { + if (!in_array($key, array('level', 'timestamp'))) { + $length += strlen($value); + } + } + + $this->assertGreaterThanOrEqual(131289, $length, 'The message should not be truncated'); + } + + private function isLegacy() + { + return interface_exists('\Gelf\IMessagePublisher'); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php new file mode 100644 index 0000000..24b06cc --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class JsonFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\JsonFormatter::__construct + * @covers Monolog\Formatter\JsonFormatter::getBatchMode + * @covers Monolog\Formatter\JsonFormatter::isAppendingNewlines + */ + public function testConstruct() + { + $formatter = new JsonFormatter(); + $this->assertEquals(JsonFormatter::BATCH_MODE_JSON, $formatter->getBatchMode()); + $this->assertEquals(true, $formatter->isAppendingNewlines()); + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES, false); + $this->assertEquals(JsonFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode()); + $this->assertEquals(false, $formatter->isAppendingNewlines()); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::format + */ + public function testFormat() + { + $formatter = new JsonFormatter(); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record)."\n", $formatter->format($record)); + + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record), $formatter->format($record)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + * @covers Monolog\Formatter\JsonFormatter::formatBatchJson + */ + public function testFormatBatch() + { + $formatter = new JsonFormatter(); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $this->assertEquals(json_encode($records), $formatter->formatBatch($records)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + * @covers Monolog\Formatter\JsonFormatter::formatBatchNewlines + */ + public function testFormatBatchNewlines() + { + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES); + $records = $expected = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + array_walk($expected, function (&$value, $key) { + $value = json_encode($value); + }); + $this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records)); + } + + public function testDefFormatWithException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo'); + $formattedException = $this->formatException($exception); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?')); + $formattedPrevException = $this->formatException($exception->getPrevious()); + $formattedException = $this->formatException($exception, $formattedPrevException); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithThrowable() + { + if (!class_exists('Error') || !is_subclass_of('Error', 'Throwable')) { + $this->markTestSkipped('Requires PHP >=7'); + } + + $formatter = new JsonFormatter(); + $throwable = new \Error('Foo'); + $formattedThrowable = $this->formatException($throwable); + + $message = $this->formatRecordWithExceptionInContext($formatter, $throwable); + + $this->assertContextContainsFormattedException($formattedThrowable, $message); + } + + /** + * @param string $expected + * @param string $actual + * + * @internal param string $exception + */ + private function assertContextContainsFormattedException($expected, $actual) + { + $this->assertEquals( + '{"level_name":"CRITICAL","channel":"core","context":{"exception":'.$expected.'},"datetime":null,"extra":[],"message":"foobar"}'."\n", + $actual + ); + } + + /** + * @param JsonFormatter $formatter + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatRecordWithExceptionInContext(JsonFormatter $formatter, $exception) + { + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => $exception), + 'datetime' => null, + 'extra' => array(), + 'message' => 'foobar', + )); + return $message; + } + + /** + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatExceptionFilePathWithLine($exception) + { + $options = 0; + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + $path = substr(json_encode($exception->getFile(), $options), 1, -1); + return $path . ':' . $exception->getLine(); + } + + /** + * @param \Exception|\Throwable $exception + * + * @param null|string $previous + * + * @return string + */ + private function formatException($exception, $previous = null) + { + $formattedException = + '{"class":"' . get_class($exception) . + '","message":"' . $exception->getMessage() . + '","code":' . $exception->getCode() . + ',"file":"' . $this->formatExceptionFilePathWithLine($exception) . + ($previous ? '","previous":' . $previous : '"') . + '}'; + return $formattedException; + } + + public function testNormalizeHandleLargeArraysWithExactly1000Items() + { + $formatter = new NormalizerFormatter(); + $largeArray = range(1, 1000); + + $res = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array($largeArray), + 'datetime' => new \DateTime, + 'extra' => array(), + )); + + $this->assertCount(1000, $res['context'][0]); + $this->assertArrayNotHasKey('...', $res['context'][0]); + } + + public function testNormalizeHandleLargeArrays() + { + $formatter = new NormalizerFormatter(); + $largeArray = range(1, 2000); + + $res = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array($largeArray), + 'datetime' => new \DateTime, + 'extra' => array(), + )); + + $this->assertCount(1001, $res['context'][0]); + $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php new file mode 100644 index 0000000..310d93c --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\LineFormatter + */ +class LineFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testDefFormatWithString() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'context' => array(), + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + )); + $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testDefFormatWithArrayContext() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'bool' => false, + 'null' => null, + ), + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux","bool":false,"null":null} []'."\n", $message); + } + + public function testDefFormatExtras() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testFormatExtras() + { + $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\n", 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testContextAndExtraOptionallyNotShownIfEmpty() + { + $formatter = new LineFormatter(null, 'Y-m-d', false, true); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message); + } + + public function testContextAndExtraReplacement() + { + $formatter = new LineFormatter('%context.foo% => %extra.foo%'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 'bar'), + 'datetime' => new \DateTime, + 'extra' => array('foo' => 'xbar'), + 'message' => 'log', + )); + $this->assertEquals('bar => xbar', $message); + } + + public function testDefFormatWithObject() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'message' => 'foobar', + )); + + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: bar)","baz":[],"res":"[resource] (stream)"}'."\n", $message); + } + + public function testDefFormatWithException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo')), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).')"} []'."\n", $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $previous = new \LogicException('Wut?'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo', 0, $previous)), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).', LogicException(code: 0): Wut? at '.substr($path, 1, -1).':'.(__LINE__ - 12).')"} []'."\n", $message); + } + + public function testBatchFormat() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'."\n".'['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testFormatShouldStripInlineLineBreaks() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format( + array( + 'message' => "foo\nbar", + 'context' => array(), + 'extra' => array(), + ) + ); + + $this->assertRegExp('/foo bar/', $message); + } + + public function testFormatShouldNotStripInlineLineBreaksWhenFlagIsSet() + { + $formatter = new LineFormatter(null, 'Y-m-d', true); + $message = $formatter->format( + array( + 'message' => "foo\nbar", + 'context' => array(), + 'extra' => array(), + ) + ); + + $this->assertRegExp('/foo\nbar/', $message); + } +} + +class TestFoo +{ + public $foo = 'foo'; +} + +class TestBar +{ + public function __toString() + { + return 'bar'; + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php new file mode 100644 index 0000000..6d59b3f --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\TestCase; + +class LogglyFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\LogglyFormatter::__construct + */ + public function testConstruct() + { + $formatter = new LogglyFormatter(); + $this->assertEquals(LogglyFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode()); + $formatter = new LogglyFormatter(LogglyFormatter::BATCH_MODE_JSON); + $this->assertEquals(LogglyFormatter::BATCH_MODE_JSON, $formatter->getBatchMode()); + } + + /** + * @covers Monolog\Formatter\LogglyFormatter::format + */ + public function testFormat() + { + $formatter = new LogglyFormatter(); + $record = $this->getRecord(); + $formatted_decoded = json_decode($formatter->format($record), true); + $this->assertArrayHasKey("timestamp", $formatted_decoded); + $this->assertEquals(new \DateTime($formatted_decoded["timestamp"]), $record["datetime"]); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php new file mode 100644 index 0000000..9f6b1cc --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php @@ -0,0 +1,333 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class LogstashFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + \PHPUnit_Framework_Error_Warning::$enabled = true; + + return parent::tearDown(); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('meh', $message['@fields']['channel']); + $this->assertContains('meh', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + + $formatter = new LogstashFormatter('mysystem'); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['@fields']['file']); + $this->assertEquals(14, $message['@fields']['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['CTXfrom']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('key', $message_array); + $this->assertEquals('pair', $message_array['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('EXTkey', $message_array); + $this->assertEquals('pair', $message_array['EXTkey']); + } + + public function testFormatWithApplicationName() + { + $formatter = new LogstashFormatter('app', 'test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('@type', $message); + $this->assertEquals('app', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatterV1() + { + $formatter = new LogstashFormatter('test', 'hostname', null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals("1", $message['@version']); + $this->assertEquals('log', $message['message']); + $this->assertEquals('meh', $message['channel']); + $this->assertEquals('ERROR', $message['level']); + $this->assertEquals('test', $message['type']); + $this->assertEquals('hostname', $message['host']); + + $formatter = new LogstashFormatter('mysystem', null, null, 'ctxt_', LogstashFormatter::V1); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLineV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['file']); + $this->assertEquals(14, $message['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContextV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('ctxt_from', $message); + $this->assertEquals('logger', $message['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX', LogstashFormatter::V1); + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('CTXfrom', $message); + $this->assertEquals('logger', $message['CTXfrom']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtraV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('key', $message); + $this->assertEquals('pair', $message['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT', 'ctxt_', LogstashFormatter::V1); + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('EXTkey', $message); + $this->assertEquals('pair', $message['EXTkey']); + } + + public function testFormatWithApplicationNameV1() + { + $formatter = new LogstashFormatter('app', 'test', null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('type', $message); + $this->assertEquals('app', $message['type']); + } + + public function testFormatWithLatin9Data() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => '¯\_(ツ)_/¯', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array( + 'user_agent' => "\xD6WN; FBCR/OrangeEspa\xF1a; Vers\xE3o/4.0; F\xE4rist", + ), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('¯\_(ツ)_/¯', $message['@fields']['channel']); + $this->assertContains('¯\_(ツ)_/¯', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertEquals('ÖWN; FBCR/OrangeEspaña; Versão/4.0; Färist', $message['@fields']['user_agent']); + } else { + // PHP <5.5 does not return false for an element encoding failure, + // instead it emits a warning (possibly) and nulls the value. + $this->assertEquals(null, $message['@fields']['user_agent']); + } + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php new file mode 100644 index 0000000..52e699e --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * @author Florian Plattner + */ +class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists('MongoDate')) { + $this->markTestSkipped('mongo extension not installed'); + } + } + + public function constructArgumentProvider() + { + return array( + array(1, true, 1, true), + array(0, false, 0, false), + ); + } + + /** + * @param $traceDepth + * @param $traceAsString + * @param $expectedTraceDepth + * @param $expectedTraceAsString + * + * @dataProvider constructArgumentProvider + */ + public function testConstruct($traceDepth, $traceAsString, $expectedTraceDepth, $expectedTraceAsString) + { + $formatter = new MongoDBFormatter($traceDepth, $traceAsString); + + $reflTrace = new \ReflectionProperty($formatter, 'exceptionTraceAsString'); + $reflTrace->setAccessible(true); + $this->assertEquals($expectedTraceAsString, $reflTrace->getValue($formatter)); + + $reflDepth = new\ReflectionProperty($formatter, 'maxNestingLevel'); + $reflDepth->setAccessible(true); + $this->assertEquals($expectedTraceDepth, $reflDepth->getValue($formatter)); + } + + public function testSimpleFormat() + { + $record = array( + 'message' => 'some log message', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(); + $formattedRecord = $formatter->format($record); + + $this->assertCount(7, $formattedRecord); + $this->assertEquals('some log message', $formattedRecord['message']); + $this->assertEquals(array(), $formattedRecord['context']); + $this->assertEquals(Logger::WARNING, $formattedRecord['level']); + $this->assertEquals(Logger::getLevelName(Logger::WARNING), $formattedRecord['level_name']); + $this->assertEquals('test', $formattedRecord['channel']); + $this->assertInstanceOf('\MongoDate', $formattedRecord['datetime']); + $this->assertEquals('0.00000000 1391212800', $formattedRecord['datetime']->__toString()); + $this->assertEquals(array(), $formattedRecord['extra']); + } + + public function testRecursiveFormat() + { + $someObject = new \stdClass(); + $someObject->foo = 'something'; + $someObject->bar = 'stuff'; + + $record = array( + 'message' => 'some log message', + 'context' => array( + 'stuff' => new \DateTime('2014-02-01 02:31:33'), + 'some_object' => $someObject, + 'context_string' => 'some string', + 'context_int' => 123456, + 'except' => new \Exception('exception message', 987), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(); + $formattedRecord = $formatter->format($record); + + $this->assertCount(5, $formattedRecord['context']); + $this->assertInstanceOf('\MongoDate', $formattedRecord['context']['stuff']); + $this->assertEquals('0.00000000 1391221893', $formattedRecord['context']['stuff']->__toString()); + $this->assertEquals( + array( + 'foo' => 'something', + 'bar' => 'stuff', + 'class' => 'stdClass', + ), + $formattedRecord['context']['some_object'] + ); + $this->assertEquals('some string', $formattedRecord['context']['context_string']); + $this->assertEquals(123456, $formattedRecord['context']['context_int']); + + $this->assertCount(5, $formattedRecord['context']['except']); + $this->assertEquals('exception message', $formattedRecord['context']['except']['message']); + $this->assertEquals(987, $formattedRecord['context']['except']['code']); + $this->assertInternalType('string', $formattedRecord['context']['except']['file']); + $this->assertInternalType('integer', $formattedRecord['context']['except']['code']); + $this->assertInternalType('string', $formattedRecord['context']['except']['trace']); + $this->assertEquals('Exception', $formattedRecord['context']['except']['class']); + } + + public function testFormatDepthArray() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => array( + 'nest4' => 'value', + 'property' => 'nothing', + ), + ), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => '[...]', + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthArrayInfiniteNesting() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => array( + 'property' => 'something', + 'nest3' => array( + 'property' => 'anything', + 'nest4' => array( + 'property' => 'nothing', + ), + ), + ), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(0); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'something', + 'nest3' => array( + 'property' => 'anything', + 'nest4' => array( + 'property' => 'nothing', + ), + ), + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthObjects() + { + $someObject = new \stdClass(); + $someObject->property = 'anything'; + $someObject->nest3 = new \stdClass(); + $someObject->nest3->property = 'nothing'; + $someObject->nest3->nest4 = 'invisible'; + + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => $someObject, + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2, true); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => '[...]', + 'class' => 'stdClass', + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthException() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => new \Exception('exception message', 987), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2, false); + $formattedRecord = $formatter->format($record); + + $this->assertEquals('exception message', $formattedRecord['context']['nest2']['message']); + $this->assertEquals(987, $formattedRecord['context']['nest2']['code']); + $this->assertEquals('[...]', $formattedRecord['context']['nest2']['trace']); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php new file mode 100644 index 0000000..bafd1c7 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -0,0 +1,481 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\NormalizerFormatter + */ +class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + \PHPUnit_Framework_Error_Warning::$enabled = true; + + return parent::tearDown(); + } + + public function testFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'inf' => INF, + '-inf' => -INF, + 'nan' => acos(4), + ), + )); + + $this->assertEquals(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => date('Y-m-d'), + 'extra' => array( + 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})', + 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: bar)', + 'baz' => array(), + 'res' => '[resource] (stream)', + ), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'inf' => 'INF', + '-inf' => '-INF', + 'nan' => 'NaN', + ), + ), $formatted); + } + + public function testFormatExceptions() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \LogicException('bar'); + $e2 = new \RuntimeException('foo', 0, $e); + $formatted = $formatter->format(array( + 'exception' => $e2, + )); + + $this->assertGreaterThan(5, count($formatted['exception']['trace'])); + $this->assertTrue(isset($formatted['exception']['previous'])); + unset($formatted['exception']['trace'], $formatted['exception']['previous']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => get_class($e2), + 'message' => $e2->getMessage(), + 'code' => $e2->getCode(), + 'file' => $e2->getFile().':'.$e2->getLine(), + ), + ), $formatted); + } + + public function testFormatSoapFaultException() + { + if (!class_exists('SoapFault')) { + $this->markTestSkipped('Requires the soap extension'); + } + + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \SoapFault('foo', 'bar', 'hello', 'world'); + $formatted = $formatter->format(array( + 'exception' => $e, + )); + + unset($formatted['exception']['trace']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => 'SoapFault', + 'message' => 'bar', + 'code' => 0, + 'file' => $e->getFile().':'.$e->getLine(), + 'faultcode' => 'foo', + 'faultactor' => 'hello', + 'detail' => 'world', + ), + ), $formatted); + } + + public function testFormatToStringExceptionHandle() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $this->setExpectedException('RuntimeException', 'Could not convert to string'); + $formatter->format(array( + 'myObject' => new TestToStringError(), + )); + } + + public function testBatchFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + ), $formatted); + } + + /** + * Test issue #137 + */ + public function testIgnoresRecursiveObjectReferences() + { + // set up the recursion + $foo = new \stdClass(); + $bar = new \stdClass(); + + $foo->bar = $bar; + $bar->foo = $foo; + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($foo, $bar), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($foo, $bar)), $res); + } + + public function testCanNormalizeReferences() + { + $formatter = new NormalizerFormatter(); + $x = array('foo' => 'bar'); + $y = array('x' => &$x); + $x['y'] = &$y; + $formatter->format($y); + } + + public function testIgnoresInvalidTypes() + { + // set up the recursion + $resource = fopen(__FILE__, 'r'); + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($resource), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($resource)), $res); + } + + public function testNormalizeHandleLargeArraysWithExactly1000Items() + { + $formatter = new NormalizerFormatter(); + $largeArray = range(1, 1000); + + $res = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array($largeArray), + 'datetime' => new \DateTime, + 'extra' => array(), + )); + + $this->assertCount(1000, $res['context'][0]); + $this->assertArrayNotHasKey('...', $res['context'][0]); + } + + public function testNormalizeHandleLargeArrays() + { + $formatter = new NormalizerFormatter(); + $largeArray = range(1, 2000); + + $res = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array($largeArray), + 'datetime' => new \DateTime, + 'extra' => array(), + )); + + $this->assertCount(1001, $res['context'][0]); + $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']); + } + + /** + * @expectedException RuntimeException + */ + public function testThrowsOnInvalidEncoding() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + + // send an invalid unicode sequence as a object that can't be cleaned + $record = new \stdClass; + $record->message = "\xB1\x31"; + $res = $reflMethod->invoke($formatter, $record); + if (PHP_VERSION_ID < 50500 && $res === '{"message":null}') { + throw new \RuntimeException('PHP 5.3/5.4 throw a warning and null the value instead of returning false entirely'); + } + } + + public function testConvertsInvalidEncodingAsLatin9() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + + $res = $reflMethod->invoke($formatter, array('message' => "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE")); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res); + } else { + // PHP <5.5 does not return false for an element encoding failure, + // instead it emits a warning (possibly) and nulls the value. + $this->assertSame('{"message":null}', $res); + } + } + + /** + * @param mixed $in Input + * @param mixed $expect Expected output + * @covers Monolog\Formatter\NormalizerFormatter::detectAndCleanUtf8 + * @dataProvider providesDetectAndCleanUtf8 + */ + public function testDetectAndCleanUtf8($in, $expect) + { + $formatter = new NormalizerFormatter(); + $formatter->detectAndCleanUtf8($in); + $this->assertSame($expect, $in); + } + + public function providesDetectAndCleanUtf8() + { + $obj = new \stdClass; + + return array( + 'null' => array(null, null), + 'int' => array(123, 123), + 'float' => array(123.45, 123.45), + 'bool false' => array(false, false), + 'bool true' => array(true, true), + 'ascii string' => array('abcdef', 'abcdef'), + 'latin9 string' => array("\xB1\x31\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xFF", '±1€ŠšŽžŒœŸÿ'), + 'unicode string' => array('¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'), + 'empty array' => array(array(), array()), + 'array' => array(array('abcdef'), array('abcdef')), + 'object' => array($obj, $obj), + ); + } + + /** + * @param int $code + * @param string $msg + * @dataProvider providesHandleJsonErrorFailure + */ + public function testHandleJsonErrorFailure($code, $msg) + { + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'handleJsonError'); + $reflMethod->setAccessible(true); + + $this->setExpectedException('RuntimeException', $msg); + $reflMethod->invoke($formatter, $code, 'faked'); + } + + public function providesHandleJsonErrorFailure() + { + return array( + 'depth' => array(JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'), + 'state' => array(JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'), + 'ctrl' => array(JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'), + 'default' => array(-1, 'Unknown error'), + ); + } + + public function testExceptionTraceWithArgs() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported in HHVM since it detects errors differently'); + } + + // This happens i.e. in React promises or Guzzle streams where stream wrappers are registered + // and no file or line are included in the trace because it's treated as internal function + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); + }); + + try { + // This will contain $resource and $wrappedResource as arguments in the trace item + $resource = fopen('php://memory', 'rw+'); + fwrite($resource, 'test_resource'); + $wrappedResource = new TestFooNorm; + $wrappedResource->foo = $resource; + // Just do something stupid with a resource/wrapped resource as argument + array_keys($wrappedResource); + } catch (\Exception $e) { + restore_error_handler(); + } + + $formatter = new NormalizerFormatter(); + $record = array('context' => array('exception' => $e)); + $result = $formatter->format($record); + + $this->assertRegExp( + '%"resource":"\[resource\] \(stream\)"%', + $result['context']['exception']['trace'][0] + ); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $pattern = '%"wrappedResource":"\[object\] \(Monolog\\\\\\\\Formatter\\\\\\\\TestFooNorm: \)"%'; + } else { + $pattern = '%\\\\"foo\\\\":null%'; + } + + // Tests that the wrapped resource is ignored while encoding, only works for PHP <= 5.4 + $this->assertRegExp( + $pattern, + $result['context']['exception']['trace'][0] + ); + } + + public function testExceptionTraceDoesNotLeakCallUserFuncArgs() + { + try { + $arg = new TestInfoLeak; + call_user_func(array($this, 'throwHelper'), $arg, $dt = new \DateTime()); + } catch (\Exception $e) { + } + + $formatter = new NormalizerFormatter(); + $record = array('context' => array('exception' => $e)); + $result = $formatter->format($record); + + $this->assertSame( + '{"function":"throwHelper","class":"Monolog\\\\Formatter\\\\NormalizerFormatterTest","type":"->","args":["[object] (Monolog\\\\Formatter\\\\TestInfoLeak)","'.$dt->format('Y-m-d H:i:s').'"]}', + $result['context']['exception']['trace'][0] + ); + } + + private function throwHelper($arg) + { + throw new \RuntimeException('Thrown'); + } +} + +class TestFooNorm +{ + public $foo = 'foo'; +} + +class TestBarNorm +{ + public function __toString() + { + return 'bar'; + } +} + +class TestStreamFoo +{ + public $foo; + public $resource; + + public function __construct($resource) + { + $this->resource = $resource; + $this->foo = 'BAR'; + } + + public function __toString() + { + fseek($this->resource, 0); + + return $this->foo . ' - ' . (string) stream_get_contents($this->resource); + } +} + +class TestToStringError +{ + public function __toString() + { + throw new \RuntimeException('Could not convert to string'); + } +} + +class TestInfoLeak +{ + public function __toString() + { + return 'Sensitive information'; + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php new file mode 100644 index 0000000..b1c8fd4 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +class ScalarFormatterTest extends \PHPUnit_Framework_TestCase +{ + private $formatter; + + public function setUp() + { + $this->formatter = new ScalarFormatter(); + } + + public function buildTrace(\Exception $e) + { + $data = array(); + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data[] = $frame['file'].':'.$frame['line']; + } else { + $data[] = json_encode($frame); + } + } + + return $data; + } + + public function encodeJson($data) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } + + public function testFormat() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => array(1, 2, 3), + 'bat' => array('foo' => 'bar'), + 'bap' => \DateTime::createFromFormat(\DateTime::ISO8601, '1970-01-01T00:00:00+0000'), + 'ban' => $exception, + )); + + $this->assertSame(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => $this->encodeJson(array(1, 2, 3)), + 'bat' => $this->encodeJson(array('foo' => 'bar')), + 'bap' => '1970-01-01 00:00:00', + 'ban' => $this->encodeJson(array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + 'trace' => $this->buildTrace($exception), + )), + ), $formatted); + } + + public function testFormatWithErrorContext() + { + $context = array('file' => 'foo', 'line' => 1); + $formatted = $this->formatter->format(array( + 'context' => $context, + )); + + $this->assertSame(array( + 'context' => $this->encodeJson($context), + ), $formatted); + } + + public function testFormatWithExceptionContext() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'context' => array( + 'exception' => $exception, + ), + )); + + $this->assertSame(array( + 'context' => $this->encodeJson(array( + 'exception' => array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + 'trace' => $this->buildTrace($exception), + ), + )), + ), $formatted); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php new file mode 100644 index 0000000..52f15a3 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class WildfireFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testDefaultFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '125|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithFileAndLine() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '129|[{"Type":"ERROR","File":"test","Line":14,"Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithoutContext() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '58|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},"log"]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::formatBatch + * @expectedException BadMethodCallException + */ + public function testBatchFormatThrowException() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $wildfire->formatBatch(array($record)); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testTableFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'table-channel', + 'context' => array( + WildfireFormatter::TABLE => array( + array('col1', 'col2', 'col3'), + array('val1', 'val2', 'val3'), + array('foo1', 'foo2', 'foo3'), + array('bar1', 'bar2', 'bar3'), + ), + ), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'table-message', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '171|[{"Type":"TABLE","File":"","Line":"","Label":"table-channel: table-message"},[["col1","col2","col3"],["val1","val2","val3"],["foo1","foo2","foo3"],["bar1","bar2","bar3"]]]|', + $message + ); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php new file mode 100644 index 0000000..568eb9d --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\WebProcessor; + +class AbstractHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractHandler::__construct + * @covers Monolog\Handler\AbstractHandler::getLevel + * @covers Monolog\Handler\AbstractHandler::setLevel + * @covers Monolog\Handler\AbstractHandler::getBubble + * @covers Monolog\Handler\AbstractHandler::setBubble + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::setFormatter + */ + public function testConstructAndGetSet() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertEquals(Logger::WARNING, $handler->getLevel()); + $this->assertEquals(false, $handler->getBubble()); + + $handler->setLevel(Logger::ERROR); + $handler->setBubble(true); + $handler->setFormatter($formatter = new LineFormatter); + $this->assertEquals(Logger::ERROR, $handler->getLevel()); + $this->assertEquals(true, $handler->getBubble()); + $this->assertSame($formatter, $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::handleBatch + */ + public function testHandleBatch() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $handler->expects($this->exactly(2)) + ->method('handle'); + $handler->handleBatch(array($this->getRecord(), $this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractHandler::isHandling + */ + public function testIsHandling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->isHandling($this->getRecord())); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::__construct + */ + public function testHandlesPsrStyleLevels() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array('warning', false)); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + $handler->setLevel('debug'); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::getDefaultFormatter + */ + public function testGetFormatterInitializesDefault() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @covers Monolog\Handler\AbstractHandler::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + + $handler->pushProcessor(new \stdClass()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php new file mode 100644 index 0000000..24d4f63 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Processor\WebProcessor; + +class AbstractProcessingHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleLowerLevelMessage() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, true)); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, true)); + $this->assertFalse($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleNotBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, false)); + $this->assertTrue($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleIsFalseWhenNotHandled() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->handle($this->getRecord())); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::processRecord + */ + public function testProcessRecord() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + $handler->pushProcessor(new WebProcessor(array( + 'REQUEST_URI' => '', + 'REQUEST_METHOD' => '', + 'REMOTE_ADDR' => '', + 'SERVER_NAME' => '', + 'UNIQUE_ID' => '', + ))); + $handledRecord = null; + $handler->expects($this->once()) + ->method('write') + ->will($this->returnCallback(function ($record) use (&$handledRecord) { + $handledRecord = $record; + })) + ; + $handler->handle($this->getRecord()); + $this->assertEquals(6, count($handledRecord['extra'])); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php new file mode 100644 index 0000000..8e0e723 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Connection\AMQPConnection; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class AmqpHandlerTest extends TestCase +{ + public function testHandleAmqpExt() + { + if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) { + $this->markTestSkipped("amqp-php not installed"); + } + + if (!class_exists('AMQPChannel')) { + $this->markTestSkipped("Please update AMQP to version >= 1.0"); + } + + $messages = array(); + + $exchange = $this->getMock('AMQPExchange', array('publish', 'setName'), array(), '', false); + $exchange->expects($this->once()) + ->method('setName') + ->with('log') + ; + $exchange->expects($this->any()) + ->method('publish') + ->will($this->returnCallback(function ($message, $routing_key, $flags = 0, $attributes = array()) use (&$messages) { + $messages[] = array($message, $routing_key, $flags, $attributes); + })) + ; + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'warn.test', + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ), + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + $messages[0][0] = json_decode($messages[0][0], true); + unset($messages[0][0]['datetime']); + $this->assertEquals($expected, $messages[0]); + } + + public function testHandlePhpAmqpLib() + { + if (!class_exists('PhpAmqpLib\Connection\AMQPConnection')) { + $this->markTestSkipped("php-amqplib not installed"); + } + + $messages = array(); + + $exchange = $this->getMock('PhpAmqpLib\Channel\AMQPChannel', array('basic_publish', '__destruct'), array(), '', false); + + $exchange->expects($this->any()) + ->method('basic_publish') + ->will($this->returnCallback(function (AMQPMessage $msg, $exchange = "", $routing_key = "", $mandatory = false, $immediate = false, $ticket = null) use (&$messages) { + $messages[] = array($msg, $exchange, $routing_key, $mandatory, $immediate, $ticket); + })) + ; + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'log', + 'warn.test', + false, + false, + null, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ), + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + + /* @var $msg AMQPMessage */ + $msg = $messages[0][0]; + $messages[0][0] = json_decode($msg->body, true); + $messages[0][] = $msg->get_properties(); + unset($messages[0][0]['datetime']); + + $this->assertEquals($expected, $messages[0]); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php new file mode 100644 index 0000000..ffe45da --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\BrowserConsoleHandlerTest + */ +class BrowserConsoleHandlerTest extends TestCase +{ + protected function setUp() + { + BrowserConsoleHandler::resetStatic(); + } + + protected function generateScript() + { + $reflMethod = new \ReflectionMethod('Monolog\Handler\BrowserConsoleHandler', 'generateScript'); + $reflMethod->setAccessible(true); + + return $reflMethod->invoke(null); + } + + public function testStyling() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, 'foo[[bar]]{color: red}')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testEscaping() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, "[foo] [[\"bar\n[baz]\"]]{color: red}")); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testAutolabel() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}')); + $handler->handle($this->getRecord(Logger::DEBUG, '[[bar]]{macro: autolabel}')); + $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testContext() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, 'test', array('foo' => 'bar'))); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testConcurrentHandlers() + { + $handler1 = new BrowserConsoleHandler(); + $handler1->setFormatter($this->getIdentityFormatter()); + + $handler2 = new BrowserConsoleHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + + $handler1->handle($this->getRecord(Logger::DEBUG, 'test1')); + $handler2->handle($this->getRecord(Logger::DEBUG, 'test2')); + $handler1->handle($this->getRecord(Logger::DEBUG, 'test3')); + $handler2->handle($this->getRecord(Logger::DEBUG, 'test4')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php new file mode 100644 index 0000000..da8b3c3 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class BufferHandlerTest extends TestCase +{ + private $shutdownCheckHandler; + + /** + * @covers Monolog\Handler\BufferHandler::__construct + * @covers Monolog\Handler\BufferHandler::handle + * @covers Monolog\Handler\BufferHandler::close + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + + /** + * @covers Monolog\Handler\BufferHandler::close + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testPropagatesRecordsAtEndOfRequest() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->shutdownCheckHandler = $test; + register_shutdown_function(array($this, 'checkPropagation')); + } + + public function checkPropagation() + { + if (!$this->shutdownCheckHandler->hasWarningRecords() || !$this->shutdownCheckHandler->hasDebugRecords()) { + echo '!!! BufferHandlerTest::testPropagatesRecordsAtEndOfRequest failed to verify that the messages have been propagated' . PHP_EOL; + exit(1); + } + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimitWithFlushOnOverflow() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 3, Logger::DEBUG, true, true); + + // send two records + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertCount(0, $test->getRecords()); + + // overflow + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertCount(3, $test->getRecords()); + + // should buffer again + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertCount(3, $test->getRecords()); + + $handler->close(); + $this->assertCount(5, $test->getRecords()); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleLevel() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testFlush() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->flush(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->flush(); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php new file mode 100644 index 0000000..421cc49 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\ChromePHPHandler + */ +class ChromePHPHandlerTest extends TestCase +{ + protected function setUp() + { + TestChromePHPHandler::resetStatic(); + $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0'; + } + + /** + * @dataProvider agentsProvider + */ + public function testHeaders($agent) + { + $_SERVER['HTTP_USER_AGENT'] = $agent; + + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public static function agentsProvider() + { + return array( + array('Monolog Test; Chrome/1.0'), + array('Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0'), + array('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/56.0.2924.76 Chrome/56.0.2924.76 Safari/537.36'), + array('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome Safari/537.36'), + ); + } + + public function testHeadersOverflow() + { + $handler = new TestChromePHPHandler(); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 150 * 1024))); + + // overflow chrome headers limit + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 100 * 1024))); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + array( + 'test', + 'test', + 'unknown', + 'log', + ), + array( + 'test', + str_repeat('a', 150 * 1024), + 'unknown', + 'warn', + ), + array( + 'monolog', + 'Incomplete logs, chrome header size limit reached', + 'unknown', + 'warn', + ), + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestChromePHPHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + 'test', + 'test', + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler2->getHeaders()); + } +} + +class TestChromePHPHandler extends ChromePHPHandler +{ + protected $headers = array(); + + public static function resetStatic() + { + self::$initialized = false; + self::$overflowed = false; + self::$sendHeaders = true; + self::$json['rows'] = array(); + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php new file mode 100644 index 0000000..9fc4b38 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class CouchDBHandlerTest extends TestCase +{ + public function testHandle() + { + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new CouchDBHandler(); + + try { + $handler->handle($record); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984'); + } + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php new file mode 100644 index 0000000..e2aff86 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DeduplicationHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + */ + public function testFlushPassthruIfAllRecordsUnderTrigger() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + + $handler->flush(); + + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + */ + public function testFlushPassthruIfEmptyLog() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar')); + $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar")); + + $handler->flush(); + + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @depends testFlushPassthruIfEmptyLog + */ + public function testFlushSkipsIfLogExists() + { + $test = new TestHandler(); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar')); + $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar")); + + $handler->flush(); + + $this->assertFalse($test->hasErrorRecords()); + $this->assertFalse($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @depends testFlushPassthruIfEmptyLog + */ + public function testFlushPassthruIfLogTooOld() + { + $test = new TestHandler(); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $record = $this->getRecord(Logger::ERROR); + $record['datetime']->modify('+62seconds'); + $handler->handle($record); + $record = $this->getRecord(Logger::CRITICAL); + $record['datetime']->modify('+62seconds'); + $handler->handle($record); + + $handler->flush(); + + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @covers Monolog\Handler\DeduplicationHandler::collectLogs + */ + public function testGcOldLogs() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + // handle two records from yesterday, and one recent + $record = $this->getRecord(Logger::ERROR); + $record['datetime']->modify('-1day -10seconds'); + $handler->handle($record); + $record2 = $this->getRecord(Logger::CRITICAL); + $record2['datetime']->modify('-1day -10seconds'); + $handler->handle($record2); + $record3 = $this->getRecord(Logger::CRITICAL); + $record3['datetime']->modify('-30seconds'); + $handler->handle($record3); + + // log is written as none of them are duplicate + $handler->flush(); + $this->assertSame( + $record['datetime']->getTimestamp() . ":ERROR:test\n" . + $record2['datetime']->getTimestamp() . ":CRITICAL:test\n" . + $record3['datetime']->getTimestamp() . ":CRITICAL:test\n", + file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log') + ); + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + + // clear test handler + $test->clear(); + $this->assertFalse($test->hasErrorRecords()); + $this->assertFalse($test->hasCriticalRecords()); + + // log new records, duplicate log gets GC'd at the end of this flush call + $handler->handle($record = $this->getRecord(Logger::ERROR)); + $handler->handle($record2 = $this->getRecord(Logger::CRITICAL)); + $handler->flush(); + + // log should now contain the new errors and the previous one that was recent enough + $this->assertSame( + $record3['datetime']->getTimestamp() . ":CRITICAL:test\n" . + $record['datetime']->getTimestamp() . ":ERROR:test\n" . + $record2['datetime']->getTimestamp() . ":CRITICAL:test\n", + file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log') + ); + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + public static function tearDownAfterClass() + { + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php new file mode 100644 index 0000000..d67da90 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DoctrineCouchDBHandlerTest extends TestCase +{ + protected function setup() + { + if (!class_exists('Doctrine\CouchDB\CouchDBClient')) { + $this->markTestSkipped('The "doctrine/couchdb" package is not installed'); + } + } + + public function testHandle() + { + $client = $this->getMockBuilder('Doctrine\\CouchDB\\CouchDBClient') + ->setMethods(array('postDocument')) + ->disableOriginalConstructor() + ->getMock(); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $client->expects($this->once()) + ->method('postDocument') + ->with($expected); + + $handler = new DoctrineCouchDBHandler($client); + $handler->handle($record); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php new file mode 100644 index 0000000..2e6c348 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +class DynamoDbHandlerTest extends TestCase +{ + private $client; + + public function setUp() + { + if (!class_exists('Aws\DynamoDb\DynamoDbClient')) { + $this->markTestSkipped('aws/aws-sdk-php not installed'); + } + + $this->client = $this->getMockBuilder('Aws\DynamoDb\DynamoDbClient') + ->setMethods(array('formatAttributes', '__call')) + ->disableOriginalConstructor()->getMock(); + } + + public function testConstruct() + { + $this->assertInstanceOf('Monolog\Handler\DynamoDbHandler', new DynamoDbHandler($this->client, 'foo')); + } + + public function testInterface() + { + $this->assertInstanceOf('Monolog\Handler\HandlerInterface', new DynamoDbHandler($this->client, 'foo')); + } + + public function testGetFormatter() + { + $handler = new DynamoDbHandler($this->client, 'foo'); + $this->assertInstanceOf('Monolog\Formatter\ScalarFormatter', $handler->getFormatter()); + } + + public function testHandle() + { + $record = $this->getRecord(); + $formatter = $this->getMock('Monolog\Formatter\FormatterInterface'); + $formatted = array('foo' => 1, 'bar' => 2); + $handler = new DynamoDbHandler($this->client, 'foo'); + $handler->setFormatter($formatter); + + $isV3 = defined('Aws\Sdk::VERSION') && version_compare(\Aws\Sdk::VERSION, '3.0', '>='); + if ($isV3) { + $expFormatted = array('foo' => array('N' => 1), 'bar' => array('N' => 2)); + } else { + $expFormatted = $formatted; + } + + $formatter + ->expects($this->once()) + ->method('format') + ->with($record) + ->will($this->returnValue($formatted)); + $this->client + ->expects($isV3 ? $this->never() : $this->once()) + ->method('formatAttributes') + ->with($this->isType('array')) + ->will($this->returnValue($formatted)); + $this->client + ->expects($this->once()) + ->method('__call') + ->with('putItem', array(array( + 'TableName' => 'foo', + 'Item' => $expFormatted, + ))); + + $handler->handle($record); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php new file mode 100644 index 0000000..1687074 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\TestCase; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Request; +use Elastica\Response; + +class ElasticSearchHandlerTest extends TestCase +{ + /** + * @var Client mock + */ + protected $client; + + /** + * @var array Default handler options + */ + protected $options = array( + 'index' => 'my_index', + 'type' => 'doc_type', + ); + + public function setUp() + { + // Elastica lib required + if (!class_exists("Elastica\Client")) { + $this->markTestSkipped("ruflin/elastica not installed"); + } + + // base mock Elastica Client object + $this->client = $this->getMockBuilder('Elastica\Client') + ->setMethods(array('addDocuments')) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::write + * @covers Monolog\Handler\ElasticSearchHandler::handleBatch + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter + */ + public function testHandle() + { + // log message + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + // format expected result + $formatter = new ElasticaFormatter($this->options['index'], $this->options['type']); + $expected = array($formatter->format($msg)); + + // setup ES client mock + $this->client->expects($this->any()) + ->method('addDocuments') + ->with($expected); + + // perform tests + $handler = new ElasticSearchHandler($this->client, $this->options); + $handler->handle($msg); + $handler->handleBatch(array($msg)); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::setFormatter + */ + public function testSetFormatter() + { + $handler = new ElasticSearchHandler($this->client); + $formatter = new ElasticaFormatter('index_new', 'type_new'); + $handler->setFormatter($formatter); + $this->assertInstanceOf('Monolog\Formatter\ElasticaFormatter', $handler->getFormatter()); + $this->assertEquals('index_new', $handler->getFormatter()->getIndex()); + $this->assertEquals('type_new', $handler->getFormatter()->getType()); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::setFormatter + * @expectedException InvalidArgumentException + * @expectedExceptionMessage ElasticSearchHandler is only compatible with ElasticaFormatter + */ + public function testSetFormatterInvalid() + { + $handler = new ElasticSearchHandler($this->client); + $formatter = new NormalizerFormatter(); + $handler->setFormatter($formatter); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::__construct + * @covers Monolog\Handler\ElasticSearchHandler::getOptions + */ + public function testOptions() + { + $expected = array( + 'index' => $this->options['index'], + 'type' => $this->options['type'], + 'ignore_error' => false, + ); + $handler = new ElasticSearchHandler($this->client, $this->options); + $this->assertEquals($expected, $handler->getOptions()); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @dataProvider providerTestConnectionErrors + */ + public function testConnectionErrors($ignore, $expectedError) + { + $clientOpts = array('host' => '127.0.0.1', 'port' => 1); + $client = new Client($clientOpts); + $handlerOpts = array('ignore_error' => $ignore); + $handler = new ElasticSearchHandler($client, $handlerOpts); + + if ($expectedError) { + $this->setExpectedException($expectedError[0], $expectedError[1]); + $handler->handle($this->getRecord()); + } else { + $this->assertFalse($handler->handle($this->getRecord())); + } + } + + /** + * @return array + */ + public function providerTestConnectionErrors() + { + return array( + array(false, array('RuntimeException', 'Error sending messages to Elasticsearch')), + array(true, false), + ); + } + + /** + * Integration test using localhost Elastic Search server + * + * @covers Monolog\Handler\ElasticSearchHandler::__construct + * @covers Monolog\Handler\ElasticSearchHandler::handleBatch + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter + */ + public function testHandleIntegration() + { + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $expected = $msg; + $expected['datetime'] = $msg['datetime']->format(\DateTime::ISO8601); + $expected['context'] = array( + 'class' => '[object] (stdClass: {})', + 'foo' => 7, + 0 => 'bar', + ); + + $client = new Client(); + $handler = new ElasticSearchHandler($client, $this->options); + try { + $handler->handleBatch(array($msg)); + } catch (\RuntimeException $e) { + $this->markTestSkipped("Cannot connect to Elastic Search server on localhost"); + } + + // check document id from ES server response + $documentId = $this->getCreatedDocId($client->getLastResponse()); + $this->assertNotEmpty($documentId, 'No elastic document id received'); + + // retrieve document source from ES and validate + $document = $this->getDocSourceFromElastic( + $client, + $this->options['index'], + $this->options['type'], + $documentId + ); + $this->assertEquals($expected, $document); + + // remove test index from ES + $client->request("/{$this->options['index']}", Request::DELETE); + } + + /** + * Return last created document id from ES response + * @param Response $response Elastica Response object + * @return string|null + */ + protected function getCreatedDocId(Response $response) + { + $data = $response->getData(); + if (!empty($data['items'][0]['create']['_id'])) { + return $data['items'][0]['create']['_id']; + } + } + + /** + * Retrieve document by id from Elasticsearch + * @param Client $client Elastica client + * @param string $index + * @param string $type + * @param string $documentId + * @return array + */ + protected function getDocSourceFromElastic(Client $client, $index, $type, $documentId) + { + $resp = $client->request("/{$index}/{$type}/{$documentId}", Request::GET); + $data = $resp->getData(); + if (!empty($data['_source'])) { + return $data['_source']; + } + + return array(); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php new file mode 100644 index 0000000..99785cb --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +function error_log() +{ + $GLOBALS['error_log'][] = func_get_args(); +} + +class ErrorLogHandlerTest extends TestCase +{ + protected function setUp() + { + $GLOBALS['error_log'] = array(); + } + + /** + * @covers Monolog\Handler\ErrorLogHandler::__construct + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The given message type "42" is not supported + */ + public function testShouldNotAcceptAnInvalidTypeOnContructor() + { + new ErrorLogHandler(42); + } + + /** + * @covers Monolog\Handler\ErrorLogHandler::write + */ + public function testShouldLogMessagesUsingErrorLogFuncion() + { + $type = ErrorLogHandler::OPERATING_SYSTEM; + $handler = new ErrorLogHandler($type); + $handler->setFormatter(new LineFormatter('%channel%.%level_name%: %message% %context% %extra%', null, true)); + $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + + $this->assertSame("test.ERROR: Foo\nBar\r\n\r\nBaz [] []", $GLOBALS['error_log'][0][0]); + $this->assertSame($GLOBALS['error_log'][0][1], $type); + + $handler = new ErrorLogHandler($type, Logger::DEBUG, true, true); + $handler->setFormatter(new LineFormatter(null, null, true)); + $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + + $this->assertStringMatchesFormat('[%s] test.ERROR: Foo', $GLOBALS['error_log'][1][0]); + $this->assertSame($GLOBALS['error_log'][1][1], $type); + + $this->assertStringMatchesFormat('Bar', $GLOBALS['error_log'][2][0]); + $this->assertSame($GLOBALS['error_log'][2][1], $type); + + $this->assertStringMatchesFormat('Baz [] []', $GLOBALS['error_log'][3][0]); + $this->assertSame($GLOBALS['error_log'][3][1], $type); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php new file mode 100644 index 0000000..31b7686 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class FilterHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FilterHandler::isHandling + */ + public function testIsHandling() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::INFO))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::NOTICE))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::CRITICAL))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::ALERT))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::EMERGENCY))); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + * @covers Monolog\Handler\FilterHandler::setAcceptedLevels + * @covers Monolog\Handler\FilterHandler::isHandling + */ + public function testHandleProcessOnlyNeededLevels() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::NOTICE)); + $this->assertTrue($test->hasNoticeRecords()); + + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $handler->handle($this->getRecord(Logger::ERROR)); + $this->assertFalse($test->hasErrorRecords()); + $handler->handle($this->getRecord(Logger::CRITICAL)); + $this->assertFalse($test->hasCriticalRecords()); + $handler->handle($this->getRecord(Logger::ALERT)); + $this->assertFalse($test->hasAlertRecords()); + $handler->handle($this->getRecord(Logger::EMERGENCY)); + $this->assertFalse($test->hasEmergencyRecords()); + + $test = new TestHandler(); + $handler = new FilterHandler($test, array(Logger::INFO, Logger::ERROR)); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::NOTICE)); + $this->assertFalse($test->hasNoticeRecords()); + $handler->handle($this->getRecord(Logger::ERROR)); + $this->assertTrue($test->hasErrorRecords()); + $handler->handle($this->getRecord(Logger::CRITICAL)); + $this->assertFalse($test->hasCriticalRecords()); + } + + /** + * @covers Monolog\Handler\FilterHandler::setAcceptedLevels + * @covers Monolog\Handler\FilterHandler::getAcceptedLevels + */ + public function testAcceptedLevelApi() + { + $test = new TestHandler(); + $handler = new FilterHandler($test); + + $levels = array(Logger::INFO, Logger::ERROR); + $handler->setAcceptedLevels($levels); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $handler->setAcceptedLevels(array('info', 'error')); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $levels = array(Logger::CRITICAL, Logger::ALERT, Logger::EMERGENCY); + $handler->setAcceptedLevels(Logger::CRITICAL, Logger::EMERGENCY); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $handler->setAcceptedLevels('critical', 'emergency'); + $this->assertSame($levels, $handler->getAcceptedLevels()); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::DEBUG, Logger::EMERGENCY); + $handler->pushProcessor( + function ($record) { + $record['extra']['foo'] = true; + + return $record; + } + ); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleRespectsBubble() + { + $test = new TestHandler(); + + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, false); + $this->assertTrue($handler->handle($this->getRecord(Logger::INFO))); + $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING))); + + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, true); + $this->assertFalse($handler->handle($this->getRecord(Logger::INFO))); + $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING))); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FilterHandler( + function ($record, $handler) use ($test) { + return $test; + }, Logger::INFO, Logger::NOTICE, false + ); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + * @expectedException \RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FilterHandler( + function ($record, $handler) { + return 'foo'; + } + ); + $handler->handle($this->getRecord(Logger::WARNING)); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php new file mode 100644 index 0000000..0ec3653 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy; +use Psr\Log\LogLevel; + +class FingersCrossedHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleStopsBufferingAfterTrigger() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + * @covers Monolog\Handler\FingersCrossedHandler::reset + */ + public function testHandleResetBufferingAfterReset() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->reset(); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleResetBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler(function ($record, $handler) use ($test) { + return $test; + }); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + * @expectedException RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FingersCrossedHandler(function ($record, $handler) { + return 'foo'; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::isHandling + */ + public function testIsHandlingAlways() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::ERROR); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategyWithPsrLevel() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning')); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testOverrideActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning')); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->activate(); + $this->assertTrue($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Logger::ERROR, array('othertest' => Logger::DEBUG))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategyWithPsrLevels() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy('error', array('othertest' => 'debug'))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::INFO); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::close + */ + public function testPassthruOnClose() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::close + */ + public function testPsrLevelPassthruOnClose() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, LogLevel::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php new file mode 100644 index 0000000..7a404e6 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\FirePHPHandler + */ +class FirePHPHandlerTest extends TestCase +{ + public function setUp() + { + TestFirePHPHandler::resetStatic(); + $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0'; + } + + public function testHeaders() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestFirePHPHandler; + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $expected2 = array( + 'X-Wf-1-1-1-3' => 'test', + 'X-Wf-1-1-1-4' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + $this->assertEquals($expected2, $handler2->getHeaders()); + } +} + +class TestFirePHPHandler extends FirePHPHandler +{ + protected $headers = array(); + + public static function resetStatic() + { + self::$initialized = false; + self::$sendHeaders = true; + self::$messageIndex = 1; + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep b/core/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php new file mode 100644 index 0000000..91cdd31 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass \Monolog\Handler\FleepHookHandler + */ +class FleepHookHandlerTest extends TestCase +{ + /** + * Default token to use in tests + */ + const TOKEN = '123abc'; + + /** + * @var FleepHookHandler + */ + private $handler; + + public function setUp() + { + parent::setUp(); + + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl extension to run'); + } + + // Create instances of the handler and logger for convenience + $this->handler = new FleepHookHandler(self::TOKEN); + } + + /** + * @covers ::__construct + */ + public function testConstructorSetsExpectedDefaults() + { + $this->assertEquals(Logger::DEBUG, $this->handler->getLevel()); + $this->assertEquals(true, $this->handler->getBubble()); + } + + /** + * @covers ::getDefaultFormatter + */ + public function testHandlerUsesLineFormatterWhichIgnoresEmptyArrays() + { + $record = array( + 'message' => 'msg', + 'context' => array(), + 'level' => Logger::DEBUG, + 'level_name' => Logger::getLevelName(Logger::DEBUG), + 'channel' => 'channel', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + + $expectedFormatter = new LineFormatter(null, null, true, true); + $expected = $expectedFormatter->format($record); + + $handlerFormatter = $this->handler->getFormatter(); + $actual = $handlerFormatter->format($record); + + $this->assertEquals($expected, $actual, 'Empty context and extra arrays should not be rendered'); + } + + /** + * @covers ::__construct + */ + public function testConnectionStringisConstructedCorrectly() + { + $this->assertEquals('ssl://' . FleepHookHandler::FLEEP_HOST . ':443', $this->handler->getConnectionString()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php new file mode 100644 index 0000000..4b120d5 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FlowdockFormatter; +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Dominik Liebler + * @see https://www.hipchat.com/docs/api + */ +class FlowdockHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var FlowdockHandler + */ + private $handler; + + public function setUp() + { + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl to run'); + } + } + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/messages\/team_inbox\/.* HTTP\/1.1\\r\\nHost: api.flowdock.com\\r\\nContent-Type: application\/json\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/"source":"test_source"/', $content); + $this->assertRegexp('/"from_address":"source@test\.com"/', $content); + } + + private function createHandler($token = 'myToken') + { + $constructorArgs = array($token, Logger::DEBUG); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\FlowdockHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter(new FlowdockFormatter('test_source', 'source@test.com')); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php new file mode 100644 index 0000000..9d007b1 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\Message; +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerLegacyTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Gelf\MessagePublisher') || !class_exists('Gelf\Message')) { + $this->markTestSkipped("mlehner/gelf-php not installed"); + } + + require_once __DIR__ . '/GelfMockMessagePublisher.php'; + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return new GelfMockMessagePublisher('localhost'); + } + + public function testDebug() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $handler->handle($record); + + $this->assertEquals(7, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testWarning() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $handler->handle($record); + + $this->assertEquals(4, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testInjectedGelfMessageFormatter() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + $handler->handle($record); + + $this->assertEquals('mysystem', $messagePublisher->lastMessage->getHost()); + $this->assertArrayHasKey('_EXTblarg', $messagePublisher->lastMessage->toArray()); + $this->assertArrayHasKey('_CTXfrom', $messagePublisher->lastMessage->toArray()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php new file mode 100644 index 0000000..8cdd64f --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\Message; +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Gelf\Publisher') || !class_exists('Gelf\Message')) { + $this->markTestSkipped("graylog2/gelf-php not installed"); + } + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return $this->getMock('Gelf\Publisher', array('publish'), array(), '', false); + } + + public function testDebug() + { + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(7) + ->setFacility("test") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + + $handler->handle($record); + } + + public function testWarning() + { + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(4) + ->setFacility("test") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + + $handler->handle($record); + } + + public function testInjectedGelfMessageFormatter() + { + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(4) + ->setFacility("test") + ->setHost("mysystem") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ->setAdditional("EXTblarg", 'yep') + ->setAdditional("CTXfrom", 'logger') + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + $handler->handle($record); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php new file mode 100644 index 0000000..873d92f --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\MessagePublisher; +use Gelf\Message; + +class GelfMockMessagePublisher extends MessagePublisher +{ + public function publish(Message $message) + { + $this->lastMessage = $message; + } + + public $lastMessage = null; +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php new file mode 100644 index 0000000..a1b8617 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class GroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new GroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new GroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new GroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleBatchUsesProcessors() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + $this->assertTrue($records[1]['extra']['foo']); + } + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php new file mode 100644 index 0000000..d8d0452 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @author Alexey Karapetov + */ +class HandlerWrapperTest extends TestCase +{ + /** + * @var HandlerWrapper + */ + private $wrapper; + + private $handler; + + public function setUp() + { + parent::setUp(); + $this->handler = $this->getMock('Monolog\\Handler\\HandlerInterface'); + $this->wrapper = new HandlerWrapper($this->handler); + } + + /** + * @return array + */ + public function trueFalseDataProvider() + { + return array( + array(true), + array(false), + ); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testIsHandling($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('isHandling') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->isHandling($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandle($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('handle') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handle($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandleBatch($result) + { + $records = $this->getMultipleRecords(); + $this->handler->expects($this->once()) + ->method('handleBatch') + ->with($records) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handleBatch($records)); + } + + public function testPushProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('pushProcessor') + ->with($processor); + + $this->assertEquals($this->wrapper, $this->wrapper->pushProcessor($processor)); + } + + public function testPopProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('popProcessor') + ->willReturn($processor); + + $this->assertEquals($processor, $this->wrapper->popProcessor()); + } + + public function testSetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('setFormatter') + ->with($formatter); + + $this->assertEquals($this->wrapper, $this->wrapper->setFormatter($formatter)); + } + + public function testGetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('getFormatter') + ->willReturn($formatter); + + $this->assertEquals($formatter, $this->wrapper->getFormatter()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php new file mode 100644 index 0000000..52dc9da --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandlerTest extends TestCase +{ + private $res; + /** @var HipChatHandler */ + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteCustomHostHeader() + { + $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteV2() + { + $this->createHandler('myToken', 'room1', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteV2Notify() + { + $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testRoomSpaces() + { + $this->createHandler('myToken', 'room name', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room%20name\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + public function testWriteContentV1WithoutName() + { + $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v1'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=$/', $content); + + return $content; + } + + /** + * @depends testWriteCustomHostHeader + */ + public function testWriteContentNotify($content) + { + $this->assertRegexp('/notify=1&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2 + */ + public function testWriteContentV2($content) + { + $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2Notify + */ + public function testWriteContentV2Notify($content) + { + $this->assertRegexp('/notify=true&message=test1&message_format=text&color=red&from=Monolog$/', $content); + } + + public function testWriteContentV2WithoutName() + { + $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red$/', $content); + + return $content; + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteTruncatesLongMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, str_repeat('abcde', 2000))); + fseek($this->res, 0); + $content = fread($this->res, 12000); + + $this->assertRegexp('/message='.str_repeat('abcde', 1900).'\+%5Btruncated%5D/', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteWithErrorLevelsAndColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, 'gray'), + array(Logger::INFO, 'green'), + array(Logger::WARNING, 'yellow'), + array(Logger::ERROR, 'red'), + array(Logger::CRITICAL, 'red'), + array(Logger::ALERT, 'red'), + array(Logger::EMERGENCY,'red'), + array(Logger::NOTICE, 'green'), + ); + } + + /** + * @dataProvider provideBatchRecords + */ + public function testHandleBatch($records, $expectedColor) + { + $this->createHandler(); + + $this->handler->handleBatch($records); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideBatchRecords() + { + return array( + array( + array( + array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + array('level' => Logger::CRITICAL, 'message' => 'Everything is broken!', 'level_name' => 'critical', 'datetime' => new \DateTime()), + ), + 'red', + ), + array( + array( + array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + ), + 'yellow', + ), + array( + array( + array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + ), + 'green', + ), + array( + array( + array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()), + ), + 'gray', + ), + ); + } + + private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false, $host = 'api.hipchat.com', $version = 'v1') + { + $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG, true, true, 'text', $host, $version); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\HipChatHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateWithTooLongName() + { + $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere'); + } + + public function testCreateWithTooLongNameV2() + { + // creating a handler with too long of a name but using the v2 api doesn't matter. + $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere', false, Logger::CRITICAL, true, true, 'test', 'api.hipchat.com', 'v2'); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php new file mode 100644 index 0000000..97c18b5 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + namespace Monolog\Handler; + + use Monolog\TestCase; + use Monolog\Logger; + +/** + * @author Robert Kaufmann III + * @author Gabriel Machado + */ +class InsightOpsHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $resource; + + /** + * @var LogEntriesHandler + */ + private $handler; + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test')); + + fseek($this->resource, 0); + $content = fread($this->resource, 1024); + + $this->assertRegexp('/testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] test.CRITICAL: Critical write test/', $content); + } + + public function testWriteBatchContent() + { + $this->createHandler(); + $this->handler->handleBatch($this->getMultipleRecords()); + + fseek($this->resource, 0); + $content = fread($this->resource, 1024); + + $this->assertRegexp('/(testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] .* \[\] \[\]\n){3}/', $content); + } + + private function createHandler() + { + $useSSL = extension_loaded('openssl'); + $args = array('testToken', 'us', $useSSL, Logger::DEBUG, true); + $this->resource = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\InsightOpsHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $args + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->resource)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php new file mode 100644 index 0000000..b2deb40 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var LogEntriesHandler + */ + private $handler; + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test')); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] test.CRITICAL: Critical write test/', $content); + } + + public function testWriteBatchContent() + { + $records = array( + $this->getRecord(), + $this->getRecord(), + $this->getRecord(), + ); + $this->createHandler(); + $this->handler->handleBatch($records); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/(testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] .* \[\] \[\]\n){3}/', $content); + } + + private function createHandler() + { + $useSSL = extension_loaded('openssl'); + $args = array('testToken', $useSSL, Logger::DEBUG, true); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\LogEntriesHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $args + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php new file mode 100644 index 0000000..6754f3d --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class MailHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatch() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once()) + ->method('formatBatch'); // Each record is formatted + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->once()) + ->method('send'); + $handler->expects($this->never()) + ->method('write'); // write is for individual records + + $handler->setFormatter($formatter); + + $handler->handleBatch($this->getMultipleRecords()); + } + + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->never()) + ->method('send'); + $handler->setLevel(Logger::ERROR); + + $handler->handleBatch($records); + } + + /** + * @covers Monolog\Handler\MailHandler::write + */ + public function testHandle() + { + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + + $record = $this->getRecord(); + $records = array($record); + $records[0]['formatted'] = '['.$record['datetime']->format('Y-m-d H:i:s').'] test.WARNING: test [] []'."\n"; + + $handler->expects($this->once()) + ->method('send') + ->with($records[0]['formatted'], $records); + + $handler->handle($record); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php new file mode 100644 index 0000000..a083322 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Raven_Client; + +class MockRavenClient extends Raven_Client +{ + public function capture($data, $stack, $vars = null) + { + $data = array_merge($this->get_user_data(), $data); + $this->lastData = $data; + $this->lastStack = $stack; + } + + public $lastData; + public $lastStack; +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php new file mode 100644 index 0000000..0fdef63 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class MongoDBHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDBHandler(new \stdClass(), 'DB', 'Collection'); + } + + public function testHandle() + { + $mongo = $this->getMock('Mongo', array('selectCollection'), array(), '', false); + $collection = $this->getMock('stdClass', array('save')); + + $mongo->expects($this->once()) + ->method('selectCollection') + ->with('DB', 'Collection') + ->will($this->returnValue($collection)); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $collection->expects($this->once()) + ->method('save') + ->with($expected); + + $handler = new MongoDBHandler($mongo, 'DB', 'Collection'); + $handler->handle($record); + } +} + +if (!class_exists('Mongo')) { + class Mongo + { + public function selectCollection() + { + } + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php new file mode 100644 index 0000000..ddf545d --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use InvalidArgumentException; + +function mail($to, $subject, $message, $additional_headers = null, $additional_parameters = null) +{ + $GLOBALS['mail'][] = func_get_args(); +} + +class NativeMailerHandlerTest extends TestCase +{ + protected function setUp() + { + $GLOBALS['mail'] = array(); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', "receiver@example.org\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader("Content-Type: text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterArrayHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader(array("Content-Type: text/html\r\nFrom: faked@attacker.org")); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterContentTypeInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->setContentType("text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterEncodingInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->setEncoding("utf-8\r\nFrom: faked@attacker.org"); + } + + public function testSend() + { + $to = 'spammer@example.org'; + $subject = 'dear victim'; + $from = 'receiver@example.org'; + + $mailer = new NativeMailerHandler($to, $subject, $from); + $mailer->handleBatch(array()); + + // batch is empty, nothing sent + $this->assertEmpty($GLOBALS['mail']); + + // non-empty batch + $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + $this->assertNotEmpty($GLOBALS['mail']); + $this->assertInternalType('array', $GLOBALS['mail']); + $this->assertArrayHasKey('0', $GLOBALS['mail']); + $params = $GLOBALS['mail'][0]; + $this->assertCount(5, $params); + $this->assertSame($to, $params[0]); + $this->assertSame($subject, $params[1]); + $this->assertStringEndsWith(" test.ERROR: Foo Bar Baz [] []\n", $params[2]); + $this->assertSame("From: $from\r\nContent-type: text/plain; charset=utf-8\r\n", $params[3]); + $this->assertSame('', $params[4]); + } + + public function testMessageSubjectFormatting() + { + $mailer = new NativeMailerHandler('to@example.org', 'Alert: %level_name% %message%', 'from@example.org'); + $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + $this->assertNotEmpty($GLOBALS['mail']); + $this->assertInternalType('array', $GLOBALS['mail']); + $this->assertArrayHasKey('0', $GLOBALS['mail']); + $params = $GLOBALS['mail'][0]; + $this->assertCount(5, $params); + $this->assertSame('Alert: ERROR Foo Bar Baz', $params[1]); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php new file mode 100644 index 0000000..4d3a615 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\TestCase; +use Monolog\Logger; + +class NewRelicHandlerTest extends TestCase +{ + public static $appname; + public static $customParameters; + public static $transactionName; + + public function setUp() + { + self::$appname = null; + self::$customParameters = array(); + self::$transactionName = null; + } + + /** + * @expectedException Monolog\Handler\MissingExtensionException + */ + public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded() + { + $handler = new StubNewRelicHandlerWithoutExtension(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanHandleTheRecord() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanAddContextParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('a' => 'b'))); + $this->assertEquals(array('context_a' => 'b'), self::$customParameters); + } + + public function testThehandlerCanAddExplodedContextParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true); + $handler->handle($this->getRecord( + Logger::ERROR, + 'log message', + array('a' => array('key1' => 'value1', 'key2' => 'value2')) + )); + $this->assertEquals( + array('context_a_key1' => 'value1', 'context_a_key2' => 'value2'), + self::$customParameters + ); + } + + public function testThehandlerCanAddExtraParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message'); + $record['extra'] = array('c' => 'd'); + + $handler = new StubNewRelicHandler(); + $handler->handle($record); + + $this->assertEquals(array('extra_c' => 'd'), self::$customParameters); + } + + public function testThehandlerCanAddExplodedExtraParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message'); + $record['extra'] = array('c' => array('key1' => 'value1', 'key2' => 'value2')); + + $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true); + $handler->handle($record); + + $this->assertEquals( + array('extra_c_key1' => 'value1', 'extra_c_key2' => 'value2'), + self::$customParameters + ); + } + + public function testThehandlerCanAddExtraContextAndParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message', array('a' => 'b')); + $record['extra'] = array('c' => 'd'); + + $handler = new StubNewRelicHandler(); + $handler->handle($record); + + $expected = array( + 'context_a' => 'b', + 'extra_c' => 'd', + ); + + $this->assertEquals($expected, self::$customParameters); + } + + public function testThehandlerCanHandleTheRecordsFormattedUsingTheLineFormatter() + { + $handler = new StubNewRelicHandler(); + $handler->setFormatter(new LineFormatter()); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testTheAppNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, self::$appname); + } + + public function testTheAppNameCanBeInjectedFromtheConstructor() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myAppName', self::$appname); + } + + public function testTheAppNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('appname' => 'logAppName'))); + + $this->assertEquals('logAppName', self::$appname); + } + + public function testTheTransactionNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, self::$transactionName); + } + + public function testTheTransactionNameCanBeInjectedFromTheConstructor() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myTransaction', self::$transactionName); + } + + public function testTheTransactionNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('transaction_name' => 'logTransactName'))); + + $this->assertEquals('logTransactName', self::$transactionName); + } +} + +class StubNewRelicHandlerWithoutExtension extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return false; + } +} + +class StubNewRelicHandler extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return true; + } +} + +function newrelic_notice_error() +{ + return true; +} + +function newrelic_set_appname($appname) +{ + return NewRelicHandlerTest::$appname = $appname; +} + +function newrelic_name_transaction($transactionName) +{ + return NewRelicHandlerTest::$transactionName = $transactionName; +} + +function newrelic_add_custom_parameter($key, $value) +{ + NewRelicHandlerTest::$customParameters[$key] = $value; + + return true; +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php new file mode 100644 index 0000000..292df78 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\NullHandler::handle + */ +class NullHandlerTest extends TestCase +{ + public function testHandle() + { + $handler = new NullHandler(); + $this->assertTrue($handler->handle($this->getRecord())); + } + + public function testHandleLowerLevelRecord() + { + $handler = new NullHandler(Logger::WARNING); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php new file mode 100644 index 0000000..152573e --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\ErrorHandler; +use Monolog\Logger; +use Monolog\TestCase; +use PhpConsole\Connector; +use PhpConsole\Dispatcher\Debug as DebugDispatcher; +use PhpConsole\Dispatcher\Errors as ErrorDispatcher; +use PhpConsole\Handler; +use PHPUnit_Framework_MockObject_MockObject; + +/** + * @covers Monolog\Handler\PHPConsoleHandler + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandlerTest extends TestCase +{ + /** @var Connector|PHPUnit_Framework_MockObject_MockObject */ + protected $connector; + /** @var DebugDispatcher|PHPUnit_Framework_MockObject_MockObject */ + protected $debugDispatcher; + /** @var ErrorDispatcher|PHPUnit_Framework_MockObject_MockObject */ + protected $errorDispatcher; + + protected function setUp() + { + if (!class_exists('PhpConsole\Connector')) { + $this->markTestSkipped('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + $this->connector = $this->initConnectorMock(); + + $this->debugDispatcher = $this->initDebugDispatcherMock($this->connector); + $this->connector->setDebugDispatcher($this->debugDispatcher); + + $this->errorDispatcher = $this->initErrorDispatcherMock($this->connector); + $this->connector->setErrorsDispatcher($this->errorDispatcher); + } + + protected function initDebugDispatcherMock(Connector $connector) + { + return $this->getMockBuilder('PhpConsole\Dispatcher\Debug') + ->disableOriginalConstructor() + ->setMethods(array('dispatchDebug')) + ->setConstructorArgs(array($connector, $connector->getDumper())) + ->getMock(); + } + + protected function initErrorDispatcherMock(Connector $connector) + { + return $this->getMockBuilder('PhpConsole\Dispatcher\Errors') + ->disableOriginalConstructor() + ->setMethods(array('dispatchError', 'dispatchException')) + ->setConstructorArgs(array($connector, $connector->getDumper())) + ->getMock(); + } + + protected function initConnectorMock() + { + $connector = $this->getMockBuilder('PhpConsole\Connector') + ->disableOriginalConstructor() + ->setMethods(array( + 'sendMessage', + 'onShutDown', + 'isActiveClient', + 'setSourcesBasePath', + 'setServerEncoding', + 'setPassword', + 'enableSslOnlyMode', + 'setAllowedIpMasks', + 'setHeadersLimit', + 'startEvalRequestsListener', + )) + ->getMock(); + + $connector->expects($this->any()) + ->method('isActiveClient') + ->will($this->returnValue(true)); + + return $connector; + } + + protected function getHandlerDefaultOption($name) + { + $handler = new PHPConsoleHandler(array(), $this->connector); + $options = $handler->getOptions(); + + return $options[$name]; + } + + protected function initLogger($handlerOptions = array(), $level = Logger::DEBUG) + { + return new Logger('test', array( + new PHPConsoleHandler($handlerOptions, $this->connector, $level), + )); + } + + public function testInitWithDefaultConnector() + { + $handler = new PHPConsoleHandler(); + $this->assertEquals(spl_object_hash(Connector::getInstance()), spl_object_hash($handler->getConnector())); + } + + public function testInitWithCustomConnector() + { + $handler = new PHPConsoleHandler(array(), $this->connector); + $this->assertEquals(spl_object_hash($this->connector), spl_object_hash($handler->getConnector())); + } + + public function testDebug() + { + $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with($this->equalTo('test')); + $this->initLogger()->addDebug('test'); + } + + public function testDebugContextInMessage() + { + $message = 'test'; + $tag = 'tag'; + $context = array($tag, 'custom' => mt_rand()); + $expectedMessage = $message . ' ' . json_encode(array_slice($context, 1)); + $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with( + $this->equalTo($expectedMessage), + $this->equalTo($tag) + ); + $this->initLogger()->addDebug($message, $context); + } + + public function testDebugTags($tagsContextKeys = null) + { + $expectedTags = mt_rand(); + $logger = $this->initLogger($tagsContextKeys ? array('debugTagsKeysInContext' => $tagsContextKeys) : array()); + if (!$tagsContextKeys) { + $tagsContextKeys = $this->getHandlerDefaultOption('debugTagsKeysInContext'); + } + foreach ($tagsContextKeys as $key) { + $debugDispatcher = $this->initDebugDispatcherMock($this->connector); + $debugDispatcher->expects($this->once())->method('dispatchDebug')->with( + $this->anything(), + $this->equalTo($expectedTags) + ); + $this->connector->setDebugDispatcher($debugDispatcher); + $logger->addDebug('test', array($key => $expectedTags)); + } + } + + public function testError($classesPartialsTraceIgnore = null) + { + $code = E_USER_NOTICE; + $message = 'message'; + $file = __FILE__; + $line = __LINE__; + $this->errorDispatcher->expects($this->once())->method('dispatchError')->with( + $this->equalTo($code), + $this->equalTo($message), + $this->equalTo($file), + $this->equalTo($line), + $classesPartialsTraceIgnore ?: $this->equalTo($this->getHandlerDefaultOption('classesPartialsTraceIgnore')) + ); + $errorHandler = ErrorHandler::register($this->initLogger($classesPartialsTraceIgnore ? array('classesPartialsTraceIgnore' => $classesPartialsTraceIgnore) : array()), false); + $errorHandler->registerErrorHandler(array(), false, E_USER_WARNING); + $errorHandler->handleError($code, $message, $file, $line); + } + + public function testException() + { + $e = new Exception(); + $this->errorDispatcher->expects($this->once())->method('dispatchException')->with( + $this->equalTo($e) + ); + $handler = $this->initLogger(); + $handler->log( + \Psr\Log\LogLevel::ERROR, + sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + } + + /** + * @expectedException Exception + */ + public function testWrongOptionsThrowsException() + { + new PHPConsoleHandler(array('xxx' => 1)); + } + + public function testOptionEnabled() + { + $this->debugDispatcher->expects($this->never())->method('dispatchDebug'); + $this->initLogger(array('enabled' => false))->addDebug('test'); + } + + public function testOptionClassesPartialsTraceIgnore() + { + $this->testError(array('Class', 'Namespace\\')); + } + + public function testOptionDebugTagsKeysInContext() + { + $this->testDebugTags(array('key1', 'key2')); + } + + public function testOptionUseOwnErrorsAndExceptionsHandler() + { + $this->initLogger(array('useOwnErrorsHandler' => true, 'useOwnExceptionsHandler' => true)); + $this->assertEquals(array(Handler::getInstance(), 'handleError'), set_error_handler(function () { + })); + $this->assertEquals(array(Handler::getInstance(), 'handleException'), set_exception_handler(function () { + })); + } + + public static function provideConnectorMethodsOptionsSets() + { + return array( + array('sourcesBasePath', 'setSourcesBasePath', __DIR__), + array('serverEncoding', 'setServerEncoding', 'cp1251'), + array('password', 'setPassword', '******'), + array('enableSslOnlyMode', 'enableSslOnlyMode', true, false), + array('ipMasks', 'setAllowedIpMasks', array('127.0.0.*')), + array('headersLimit', 'setHeadersLimit', 2500), + array('enableEvalListener', 'startEvalRequestsListener', true, false), + ); + } + + /** + * @dataProvider provideConnectorMethodsOptionsSets + */ + public function testOptionCallsConnectorMethod($option, $method, $value, $isArgument = true) + { + $expectCall = $this->connector->expects($this->once())->method($method); + if ($isArgument) { + $expectCall->with($value); + } + new PHPConsoleHandler(array($option => $value), $this->connector); + } + + public function testOptionDetectDumpTraceAndSource() + { + new PHPConsoleHandler(array('detectDumpTraceAndSource' => true), $this->connector); + $this->assertTrue($this->connector->getDebugDispatcher()->detectTraceAndSource); + } + + public static function provideDumperOptionsValues() + { + return array( + array('dumperLevelLimit', 'levelLimit', 1001), + array('dumperItemsCountLimit', 'itemsCountLimit', 1002), + array('dumperItemSizeLimit', 'itemSizeLimit', 1003), + array('dumperDumpSizeLimit', 'dumpSizeLimit', 1004), + array('dumperDetectCallbacks', 'detectCallbacks', true), + ); + } + + /** + * @dataProvider provideDumperOptionsValues + */ + public function testDumperOptions($option, $dumperProperty, $value) + { + new PHPConsoleHandler(array($option => $value), $this->connector); + $this->assertEquals($value, $this->connector->getDumper()->$dumperProperty); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php new file mode 100644 index 0000000..64eaab1 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\PsrHandler::handle + */ +class PsrHandlerTest extends TestCase +{ + public function logLevelProvider() + { + $levels = array(); + $monologLogger = new Logger(''); + + foreach ($monologLogger->getLevels() as $levelName => $level) { + $levels[] = array($levelName, $level); + } + + return $levels; + } + + /** + * @dataProvider logLevelProvider + */ + public function testHandlesAllLevels($levelName, $level) + { + $message = 'Hello, world! ' . $level; + $context = array('foo' => 'bar', 'level' => $level); + + $psrLogger = $this->getMock('Psr\Log\NullLogger'); + $psrLogger->expects($this->once()) + ->method('log') + ->with(strtolower($levelName), $message, $context); + + $handler = new PsrHandler($psrLogger); + $handler->handle(array('level' => $level, 'level_name' => $levelName, 'message' => $message, 'context' => $context)); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php new file mode 100644 index 0000000..56df474 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * Almost all examples (expected header, titles, messages) taken from + * https://www.pushover.net/api + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandlerTest extends TestCase +{ + private $res; + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/1\/messages.json HTTP\/1.1\\r\\nHost: api.pushover.net\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}$/', $content); + } + + public function testWriteWithComplexTitle() + { + $this->createHandler('myToken', 'myUser', 'Backup finished - SQL1'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/title=Backup\+finished\+-\+SQL1/', $content); + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteWithTooLongMessage() + { + $message = str_pad('test', 520, 'a'); + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, $message)); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $expectedMessage = substr($message, 0, 505); + + $this->assertRegexp('/message=' . $expectedMessage . '&title/', $content); + } + + public function testWriteWithHighPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=1$/', $content); + } + + public function testWriteWithEmergencyPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + public function testWriteToMultipleUsers() + { + $this->createHandler('myToken', array('userA', 'userB')); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=userA&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200POST/', $content); + $this->assertRegexp('/token=myToken&user=userB&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog') + { + $constructorArgs = array($token, $user, $title); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\PushoverHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php new file mode 100644 index 0000000..26d212b --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RavenHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Raven_Client')) { + $this->markTestSkipped('raven/raven not installed'); + } + + require_once __DIR__ . '/MockRavenClient.php'; + } + + /** + * @covers Monolog\Handler\RavenHandler::__construct + */ + public function testConstruct() + { + $handler = new RavenHandler($this->getRavenClient()); + $this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler); + } + + protected function getHandler($ravenClient) + { + $handler = new RavenHandler($ravenClient); + + return $handler; + } + + protected function getRavenClient() + { + $dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1'; + + return new MockRavenClient($dsn); + } + + public function testDebug() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::DEBUG, 'A test debug message'); + $handler->handle($record); + + $this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testWarning() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::WARNING, 'A test warning message'); + $handler->handle($record); + + $this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testTag() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $tags = array(1, 2, 'foo'); + $record = $this->getRecord(Logger::INFO, 'test', array('tags' => $tags)); + $handler->handle($record); + + $this->assertEquals($tags, $ravenClient->lastData['tags']); + } + + public function testExtraParameters() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $checksum = '098f6bcd4621d373cade4e832627b4f6'; + $release = '05a671c66aefea124cc08b76ea6d30bb'; + $eventId = '31423'; + $record = $this->getRecord(Logger::INFO, 'test', array('checksum' => $checksum, 'release' => $release, 'event_id' => $eventId)); + $handler->handle($record); + + $this->assertEquals($checksum, $ravenClient->lastData['checksum']); + $this->assertEquals($release, $ravenClient->lastData['release']); + $this->assertEquals($eventId, $ravenClient->lastData['event_id']); + } + + public function testFingerprint() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $fingerprint = array('{{ default }}', 'other value'); + $record = $this->getRecord(Logger::INFO, 'test', array('fingerprint' => $fingerprint)); + $handler->handle($record); + + $this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']); + } + + public function testUserContext() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $recordWithNoContext = $this->getRecord(Logger::INFO, 'test with default user context'); + // set user context 'externally' + + $user = array( + 'id' => '123', + 'email' => 'test@test.com', + ); + + $recordWithContext = $this->getRecord(Logger::INFO, 'test', array('user' => $user)); + + $ravenClient->user_context(array('id' => 'test_user_id')); + // handle context + $handler->handle($recordWithContext); + $this->assertEquals($user, $ravenClient->lastData['user']); + + // check to see if its reset + $handler->handle($recordWithNoContext); + $this->assertInternalType('array', $ravenClient->context->user); + $this->assertSame('test_user_id', $ravenClient->context->user['id']); + + // handle with null context + $ravenClient->user_context(null); + $handler->handle($recordWithContext); + $this->assertEquals($user, $ravenClient->lastData['user']); + + // check to see if its reset + $handler->handle($recordWithNoContext); + $this->assertNull($ravenClient->context->user); + } + + public function testException() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + try { + $this->methodThatThrowsAnException(); + } catch (\Exception $e) { + $record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e)); + $handler->handle($record); + } + + $this->assertEquals($record['message'], $ravenClient->lastData['message']); + } + + public function testHandleBatch() + { + $records = $this->getMultipleRecords(); + $records[] = $this->getRecord(Logger::WARNING, 'warning'); + $records[] = $this->getRecord(Logger::WARNING, 'warning'); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) { + return $record['level'] == 400; + })); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testHandleBatchDoNothingIfRecordsAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMock('Monolog\Handler\RavenHandler', null, array($this->getRavenClient())); + $handler->expects($this->never())->method('handle'); + $handler->setLevel(Logger::ERROR); + $handler->handleBatch($records); + } + + public function testHandleBatchPicksProperMessage() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information 1'), + $this->getRecord(Logger::ERROR, 'error 1'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error 2'), + $this->getRecord(Logger::INFO, 'information 2'), + ); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) use ($records) { + return $record['message'] == 'error 1'; + })); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testGetSetBatchFormatter() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $handler->setBatchFormatter($formatter = new LineFormatter()); + $this->assertSame($formatter, $handler->getBatchFormatter()); + } + + public function testRelease() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + $release = 'v42.42.42'; + $handler->setRelease($release); + $record = $this->getRecord(Logger::INFO, 'test'); + $handler->handle($record); + $this->assertEquals($release, $ravenClient->lastData['release']); + + $localRelease = 'v41.41.41'; + $record = $this->getRecord(Logger::INFO, 'test', array('release' => $localRelease)); + $handler->handle($record); + $this->assertEquals($localRelease, $ravenClient->lastData['release']); + } + + private function methodThatThrowsAnException() + { + throw new \Exception('This is an exception'); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php new file mode 100644 index 0000000..689d527 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RedisHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidRedis() + { + new RedisHandler(new \stdClass(), 'key'); + } + + public function testConstructorShouldWorkWithPredis() + { + $redis = $this->getMock('Predis\Client'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testConstructorShouldWorkWithRedis() + { + $redis = $this->getMock('Redis'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testPredisHandle() + { + $redis = $this->getMock('Predis\Client', array('rpush')); + + // Predis\Client uses rpush + $redis->expects($this->once()) + ->method('rpush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandle() + { + $redis = $this->getMock('Redis', array('rpush')); + + // Redis uses rPush + $redis->expects($this->once()) + ->method('rPush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandleCapped() + { + $redis = $this->getMock('Redis', array('multi', 'rpush', 'ltrim', 'exec')); + + // Redis uses multi + $redis->expects($this->once()) + ->method('multi') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('rpush') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('ltrim') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('exec') + ->will($this->returnSelf()); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testPredisHandleCapped() + { + $redis = $this->getMock('Predis\Client', array('transaction')); + + $redisTransaction = $this->getMock('Predis\Client', array('rpush', 'ltrim')); + + $redisTransaction->expects($this->once()) + ->method('rpush') + ->will($this->returnSelf()); + + $redisTransaction->expects($this->once()) + ->method('ltrim') + ->will($this->returnSelf()); + + // Redis uses multi + $redis->expects($this->once()) + ->method('transaction') + ->will($this->returnCallback(function ($cb) use ($redisTransaction) { + $cb($redisTransaction); + })); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php new file mode 100644 index 0000000..f302e91 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\TestCase; +use Monolog\Logger; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * @author Erik Johansson + * @see https://rollbar.com/docs/notifier/rollbar-php/ + * + * @coversDefaultClass Monolog\Handler\RollbarHandler + */ +class RollbarHandlerTest extends TestCase +{ + /** + * @var MockObject + */ + private $rollbarNotifier; + + /** + * @var array + */ + public $reportedExceptionArguments = null; + + protected function setUp() + { + parent::setUp(); + + $this->setupRollbarNotifierMock(); + } + + /** + * When reporting exceptions to Rollbar the + * level has to be set in the payload data + */ + public function testExceptionLogLevel() + { + $handler = $this->createHandler(); + + $handler->handle($this->createExceptionRecord(Logger::DEBUG)); + + $this->assertEquals('debug', $this->reportedExceptionArguments['payload']['level']); + } + + private function setupRollbarNotifierMock() + { + $this->rollbarNotifier = $this->getMockBuilder('RollbarNotifier') + ->setMethods(array('report_message', 'report_exception', 'flush')) + ->getMock(); + + $that = $this; + + $this->rollbarNotifier + ->expects($this->any()) + ->method('report_exception') + ->willReturnCallback(function ($exception, $context, $payload) use ($that) { + $that->reportedExceptionArguments = compact('exception', 'context', 'payload'); + }); + } + + private function createHandler() + { + return new RollbarHandler($this->rollbarNotifier, Logger::DEBUG); + } + + private function createExceptionRecord($level = Logger::DEBUG, $message = 'test', $exception = null) + { + return $this->getRecord($level, $message, array( + 'exception' => $exception ?: new Exception() + )); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php new file mode 100644 index 0000000..c6f5fac --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php @@ -0,0 +1,245 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use PHPUnit_Framework_Error_Deprecated; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class RotatingFileHandlerTest extends TestCase +{ + /** + * This var should be private but then the anonymous function + * in the `setUp` method won't be able to set it. `$this` cant't + * be used in the anonymous function in `setUp` because PHP 5.3 + * does not support it. + */ + public $lastError; + + public function setUp() + { + $dir = __DIR__.'/Fixtures'; + chmod($dir, 0777); + if (!is_writable($dir)) { + $this->markTestSkipped($dir.' must be writable to test the RotatingFileHandler.'); + } + $this->lastError = null; + $self = $this; + // workaround with &$self used for PHP 5.3 + set_error_handler(function($code, $message) use (&$self) { + $self->lastError = array( + 'code' => $code, + 'message' => $message, + ); + }); + } + + private function assertErrorWasTriggered($code, $message) + { + if (empty($this->lastError)) { + $this->fail( + sprintf( + 'Failed asserting that error with code `%d` and message `%s` was triggered', + $code, + $message + ) + ); + } + $this->assertEquals($code, $this->lastError['code'], sprintf('Expected an error with code %d to be triggered, got `%s` instead', $code, $this->lastError['code'])); + $this->assertEquals($message, $this->lastError['message'], sprintf('Expected an error with message `%d` to be triggered, got `%s` instead', $message, $this->lastError['message'])); + } + + public function testRotationCreatesNewFile() + { + touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot'); + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + $this->assertTrue(file_exists($log)); + $this->assertEquals('test', file_get_contents($log)); + } + + /** + * @dataProvider rotationTests + */ + public function testRotation($createFile, $dateFormat, $timeCallback) + { + touch($old1 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-1)).'.rot'); + touch($old2 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-2)).'.rot'); + touch($old3 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-3)).'.rot'); + touch($old4 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-4)).'.rot'); + + $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot'; + + if ($createFile) { + touch($log); + } + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + $handler->handle($this->getRecord()); + + $handler->close(); + + $this->assertTrue(file_exists($log)); + $this->assertTrue(file_exists($old1)); + $this->assertEquals($createFile, file_exists($old2)); + $this->assertEquals($createFile, file_exists($old3)); + $this->assertEquals($createFile, file_exists($old4)); + $this->assertEquals('test', file_get_contents($log)); + } + + public function rotationTests() + { + $now = time(); + $dayCallback = function($ago) use ($now) { + return $now + 86400 * $ago; + }; + $monthCallback = function($ago) { + return gmmktime(0, 0, 0, date('n') + $ago, 1, date('Y')); + }; + $yearCallback = function($ago) { + return gmmktime(0, 0, 0, 1, 1, date('Y') + $ago); + }; + + return array( + 'Rotation is triggered when the file of the current day is not present' + => array(true, RotatingFileHandler::FILE_PER_DAY, $dayCallback), + 'Rotation is not triggered when the file of the current day is already present' + => array(false, RotatingFileHandler::FILE_PER_DAY, $dayCallback), + + 'Rotation is triggered when the file of the current month is not present' + => array(true, RotatingFileHandler::FILE_PER_MONTH, $monthCallback), + 'Rotation is not triggered when the file of the current month is already present' + => array(false, RotatingFileHandler::FILE_PER_MONTH, $monthCallback), + + 'Rotation is triggered when the file of the current year is not present' + => array(true, RotatingFileHandler::FILE_PER_YEAR, $yearCallback), + 'Rotation is not triggered when the file of the current year is already present' + => array(false, RotatingFileHandler::FILE_PER_YEAR, $yearCallback), + ); + } + + /** + * @dataProvider dateFormatProvider + */ + public function testAllowOnlyFixedDefinedDateFormats($dateFormat, $valid) + { + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + if (!$valid) { + $this->assertErrorWasTriggered( + E_USER_DEPRECATED, + 'Invalid date format - format must be one of RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), '. + 'RotatingFileHandler::FILE_PER_MONTH ("Y-m") or RotatingFileHandler::FILE_PER_YEAR ("Y"), '. + 'or you can set one of the date formats using slashes, underscores and/or dots instead of dashes.' + ); + } + } + + public function dateFormatProvider() + { + return array( + array(RotatingFileHandler::FILE_PER_DAY, true), + array(RotatingFileHandler::FILE_PER_MONTH, true), + array(RotatingFileHandler::FILE_PER_YEAR, true), + array('m-d-Y', false), + array('Y-m-d-h-i', false) + ); + } + + /** + * @dataProvider filenameFormatProvider + */ + public function testDisallowFilenameFormatsWithoutDate($filenameFormat, $valid) + { + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFilenameFormat($filenameFormat, RotatingFileHandler::FILE_PER_DAY); + if (!$valid) { + $this->assertErrorWasTriggered( + E_USER_DEPRECATED, + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.' + ); + } + } + + public function filenameFormatProvider() + { + return array( + array('{filename}', false), + array('{filename}-{date}', true), + array('{date}', true), + array('foobar-{date}', true), + array('foo-{date}-bar', true), + array('{date}-foobar', true), + array('foobar', false), + ); + } + + /** + * @dataProvider rotationWhenSimilarFilesExistTests + */ + public function testRotationWhenSimilarFileNamesExist($dateFormat) + { + touch($old1 = __DIR__.'/Fixtures/foo-foo-'.date($dateFormat).'.rot'); + touch($old2 = __DIR__.'/Fixtures/foo-bar-'.date($dateFormat).'.rot'); + + $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot'; + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + $handler->handle($this->getRecord()); + $handler->close(); + + $this->assertTrue(file_exists($log)); + } + + public function rotationWhenSimilarFilesExistTests() + { + + return array( + 'Rotation is triggered when the file of the current day is not present but similar exists' + => array(RotatingFileHandler::FILE_PER_DAY), + + 'Rotation is triggered when the file of the current month is not present but similar exists' + => array(RotatingFileHandler::FILE_PER_MONTH), + + 'Rotation is triggered when the file of the current year is not present but similar exists' + => array(RotatingFileHandler::FILE_PER_YEAR), + ); + } + + public function testReuseCurrentFile() + { + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + file_put_contents($log, "foo"); + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + $this->assertEquals('footest', file_get_contents($log)); + } + + public function tearDown() + { + foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) { + unlink($file); + } + restore_error_handler(); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php new file mode 100644 index 0000000..b354cee --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @covers Monolog\Handler\SamplingHandler::handle + */ +class SamplingHandlerTest extends TestCase +{ + public function testHandle() + { + $testHandler = new TestHandler(); + $handler = new SamplingHandler($testHandler, 2); + for ($i = 0; $i < 10000; $i++) { + $handler->handle($this->getRecord()); + } + $count = count($testHandler->getRecords()); + // $count should be half of 10k, so between 4k and 6k + $this->assertLessThan(6000, $count); + $this->assertGreaterThan(4000, $count); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php new file mode 100644 index 0000000..b9de736 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass Monolog\Handler\Slack\SlackRecord + */ +class SlackRecordTest extends TestCase +{ + private $jsonPrettyPrintFlag; + + protected function setUp() + { + $this->jsonPrettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + } + + public function dataGetAttachmentColor() + { + return array( + array(Logger::DEBUG, SlackRecord::COLOR_DEFAULT), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY, SlackRecord::COLOR_DANGER), + ); + } + + /** + * @dataProvider dataGetAttachmentColor + * @param int $logLevel + * @param string $expectedColour RGB hex color or name of Slack color + * @covers ::getAttachmentColor + */ + public function testGetAttachmentColor($logLevel, $expectedColour) + { + $slackRecord = new SlackRecord(); + $this->assertSame( + $expectedColour, + $slackRecord->getAttachmentColor($logLevel) + ); + } + + public function testAddsChannel() + { + $channel = '#test'; + $record = new SlackRecord($channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('channel', $data); + $this->assertSame($channel, $data['channel']); + } + + public function testNoUsernameByDefault() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('username', $data); + } + + /** + * @return array + */ + public function dataStringify() + { + $jsonPrettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + + $multipleDimensions = array(array(1, 2)); + $numericKeys = array('library' => 'monolog'); + $singleDimension = array(1, 'Hello', 'Jordi'); + + return array( + array(array(), '[]'), + array($multipleDimensions, json_encode($multipleDimensions, $jsonPrettyPrintFlag)), + array($numericKeys, json_encode($numericKeys, $jsonPrettyPrintFlag)), + array($singleDimension, json_encode($singleDimension)) + ); + } + + /** + * @dataProvider dataStringify + */ + public function testStringify($fields, $expectedResult) + { + $slackRecord = new SlackRecord( + '#test', + 'test', + true, + null, + true, + true + ); + + $this->assertSame($expectedResult, $slackRecord->stringify($fields)); + } + + public function testAddsCustomUsername() + { + $username = 'Monolog bot'; + $record = new SlackRecord(null, $username); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('username', $data); + $this->assertSame($username, $data['username']); + } + + public function testNoIcon() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('icon_emoji', $data); + } + + public function testAddsIcon() + { + $record = $this->getRecord(); + $slackRecord = new SlackRecord(null, null, false, 'ghost'); + $data = $slackRecord->getSlackData($record); + + $slackRecord2 = new SlackRecord(null, null, false, 'http://github.com/Seldaek/monolog'); + $data2 = $slackRecord2->getSlackData($record); + + $this->assertArrayHasKey('icon_emoji', $data); + $this->assertSame(':ghost:', $data['icon_emoji']); + $this->assertArrayHasKey('icon_url', $data2); + $this->assertSame('http://github.com/Seldaek/monolog', $data2['icon_url']); + } + + public function testAttachmentsNotPresentIfNoAttachment() + { + $record = new SlackRecord(null, null, false); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('attachments', $data); + } + + public function testAddsOneAttachment() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('attachments', $data); + $this->assertArrayHasKey(0, $data['attachments']); + $this->assertInternalType('array', $data['attachments'][0]); + } + + public function testTextEqualsMessageIfNoAttachment() + { + $message = 'Test message'; + $record = new SlackRecord(null, null, false); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message, $data['text']); + } + + public function testTextEqualsFormatterOutput() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test'; })); + + $formatter2 = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter2 + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test1'; })); + + $message = 'Test message'; + $record = new SlackRecord(null, null, false, null, false, false, array(), $formatter); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test', $data['text']); + + $record->setFormatter($formatter2); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test1', $data['text']); + } + + public function testAddsFallbackAndTextToAttachment() + { + $message = 'Test message'; + $record = new SlackRecord(null); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertSame($message, $data['attachments'][0]['text']); + $this->assertSame($message, $data['attachments'][0]['fallback']); + } + + public function testMapsLevelToColorAttachmentColor() + { + $record = new SlackRecord(null); + $errorLoggerRecord = $this->getRecord(Logger::ERROR); + $emergencyLoggerRecord = $this->getRecord(Logger::EMERGENCY); + $warningLoggerRecord = $this->getRecord(Logger::WARNING); + $infoLoggerRecord = $this->getRecord(Logger::INFO); + $debugLoggerRecord = $this->getRecord(Logger::DEBUG); + + $data = $record->getSlackData($errorLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($emergencyLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($warningLoggerRecord); + $this->assertSame(SlackRecord::COLOR_WARNING, $data['attachments'][0]['color']); + + $data = $record->getSlackData($infoLoggerRecord); + $this->assertSame(SlackRecord::COLOR_GOOD, $data['attachments'][0]['color']); + + $data = $record->getSlackData($debugLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DEFAULT, $data['attachments'][0]['color']); + } + + public function testAddsShortAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord(null, null, true, null, true); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame(array(), $attachment['fields']); + } + + public function testAddsShortAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $context = array('test' => 1); + $extra = array('tags' => array('web')); + $record = new SlackRecord(null, null, true, null, true, true); + $loggerRecord = $this->getRecord($level, 'test', $context); + $loggerRecord['extra'] = $extra; + $data = $record->getSlackData($loggerRecord); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(2, $attachment['fields']); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame( + array( + array( + 'title' => 'Extra', + 'value' => sprintf('```%s```', json_encode($extra, $this->jsonPrettyPrintFlag)), + 'short' => false + ), + array( + 'title' => 'Context', + 'value' => sprintf('```%s```', json_encode($context, $this->jsonPrettyPrintFlag)), + 'short' => false + ) + ), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord(null, null, true, null); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(1, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + array(array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => false + )), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $context = array('test' => 1); + $extra = array('tags' => array('web')); + $record = new SlackRecord(null, null, true, null, false, true); + $loggerRecord = $this->getRecord($level, 'test', $context); + $loggerRecord['extra'] = $extra; + $data = $record->getSlackData($loggerRecord); + + $expectedFields = array( + array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => false, + ), + array( + 'title' => 'Tags', + 'value' => sprintf('```%s```', json_encode($extra['tags'])), + 'short' => false + ), + array( + 'title' => 'Test', + 'value' => $context['test'], + 'short' => false + ) + ); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(3, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + $expectedFields, + $attachment['fields'] + ); + } + + public function testAddsTimestampToAttachment() + { + $record = $this->getRecord(); + $slackRecord = new SlackRecord(); + $data = $slackRecord->getSlackData($this->getRecord()); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('ts', $attachment); + $this->assertSame($record['datetime']->getTimestamp(), $attachment['ts']); + } + + public function testContextHasException() + { + $record = $this->getRecord(Logger::CRITICAL, 'This is a critical message.', array('exception' => new \Exception())); + $slackRecord = new SlackRecord(null, null, true, null, false, true); + $data = $slackRecord->getSlackData($record); + $this->assertInternalType('string', $data['attachments'][0]['fields'][1]['value']); + } + + public function testExcludeExtraAndContextFields() + { + $record = $this->getRecord( + Logger::WARNING, + 'test', + array('info' => array('library' => 'monolog', 'author' => 'Jordi')) + ); + $record['extra'] = array('tags' => array('web', 'cli')); + + $slackRecord = new SlackRecord(null, null, true, null, false, true, array('context.info.library', 'extra.tags.1')); + $data = $slackRecord->getSlackData($record); + $attachment = $data['attachments'][0]; + + $expected = array( + array( + 'title' => 'Info', + 'value' => sprintf('```%s```', json_encode(array('author' => 'Jordi'), $this->jsonPrettyPrintFlag)), + 'short' => false + ), + array( + 'title' => 'Tags', + 'value' => sprintf('```%s```', json_encode(array('web'))), + 'short' => false + ), + ); + + foreach ($expected as $field) { + $this->assertNotFalse(array_search($field, $attachment['fields'])); + break; + } + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php new file mode 100644 index 0000000..b12b01f --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var SlackHandler + */ + private $handler; + + public function setUp() + { + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl to run'); + } + } + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/api\/chat.postMessage HTTP\/1.1\\r\\nHost: slack.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + } + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegExp('/username=Monolog/', $content); + $this->assertRegExp('/channel=channel1/', $content); + $this->assertRegExp('/token=myToken/', $content); + $this->assertRegExp('/attachments/', $content); + } + + public function testWriteContentUsesFormatterIfProvided() + { + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->setFormatter(new LineFormatter('foo--%message%')); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test2')); + fseek($this->res, 0); + $content2 = fread($this->res, 1024); + + $this->assertRegexp('/text=test1/', $content); + $this->assertRegexp('/text=foo--test2/', $content2); + } + + public function testWriteContentWithEmoji() + { + $this->createHandler('myToken', 'channel1', 'Monolog', true, 'alien'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/icon_emoji=%3Aalien%3A/', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteContentWithColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/%22color%22%3A%22'.$expectedColor.'/', $content); + } + + public function testWriteContentWithPlainTextMessage() + { + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/text=test1/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, urlencode(SlackRecord::COLOR_DEFAULT)), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY,SlackRecord::COLOR_DANGER), + ); + } + + private function createHandler($token = 'myToken', $channel = 'channel1', $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeExtra = false) + { + $constructorArgs = array($token, $channel, $username, $useAttachment, $iconEmoji, Logger::DEBUG, true, $useShortAttachment, $includeExtra); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\SlackHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php new file mode 100644 index 0000000..c9229e2 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @coversDefaultClass Monolog\Handler\SlackWebhookHandler + */ +class SlackWebhookHandlerTest extends TestCase +{ + const WEBHOOK_URL = 'https://hooks.slack.com/services/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E'; + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorMinimal() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $record = $this->getRecord(); + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'attachments' => array( + array( + 'fallback' => 'test', + 'text' => 'test', + 'color' => SlackRecord::COLOR_WARNING, + 'fields' => array( + array( + 'title' => 'Level', + 'value' => 'WARNING', + 'short' => false, + ), + ), + 'title' => 'Message', + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp(), + ), + ), + ), $slackRecord->getSlackData($record)); + } + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorFull() + { + $handler = new SlackWebhookHandler( + self::WEBHOOK_URL, + 'test-channel', + 'test-username', + false, + ':ghost:', + false, + false, + Logger::DEBUG, + false + ); + + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'username' => 'test-username', + 'text' => 'test', + 'channel' => 'test-channel', + 'icon_emoji' => ':ghost:', + ), $slackRecord->getSlackData($this->getRecord())); + } + + /** + * @covers ::getFormatter + */ + public function testGetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = $handler->getFormatter(); + $this->assertInstanceOf('Monolog\Formatter\FormatterInterface', $formatter); + } + + /** + * @covers ::setFormatter + */ + public function testSetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = new LineFormatter(); + $handler->setFormatter($formatter); + $this->assertSame($formatter, $handler->getFormatter()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php new file mode 100644 index 0000000..b1b02bd --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + * @coversDefaultClass Monolog\Handler\SlackbotHandler + */ +class SlackbotHandlerTest extends TestCase +{ + /** + * @covers ::__construct + */ + public function testConstructorMinimal() + { + $handler = new SlackbotHandler('test-team', 'test-token', 'test-channel'); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } + + /** + * @covers ::__construct + */ + public function testConstructorFull() + { + $handler = new SlackbotHandler( + 'test-team', + 'test-token', + 'test-channel', + Logger::DEBUG, + false + ); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php new file mode 100644 index 0000000..1da987c --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php @@ -0,0 +1,335 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Pablo de Leon Belloc + */ +class SocketHandlerTest extends TestCase +{ + /** + * @var Monolog\Handler\SocketHandler + */ + private $handler; + + /** + * @var resource + */ + private $res; + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidHostname() + { + $this->createHandler('garbage://here'); + $this->writeRecord('data'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(-1); + } + + public function testSetConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(10.1); + $this->assertEquals(10.1, $this->handler->getConnectionTimeout()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(-1); + } + + public function testSetTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getTimeout()); + } + + public function testSetWritingTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setWritingTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getWritingTimeout()); + } + + public function testSetChunkSize() + { + $this->createHandler('localhost:1234'); + $this->handler->setChunkSize(1025); + $this->assertEquals(1025, $this->handler->getChunkSize()); + } + + public function testSetConnectionString() + { + $this->createHandler('tcp://localhost:9090'); + $this->assertEquals('tcp://localhost:9090', $this->handler->getConnectionString()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnFsockopenError() + { + $this->setMockHandler(array('fsockopen')); + $this->handler->expects($this->once()) + ->method('fsockopen') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnPfsockopenError() + { + $this->setMockHandler(array('pfsockopen')); + $this->handler->expects($this->once()) + ->method('pfsockopen') + ->will($this->returnValue(false)); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownIfCannotSetTimeout() + { + $this->setMockHandler(array('streamSetTimeout')); + $this->handler->expects($this->once()) + ->method('streamSetTimeout') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownIfCannotSetChunkSize() + { + $this->setMockHandler(array('streamSetChunkSize')); + $this->handler->setChunkSize(8192); + $this->handler->expects($this->once()) + ->method('streamSetChunkSize') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIfFwriteReturnsFalse() + { + $this->setMockHandler(array('fwrite')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => false, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsIfStreamTimesOut() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => true))); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIncompleteWrite() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $res = $this->res; + $callback = function ($string) use ($res) { + fclose($res); + + return strlen('Hello'); + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->writeRecord('Hello world'); + } + + public function testWriteWithMemoryFile() + { + $this->setMockHandler(); + $this->writeRecord('test1'); + $this->writeRecord('test2'); + $this->writeRecord('test3'); + fseek($this->res, 0); + $this->assertEquals('test1test2test3', fread($this->res, 1024)); + } + + public function testWriteWithMock() + { + $this->setMockHandler(array('fwrite')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + public function testClose() + { + $this->setMockHandler(); + $this->writeRecord('Hello world'); + $this->assertInternalType('resource', $this->res); + $this->handler->close(); + $this->assertFalse(is_resource($this->res), "Expected resource to be closed after closing handler"); + } + + public function testCloseDoesNotClosePersistentSocket() + { + $this->setMockHandler(); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + $this->assertTrue(is_resource($this->res)); + $this->handler->close(); + $this->assertTrue(is_resource($this->res)); + } + + /** + * @expectedException \RuntimeException + */ + public function testAvoidInfiniteLoopWhenNoDataIsWrittenForAWritingTimeoutSeconds() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $this->handler->expects($this->any()) + ->method('fwrite') + ->will($this->returnValue(0)); + + $this->handler->expects($this->any()) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->handler->setWritingTimeout(1); + + $this->writeRecord('Hello world'); + } + + private function createHandler($connectionString) + { + $this->handler = new SocketHandler($connectionString); + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + private function writeRecord($string) + { + $this->handler->handle($this->getRecord(Logger::WARNING, $string)); + } + + private function setMockHandler(array $methods = array()) + { + $this->res = fopen('php://memory', 'a'); + + $defaultMethods = array('fsockopen', 'pfsockopen', 'streamSetTimeout'); + $newMethods = array_diff($methods, $defaultMethods); + + $finalMethods = array_merge($defaultMethods, $newMethods); + + $this->handler = $this->getMock( + '\Monolog\Handler\SocketHandler', $finalMethods, array('localhost:1234') + ); + + if (!in_array('fsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('pfsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('pfsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('streamSetTimeout', $methods)) { + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + } + + if (!in_array('streamSetChunkSize', $methods)) { + $this->handler->expects($this->any()) + ->method('streamSetChunkSize') + ->will($this->returnValue(8192)); + } + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php new file mode 100644 index 0000000..487030f --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class StreamHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWrite() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $handler->handle($this->getRecord(Logger::WARNING, 'test2')); + $handler->handle($this->getRecord(Logger::WARNING, 'test3')); + fseek($handle, 0); + $this->assertEquals('testtest2test3', fread($handle, 100)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testCloseKeepsExternalHandlersOpen() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertTrue(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testClose() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $streamProp = new \ReflectionProperty('Monolog\Handler\StreamHandler', 'stream'); + $streamProp->setAccessible(true); + $handle = $streamProp->getValue($handler); + + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertFalse(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteCreatesTheStreamResource() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteLocking() + { + $temp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'monolog_locked_log'; + $handler = new StreamHandler($temp, Logger::DEBUG, true, null, true); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException LogicException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteMissingResource() + { + $handler = new StreamHandler(null); + $handler->handle($this->getRecord()); + } + + public function invalidArgumentProvider() + { + return array( + array(1), + array(array()), + array(array('bogus://url')), + ); + } + + /** + * @dataProvider invalidArgumentProvider + * @expectedException InvalidArgumentException + * @covers Monolog\Handler\StreamHandler::__construct + */ + public function testWriteInvalidArgument($invalidArgument) + { + $handler = new StreamHandler($invalidArgument); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteInvalidResource() + { + $handler = new StreamHandler('bogus://url'); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingResource() + { + $handler = new StreamHandler('ftp://foo/bar/baz/'.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingPath() + { + $handler = new StreamHandler(sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingFileResource() + { + $handler = new StreamHandler('file://'.sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessageRegExp /There is no existing directory at/ + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingAndNotCreatablePath() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Permissions checks can not run on windows'); + } + $handler = new StreamHandler('/foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessageRegExp /There is no existing directory at/ + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingAndNotCreatableFileResource() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Permissions checks can not run on windows'); + } + $handler = new StreamHandler('file:///foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php new file mode 100644 index 0000000..1d62940 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class SwiftMailerHandlerTest extends TestCase +{ + /** @var \Swift_Mailer|\PHPUnit_Framework_MockObject_MockObject */ + private $mailer; + + public function setUp() + { + $this->mailer = $this + ->getMockBuilder('Swift_Mailer') + ->disableOriginalConstructor() + ->getMock(); + } + + public function testMessageCreationIsLazyWhenUsingCallback() + { + $this->mailer->expects($this->never()) + ->method('send'); + + $callback = function () { + throw new \RuntimeException('Swift_Message creation callback should not have been called in this test'); + }; + $handler = new SwiftMailerHandler($this->mailer, $callback); + + $records = array( + $this->getRecord(Logger::DEBUG), + $this->getRecord(Logger::INFO), + ); + $handler->handleBatch($records); + } + + public function testMessageCanBeCustomizedGivenLoggedData() + { + // Wire Mailer to expect a specific Swift_Message with a customized Subject + $expectedMessage = new \Swift_Message(); + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->callback(function ($value) use ($expectedMessage) { + return $value instanceof \Swift_Message + && $value->getSubject() === 'Emergency' + && $value === $expectedMessage; + })); + + // Callback dynamically changes subject based on number of logged records + $callback = function ($content, array $records) use ($expectedMessage) { + $subject = count($records) > 0 ? 'Emergency' : 'Normal'; + $expectedMessage->setSubject($subject); + + return $expectedMessage; + }; + $handler = new SwiftMailerHandler($this->mailer, $callback); + + // Logging 1 record makes this an Emergency + $records = array( + $this->getRecord(Logger::EMERGENCY), + ); + $handler->handleBatch($records); + } + + public function testMessageSubjectFormatting() + { + // Wire Mailer to expect a specific Swift_Message with a customized Subject + $messageTemplate = new \Swift_Message(); + $messageTemplate->setSubject('Alert: %level_name% %message%'); + $receivedMessage = null; + + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->callback(function ($value) use (&$receivedMessage) { + $receivedMessage = $value; + return true; + })); + + $handler = new SwiftMailerHandler($this->mailer, $messageTemplate); + + $records = array( + $this->getRecord(Logger::EMERGENCY), + ); + $handler->handleBatch($records); + + $this->assertEquals('Alert: EMERGENCY test', $receivedMessage->getSubject()); + } + + public function testMessageHaveUniqueId() + { + $messageTemplate = new \Swift_Message(); + $handler = new SwiftMailerHandler($this->mailer, $messageTemplate); + + $method = new \ReflectionMethod('Monolog\Handler\SwiftMailerHandler', 'buildMessage'); + $method->setAccessible(true); + $method->invokeArgs($handler, array($messageTemplate, array())); + + $builtMessage1 = $method->invoke($handler, $messageTemplate, array()); + $builtMessage2 = $method->invoke($handler, $messageTemplate, array()); + + $this->assertFalse($builtMessage1->getId() === $builtMessage2->getId(), 'Two different messages have the same id'); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php new file mode 100644 index 0000000..8f9e46b --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +class SyslogHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstruct() + { + $handler = new SyslogHandler('test'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', 'user'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + } + + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstructInvalidFacility() + { + $this->setExpectedException('UnexpectedValueException'); + $handler = new SyslogHandler('test', 'unknown'); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php new file mode 100644 index 0000000..7ee8a98 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @requires extension sockets + */ +class SyslogUdpHandlerTest extends TestCase +{ + /** + * @expectedException UnexpectedValueException + */ + public function testWeValidateFacilities() + { + $handler = new SyslogUdpHandler("ip", null, "invalidFacility"); + } + + public function testWeSplitIntoLines() + { + $time = '2014-01-07T12:34'; + $pid = getmypid(); + $host = gethostname(); + + $handler = $this->getMockBuilder('\Monolog\Handler\SyslogUdpHandler') + ->setConstructorArgs(array("127.0.0.1", 514, "authpriv")) + ->setMethods(array('getDateTime')) + ->getMock(); + + $handler->method('getDateTime') + ->willReturn($time); + + $handler->setFormatter(new \Monolog\Formatter\ChromePHPFormatter()); + + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol')); + $socket->expects($this->at(0)) + ->method('write') + ->with("lol", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 $time $host php $pid - - "); + $socket->expects($this->at(1)) + ->method('write') + ->with("hej", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 $time $host php $pid - - "); + + $handler->setSocket($socket); + + $handler->handle($this->getRecordWithMessage("hej\nlol")); + } + + public function testSplitWorksOnEmptyMsg() + { + $handler = new SyslogUdpHandler("127.0.0.1", 514, "authpriv"); + $handler->setFormatter($this->getIdentityFormatter()); + + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol')); + $socket->expects($this->never()) + ->method('write'); + + $handler->setSocket($socket); + + $handler->handle($this->getRecordWithMessage(null)); + } + + protected function getRecordWithMessage($msg) + { + return array('message' => $msg, 'level' => \Monolog\Logger::WARNING, 'context' => null, 'extra' => array(), 'channel' => 'lol'); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php new file mode 100644 index 0000000..a7c4fc9 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\TestHandler + */ +class TestHandlerTest extends TestCase +{ + /** + * @dataProvider methodProvider + */ + public function testHandler($method, $level) + { + $handler = new TestHandler; + $record = $this->getRecord($level, 'test'.$method); + $this->assertFalse($handler->hasRecords($level)); + $this->assertFalse($handler->hasRecord($record, $level)); + $this->assertFalse($handler->{'has'.$method}($record), 'has'.$method); + $this->assertFalse($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains'); + $this->assertFalse($handler->{'has'.$method.'ThatPasses'}(function ($rec) { + return true; + }), 'has'.$method.'ThatPasses'); + $this->assertFalse($handler->{'has'.$method.'ThatMatches'}('/test\w+/')); + $this->assertFalse($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records'); + $handler->handle($record); + + $this->assertFalse($handler->{'has'.$method}('bar'), 'has'.$method); + $this->assertTrue($handler->hasRecords($level)); + $this->assertTrue($handler->hasRecord($record, $level)); + $this->assertTrue($handler->{'has'.$method}($record), 'has'.$method); + $this->assertTrue($handler->{'has'.$method}('test'.$method), 'has'.$method); + $this->assertTrue($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains'); + $this->assertTrue($handler->{'has'.$method.'ThatPasses'}(function ($rec) { + return true; + }), 'has'.$method.'ThatPasses'); + $this->assertTrue($handler->{'has'.$method.'ThatMatches'}('/test\w+/')); + $this->assertTrue($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records'); + + $records = $handler->getRecords(); + unset($records[0]['formatted']); + $this->assertEquals(array($record), $records); + } + + public function testHandlerAssertEmptyContext() { + $handler = new TestHandler; + $record = $this->getRecord(Logger::WARNING, 'test', array()); + $this->assertFalse($handler->hasWarning(array( + 'message' => 'test', + 'context' => array(), + ))); + + $handler->handle($record); + + $this->assertTrue($handler->hasWarning(array( + 'message' => 'test', + 'context' => array(), + ))); + $this->assertFalse($handler->hasWarning(array( + 'message' => 'test', + 'context' => array( + 'foo' => 'bar' + ), + ))); + } + + public function testHandlerAssertNonEmptyContext() { + $handler = new TestHandler; + $record = $this->getRecord(Logger::WARNING, 'test', array('foo' => 'bar')); + $this->assertFalse($handler->hasWarning(array( + 'message' => 'test', + 'context' => array( + 'foo' => 'bar' + ), + ))); + + $handler->handle($record); + + $this->assertTrue($handler->hasWarning(array( + 'message' => 'test', + 'context' => array( + 'foo' => 'bar' + ), + ))); + $this->assertFalse($handler->hasWarning(array( + 'message' => 'test', + 'context' => array(), + ))); + } + + public function methodProvider() + { + return array( + array('Emergency', Logger::EMERGENCY), + array('Alert' , Logger::ALERT), + array('Critical' , Logger::CRITICAL), + array('Error' , Logger::ERROR), + array('Warning' , Logger::WARNING), + array('Info' , Logger::INFO), + array('Notice' , Logger::NOTICE), + array('Debug' , Logger::DEBUG), + ); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php new file mode 100644 index 0000000..fa524d0 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * @requires extension sockets + */ +class UdpSocketTest extends TestCase +{ + public function testWeDoNotTruncateShortMessages() + { + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol')); + + $socket->expects($this->at(0)) + ->method('send') + ->with("HEADER: The quick brown fox jumps over the lazy dog"); + + $socket->write("The quick brown fox jumps over the lazy dog", "HEADER: "); + } + + public function testLongMessagesAreTruncated() + { + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol')); + + $truncatedString = str_repeat("derp", 16254).'d'; + + $socket->expects($this->exactly(1)) + ->method('send') + ->with("HEADER" . $truncatedString); + + $longString = str_repeat("derp", 20000); + + $socket->write($longString, "HEADER"); + } + + public function testDoubleCloseDoesNotError() + { + $socket = new UdpSocket('127.0.0.1', 514); + $socket->close(); + $socket->close(); + } + + /** + * @expectedException LogicException + */ + public function testWriteAfterCloseErrors() + { + $socket = new UdpSocket('127.0.0.1', 514); + $socket->close(); + $socket->write('foo', "HEADER"); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php new file mode 100644 index 0000000..0594a23 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class WhatFailureGroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new WhatFailureGroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::__construct + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new WhatFailureGroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new WhatFailureGroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch + */ + public function testHandleBatchUsesProcessors() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + $this->assertTrue($records[1]['extra']['foo']); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandleException() + { + $test = new TestHandler(); + $exception = new ExceptionTestHandler(); + $handler = new WhatFailureGroupHandler(array($exception, $test, $exception)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} + +class ExceptionTestHandler extends TestHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + parent::handle($record); + + throw new \Exception("ExceptionTestHandler::handle"); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php new file mode 100644 index 0000000..69b001e --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +class ZendMonitorHandlerTest extends TestCase +{ + protected $zendMonitorHandler; + + public function setUp() + { + if (!function_exists('zend_monitor_custom_event')) { + $this->markTestSkipped('ZendServer is not installed'); + } + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::write + */ + public function testWrite() + { + $record = $this->getRecord(); + $formatterResult = array( + 'message' => $record['message'], + ); + + $zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler') + ->setMethods(array('writeZendMonitorCustomEvent', 'getDefaultFormatter')) + ->getMock(); + + $formatterMock = $this->getMockBuilder('Monolog\Formatter\NormalizerFormatter') + ->disableOriginalConstructor() + ->getMock(); + + $formatterMock->expects($this->once()) + ->method('format') + ->will($this->returnValue($formatterResult)); + + $zendMonitor->expects($this->once()) + ->method('getDefaultFormatter') + ->will($this->returnValue($formatterMock)); + + $levelMap = $zendMonitor->getLevelMap(); + + $zendMonitor->expects($this->once()) + ->method('writeZendMonitorCustomEvent') + ->with($levelMap[$record['level']], $record['message'], $formatterResult); + + $zendMonitor->handle($record); + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::getDefaultFormatter + */ + public function testGetDefaultFormatterReturnsNormalizerFormatter() + { + $zendMonitor = new ZendMonitorHandler(); + $this->assertInstanceOf('Monolog\Formatter\NormalizerFormatter', $zendMonitor->getDefaultFormatter()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/LoggerTest.php b/core/vendor/monolog/monolog/tests/Monolog/LoggerTest.php new file mode 100644 index 0000000..442e87d --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/LoggerTest.php @@ -0,0 +1,690 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Processor\WebProcessor; +use Monolog\Handler\TestHandler; + +class LoggerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Logger::getName + */ + public function testGetName() + { + $logger = new Logger('foo'); + $this->assertEquals('foo', $logger->getName()); + } + + /** + * @covers Monolog\Logger::getLevelName + */ + public function testGetLevelName() + { + $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR)); + } + + /** + * @covers Monolog\Logger::withName + */ + public function testWithName() + { + $first = new Logger('first', array($handler = new TestHandler())); + $second = $first->withName('second'); + + $this->assertSame('first', $first->getName()); + $this->assertSame('second', $second->getName()); + $this->assertSame($handler, $second->popHandler()); + } + + /** + * @covers Monolog\Logger::toMonologLevel + */ + public function testConvertPSR3ToMonologLevel() + { + $this->assertEquals(Logger::toMonologLevel('debug'), 100); + $this->assertEquals(Logger::toMonologLevel('info'), 200); + $this->assertEquals(Logger::toMonologLevel('notice'), 250); + $this->assertEquals(Logger::toMonologLevel('warning'), 300); + $this->assertEquals(Logger::toMonologLevel('error'), 400); + $this->assertEquals(Logger::toMonologLevel('critical'), 500); + $this->assertEquals(Logger::toMonologLevel('alert'), 550); + $this->assertEquals(Logger::toMonologLevel('emergency'), 600); + } + + /** + * @covers Monolog\Logger::getLevelName + * @expectedException InvalidArgumentException + */ + public function testGetLevelNameThrows() + { + Logger::getLevelName(5); + } + + /** + * @covers Monolog\Logger::__construct + */ + public function testChannel() + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->addWarning('test'); + list($record) = $handler->getRecords(); + $this->assertEquals('foo', $record['channel']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLog() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle')); + $handler->expects($this->once()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->addWarning('test')); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLogNotHandled() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'), array(Logger::ERROR)); + $handler->expects($this->never()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertFalse($logger->addWarning('test')); + } + + public function testHandlersInCtor() + { + $handler1 = new TestHandler; + $handler2 = new TestHandler; + $logger = new Logger(__METHOD__, array($handler1, $handler2)); + + $this->assertEquals($handler1, $logger->popHandler()); + $this->assertEquals($handler2, $logger->popHandler()); + } + + public function testProcessorsInCtor() + { + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + $logger = new Logger(__METHOD__, array(), array($processor1, $processor2)); + + $this->assertEquals($processor1, $logger->popProcessor()); + $this->assertEquals($processor2, $logger->popProcessor()); + } + + /** + * @covers Monolog\Logger::pushHandler + * @covers Monolog\Logger::popHandler + * @expectedException LogicException + */ + public function testPushPopHandler() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->pushHandler($handler2); + + $this->assertEquals($handler2, $logger->popHandler()); + $this->assertEquals($handler1, $logger->popHandler()); + $logger->popHandler(); + } + + /** + * @covers Monolog\Logger::setHandlers + */ + public function testSetHandlers() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->setHandlers(array($handler2)); + + // handler1 has been removed + $this->assertEquals(array($handler2), $logger->getHandlers()); + + $logger->setHandlers(array( + "AMapKey" => $handler1, + "Woop" => $handler2, + )); + + // Keys have been scrubbed + $this->assertEquals(array($handler1, $handler2), $logger->getHandlers()); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @covers Monolog\Logger::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = new Logger(__METHOD__); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $logger = new Logger(__METHOD__); + + $logger->pushProcessor(new \stdClass()); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreExecuted() + { + $logger = new Logger(__METHOD__); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->pushProcessor(function ($record) { + $record['extra']['win'] = true; + + return $record; + }); + $logger->addError('test'); + list($record) = $handler->getRecords(); + $this->assertTrue($record['extra']['win']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreCalledOnlyOnce() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler); + + $processor = $this->getMockBuilder('Monolog\Processor\WebProcessor') + ->disableOriginalConstructor() + ->setMethods(array('__invoke')) + ->getMock() + ; + $processor->expects($this->once()) + ->method('__invoke') + ->will($this->returnArgument(0)) + ; + $logger->pushProcessor($processor); + + $logger->addError('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsNotCalledWhenNotHandled() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler); + $that = $this; + $logger->pushProcessor(function ($record) use ($that) { + $that->fail('The processor should not be called'); + }); + $logger->addAlert('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler3); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandlingWithAssocArray() + { + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + + $logger = new Logger(__METHOD__, array('last' => $handler3, 'second' => $handler2, 'first' => $handler1)); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testBubblingWhenTheHandlerReturnsFalse() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testNotBubblingWhenTheHandlerReturnsTrue() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::isHandling + */ + public function testIsHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + + $logger->pushHandler($handler1); + $this->assertFalse($logger->isHandling(Logger::DEBUG)); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + + $logger->pushHandler($handler2); + $this->assertTrue($logger->isHandling(Logger::DEBUG)); + } + + /** + * @dataProvider logMethodProvider + * @covers Monolog\Logger::addDebug + * @covers Monolog\Logger::addInfo + * @covers Monolog\Logger::addNotice + * @covers Monolog\Logger::addWarning + * @covers Monolog\Logger::addError + * @covers Monolog\Logger::addCritical + * @covers Monolog\Logger::addAlert + * @covers Monolog\Logger::addEmergency + * @covers Monolog\Logger::debug + * @covers Monolog\Logger::info + * @covers Monolog\Logger::notice + * @covers Monolog\Logger::warn + * @covers Monolog\Logger::err + * @covers Monolog\Logger::crit + * @covers Monolog\Logger::alert + * @covers Monolog\Logger::emerg + */ + public function testLogMethods($method, $expectedLevel) + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->{$method}('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($expectedLevel, $record['level']); + } + + public function logMethodProvider() + { + return array( + // monolog methods + array('addDebug', Logger::DEBUG), + array('addInfo', Logger::INFO), + array('addNotice', Logger::NOTICE), + array('addWarning', Logger::WARNING), + array('addError', Logger::ERROR), + array('addCritical', Logger::CRITICAL), + array('addAlert', Logger::ALERT), + array('addEmergency', Logger::EMERGENCY), + + // ZF/Sf2 compat methods + array('debug', Logger::DEBUG), + array('info', Logger::INFO), + array('notice', Logger::NOTICE), + array('warn', Logger::WARNING), + array('err', Logger::ERROR), + array('crit', Logger::CRITICAL), + array('alert', Logger::ALERT), + array('emerg', Logger::EMERGENCY), + ); + } + + /** + * @dataProvider setTimezoneProvider + * @covers Monolog\Logger::setTimezone + */ + public function testSetTimezone($tz) + { + Logger::setTimezone($tz); + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->info('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($tz, $record['datetime']->getTimezone()); + } + + public function setTimezoneProvider() + { + return array_map( + function ($tz) { return array(new \DateTimeZone($tz)); }, + \DateTimeZone::listIdentifiers() + ); + } + + /** + * @dataProvider useMicrosecondTimestampsProvider + * @covers Monolog\Logger::useMicrosecondTimestamps + * @covers Monolog\Logger::addRecord + */ + public function testUseMicrosecondTimestamps($micro, $assert) + { + $logger = new Logger('foo'); + $logger->useMicrosecondTimestamps($micro); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->info('test'); + list($record) = $handler->getRecords(); + $this->{$assert}('000000', $record['datetime']->format('u')); + } + + public function useMicrosecondTimestampsProvider() + { + return array( + // this has a very small chance of a false negative (1/10^6) + 'with microseconds' => array(true, 'assertNotSame'), + 'without microseconds' => array(false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame'), + ); + } + + /** + * @covers Monolog\Logger::setExceptionHandler + */ + public function testSetExceptionHandler() + { + $logger = new Logger(__METHOD__); + $this->assertNull($logger->getExceptionHandler()); + $callback = function ($ex) { + }; + $logger->setExceptionHandler($callback); + $this->assertEquals($callback, $logger->getExceptionHandler()); + } + + /** + * @covers Monolog\Logger::setExceptionHandler + * @expectedException InvalidArgumentException + */ + public function testBadExceptionHandlerType() + { + $logger = new Logger(__METHOD__); + $logger->setExceptionHandler(false); + } + + /** + * @covers Monolog\Logger::handleException + * @expectedException Exception + */ + public function testDefaultHandleException() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->throwException(new \Exception('Some handler exception'))) + ; + $logger->pushHandler($handler); + $logger->info('test'); + } + + /** + * @covers Monolog\Logger::handleException + * @covers Monolog\Logger::addRecord + */ + public function testCustomHandleException() + { + $logger = new Logger(__METHOD__); + $that = $this; + $logger->setExceptionHandler(function ($e, $record) use ($that) { + $that->assertEquals($e->getMessage(), 'Some handler exception'); + $that->assertTrue(is_array($record)); + $that->assertEquals($record['message'], 'test'); + }); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->throwException(new \Exception('Some handler exception'))) + ; + $logger->pushHandler($handler); + $logger->info('test'); + } + + public function testReset() + { + $logger = new Logger('app'); + + $testHandler = new Handler\TestHandler(); + $bufferHandler = new Handler\BufferHandler($testHandler); + $groupHandler = new Handler\GroupHandler(array($bufferHandler)); + $fingersCrossedHandler = new Handler\FingersCrossedHandler($groupHandler); + + $logger->pushHandler($fingersCrossedHandler); + + $processorUid1 = new Processor\UidProcessor(10); + $uid1 = $processorUid1->getUid(); + $groupHandler->pushProcessor($processorUid1); + + $processorUid2 = new Processor\UidProcessor(5); + $uid2 = $processorUid2->getUid(); + $logger->pushProcessor($processorUid2); + + $getProperty = function ($object, $property) { + $reflectionProperty = new \ReflectionProperty(get_class($object), $property); + $reflectionProperty->setAccessible(true); + + return $reflectionProperty->getValue($object); + }; + $that = $this; + $assertBufferOfBufferHandlerEmpty = function () use ($getProperty, $bufferHandler, $that) { + $that->assertEmpty($getProperty($bufferHandler, 'buffer')); + }; + $assertBuffersEmpty = function() use ($assertBufferOfBufferHandlerEmpty, $getProperty, $fingersCrossedHandler, $that) { + $assertBufferOfBufferHandlerEmpty(); + $that->assertEmpty($getProperty($fingersCrossedHandler, 'buffer')); + }; + + $logger->debug('debug'); + $logger->reset(); + $assertBuffersEmpty(); + $this->assertFalse($testHandler->hasDebugRecords()); + $this->assertFalse($testHandler->hasErrorRecords()); + $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid()); + $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid()); + + $logger->debug('debug'); + $logger->error('error'); + $logger->reset(); + $assertBuffersEmpty(); + $this->assertTrue($testHandler->hasDebugRecords()); + $this->assertTrue($testHandler->hasErrorRecords()); + $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid()); + $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid()); + + $logger->info('info'); + $this->assertNotEmpty($getProperty($fingersCrossedHandler, 'buffer')); + $assertBufferOfBufferHandlerEmpty(); + $this->assertFalse($testHandler->hasInfoRecords()); + + $logger->reset(); + $assertBuffersEmpty(); + $this->assertFalse($testHandler->hasInfoRecords()); + $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid()); + $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid()); + + $logger->notice('notice'); + $logger->emergency('emergency'); + $logger->reset(); + $assertBuffersEmpty(); + $this->assertFalse($testHandler->hasInfoRecords()); + $this->assertTrue($testHandler->hasNoticeRecords()); + $this->assertTrue($testHandler->hasEmergencyRecords()); + $this->assertNotSame($uid1, $processorUid1->getUid()); + $this->assertNotSame($uid2, $processorUid2->getUid()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php new file mode 100644 index 0000000..5adb505 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class GitProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\GitProcessor::__invoke + */ + public function testProcessor() + { + $processor = new GitProcessor(); + $record = $processor($this->getRecord()); + + $this->assertArrayHasKey('git', $record['extra']); + $this->assertTrue(!is_array($record['extra']['git']['branch'])); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php new file mode 100644 index 0000000..0dd411d --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Acme; + +class Tester +{ + public function test($handler, $record) + { + $handler->handle($record); + } +} + +function tester($handler, $record) +{ + $handler->handle($record); +} + +namespace Monolog\Processor; + +use Monolog\Logger; +use Monolog\TestCase; +use Monolog\Handler\TestHandler; + +class IntrospectionProcessorTest extends TestCase +{ + public function getHandler() + { + $processor = new IntrospectionProcessor(); + $handler = new TestHandler(); + $handler->pushProcessor($processor); + + return $handler; + } + + public function testProcessorFromClass() + { + $handler = $this->getHandler(); + $tester = new \Acme\Tester; + $tester->test($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(18, $record['extra']['line']); + $this->assertEquals('Acme\Tester', $record['extra']['class']); + $this->assertEquals('test', $record['extra']['function']); + } + + public function testProcessorFromFunc() + { + $handler = $this->getHandler(); + \Acme\tester($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(24, $record['extra']['line']); + $this->assertEquals(null, $record['extra']['class']); + $this->assertEquals('Acme\tester', $record['extra']['function']); + } + + public function testLevelTooLow() + { + $input = array( + 'level' => Logger::DEBUG, + 'extra' => array(), + ); + + $expected = $input; + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } + + public function testLevelEqual() + { + $input = array( + 'level' => Logger::CRITICAL, + 'extra' => array(), + ); + + $expected = $input; + $expected['extra'] = array( + 'file' => null, + 'line' => null, + 'class' => 'ReflectionMethod', + 'function' => 'invokeArgs', + ); + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } + + public function testLevelHigher() + { + $input = array( + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ); + + $expected = $input; + $expected['extra'] = array( + 'file' => null, + 'line' => null, + 'class' => 'ReflectionMethod', + 'function' => 'invokeArgs', + ); + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php new file mode 100644 index 0000000..eb66614 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MemoryPeakUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryPeakUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_peak_usage']); + } + + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessorWithoutFormatting() + { + $processor = new MemoryPeakUsageProcessor(true, false); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertInternalType('int', $record['extra']['memory_peak_usage']); + $this->assertGreaterThan(0, $record['extra']['memory_peak_usage']); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php new file mode 100644 index 0000000..4692dbf --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MemoryUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_usage']); + } + + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessorWithoutFormatting() + { + $processor = new MemoryUsageProcessor(true, false); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertInternalType('int', $record['extra']['memory_usage']); + $this->assertGreaterThan(0, $record['extra']['memory_usage']); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php new file mode 100644 index 0000000..11f2b35 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MercurialProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MercurialProcessor::__invoke + */ + public function testProcessor() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + exec("where hg 2>NUL", $output, $result); + } else { + exec("which hg 2>/dev/null >/dev/null", $output, $result); + } + if ($result != 0) { + $this->markTestSkipped('hg is missing'); + return; + } + + `hg init`; + $processor = new MercurialProcessor(); + $record = $processor($this->getRecord()); + + $this->assertArrayHasKey('hg', $record['extra']); + $this->assertTrue(!is_array($record['extra']['hg']['branch'])); + $this->assertTrue(!is_array($record['extra']['hg']['revision'])); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php new file mode 100644 index 0000000..458d2a3 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class ProcessIdProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\ProcessIdProcessor::__invoke + */ + public function testProcessor() + { + $processor = new ProcessIdProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('process_id', $record['extra']); + $this->assertInternalType('int', $record['extra']['process_id']); + $this->assertGreaterThan(0, $record['extra']['process_id']); + $this->assertEquals(getmypid(), $record['extra']['process_id']); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php new file mode 100644 index 0000000..029a0c0 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +class PsrLogMessageProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getPairs + */ + public function testReplacement($val, $expected) + { + $proc = new PsrLogMessageProcessor; + + $message = $proc(array( + 'message' => '{foo}', + 'context' => array('foo' => $val), + )); + $this->assertEquals($expected, $message['message']); + } + + public function getPairs() + { + return array( + array('foo', 'foo'), + array('3', '3'), + array(3, '3'), + array(null, ''), + array(true, '1'), + array(false, ''), + array(new \stdClass, '[object stdClass]'), + array(array(), '[array]'), + ); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php new file mode 100644 index 0000000..0d860c6 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class TagProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\TagProcessor::__invoke + */ + public function testProcessor() + { + $tags = array(1, 2, 3); + $processor = new TagProcessor($tags); + $record = $processor($this->getRecord()); + + $this->assertEquals($tags, $record['extra']['tags']); + } + + /** + * @covers Monolog\Processor\TagProcessor::__invoke + */ + public function testProcessorTagModification() + { + $tags = array(1, 2, 3); + $processor = new TagProcessor($tags); + + $record = $processor($this->getRecord()); + $this->assertEquals($tags, $record['extra']['tags']); + + $processor->setTags(array('a', 'b')); + $record = $processor($this->getRecord()); + $this->assertEquals(array('a', 'b'), $record['extra']['tags']); + + $processor->addTags(array('a', 'c', 'foo' => 'bar')); + $record = $processor($this->getRecord()); + $this->assertEquals(array('a', 'b', 'a', 'c', 'foo' => 'bar'), $record['extra']['tags']); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php new file mode 100644 index 0000000..5d13058 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class UidProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\UidProcessor::__invoke + */ + public function testProcessor() + { + $processor = new UidProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('uid', $record['extra']); + } + + public function testGetUid() + { + $processor = new UidProcessor(10); + $this->assertEquals(10, strlen($processor->getUid())); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php new file mode 100644 index 0000000..4105baf --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class WebProcessorTest extends TestCase +{ + public function testProcessor() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'HTTP_REFERER' => 'D', + 'SERVER_NAME' => 'F', + 'UNIQUE_ID' => 'G', + ); + + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']); + $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); + $this->assertEquals($server['UNIQUE_ID'], $record['extra']['unique_id']); + } + + public function testProcessorDoNothingIfNoRequestUri() + { + $server = array( + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEmpty($record['extra']); + } + + public function testProcessorReturnNullIfNoHttpReferer() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertNull($record['extra']['referrer']); + } + + public function testProcessorDoesNotAddUniqueIdIfNotPresent() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertFalse(isset($record['extra']['unique_id'])); + } + + public function testProcessorAddsOnlyRequestedExtraFields() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server, array('url', 'http_method')); + $record = $processor($this->getRecord()); + + $this->assertSame(array('url' => 'A', 'http_method' => 'C'), $record['extra']); + } + + public function testProcessorConfiguringOfExtraFields() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server, array('url' => 'REMOTE_ADDR')); + $record = $processor($this->getRecord()); + + $this->assertSame(array('url' => 'B'), $record['extra']); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidData() + { + new WebProcessor(new \stdClass); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php b/core/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php new file mode 100644 index 0000000..ab89944 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\PsrLogMessageProcessor; +use Psr\Log\Test\LoggerInterfaceTest; + +class PsrLogCompatTest extends LoggerInterfaceTest +{ + private $handler; + + public function getLogger() + { + $logger = new Logger('foo'); + $logger->pushHandler($handler = new TestHandler); + $logger->pushProcessor(new PsrLogMessageProcessor); + $handler->setFormatter(new LineFormatter('%level_name% %message%')); + + $this->handler = $handler; + + return $logger; + } + + public function getLogs() + { + $convert = function ($record) { + $lower = function ($match) { + return strtolower($match[0]); + }; + + return preg_replace_callback('{^[A-Z]+}', $lower, $record['formatted']); + }; + + return array_map($convert, $this->handler->getRecords()); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/RegistryTest.php b/core/vendor/monolog/monolog/tests/Monolog/RegistryTest.php new file mode 100644 index 0000000..15fdfbd --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/RegistryTest.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class RegistryTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Registry::clear(); + } + + /** + * @dataProvider hasLoggerProvider + * @covers Monolog\Registry::hasLogger + */ + public function testHasLogger(array $loggersToAdd, array $loggersToCheck, array $expectedResult) + { + foreach ($loggersToAdd as $loggerToAdd) { + Registry::addLogger($loggerToAdd); + } + foreach ($loggersToCheck as $index => $loggerToCheck) { + $this->assertSame($expectedResult[$index], Registry::hasLogger($loggerToCheck)); + } + } + + public function hasLoggerProvider() + { + $logger1 = new Logger('test1'); + $logger2 = new Logger('test2'); + $logger3 = new Logger('test3'); + + return array( + // only instances + array( + array($logger1), + array($logger1, $logger2), + array(true, false), + ), + // only names + array( + array($logger1), + array('test1', 'test2'), + array(true, false), + ), + // mixed case + array( + array($logger1, $logger2), + array('test1', $logger2, 'test3', $logger3), + array(true, true, false, false), + ), + ); + } + + /** + * @covers Monolog\Registry::clear + */ + public function testClearClears() + { + Registry::addLogger(new Logger('test1'), 'log'); + Registry::clear(); + + $this->setExpectedException('\InvalidArgumentException'); + Registry::getInstance('log'); + } + + /** + * @dataProvider removedLoggerProvider + * @covers Monolog\Registry::addLogger + * @covers Monolog\Registry::removeLogger + */ + public function testRemovesLogger($loggerToAdd, $remove) + { + Registry::addLogger($loggerToAdd); + Registry::removeLogger($remove); + + $this->setExpectedException('\InvalidArgumentException'); + Registry::getInstance($loggerToAdd->getName()); + } + + public function removedLoggerProvider() + { + $logger1 = new Logger('test1'); + + return array( + array($logger1, $logger1), + array($logger1, 'test1'), + ); + } + + /** + * @covers Monolog\Registry::addLogger + * @covers Monolog\Registry::getInstance + * @covers Monolog\Registry::__callStatic + */ + public function testGetsSameLogger() + { + $logger1 = new Logger('test1'); + $logger2 = new Logger('test2'); + + Registry::addLogger($logger1, 'test1'); + Registry::addLogger($logger2); + + $this->assertSame($logger1, Registry::getInstance('test1')); + $this->assertSame($logger2, Registry::test2()); + } + + /** + * @expectedException \InvalidArgumentException + * @covers Monolog\Registry::getInstance + */ + public function testFailsOnNonExistantLogger() + { + Registry::getInstance('test1'); + } + + /** + * @covers Monolog\Registry::addLogger + */ + public function testReplacesLogger() + { + $log1 = new Logger('test1'); + $log2 = new Logger('test2'); + + Registry::addLogger($log1, 'log'); + + Registry::addLogger($log2, 'log', true); + + $this->assertSame($log2, Registry::getInstance('log')); + } + + /** + * @expectedException \InvalidArgumentException + * @covers Monolog\Registry::addLogger + */ + public function testFailsOnUnspecifiedReplacement() + { + $log1 = new Logger('test1'); + $log2 = new Logger('test2'); + + Registry::addLogger($log1, 'log'); + + Registry::addLogger($log2, 'log'); + } +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php new file mode 100644 index 0000000..9fa0792 --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\StreamHandler; +use Monolog\Handler\TestHandler; +use Psr\Log\LogLevel; + +/** + * @author Robert Gust-Bardon + * @covers Monolog\SignalHandler + */ +class SignalHandlerTest extends TestCase +{ + + private $asyncSignalHandling; + private $blockedSignals; + private $signalHandlers; + + protected function setUp() + { + $this->signalHandlers = array(); + if (extension_loaded('pcntl')) { + if (function_exists('pcntl_async_signals')) { + $this->asyncSignalHandling = pcntl_async_signals(); + } + if (function_exists('pcntl_sigprocmask')) { + pcntl_sigprocmask(SIG_BLOCK, array(), $this->blockedSignals); + } + } + } + + protected function tearDown() + { + if ($this->asyncSignalHandling !== null) { + pcntl_async_signals($this->asyncSignalHandling); + } + if ($this->blockedSignals !== null) { + pcntl_sigprocmask(SIG_SETMASK, $this->blockedSignals); + } + if ($this->signalHandlers) { + pcntl_signal_dispatch(); + foreach ($this->signalHandlers as $signo => $handler) { + pcntl_signal($signo, $handler); + } + } + } + + private function setSignalHandler($signo, $handler = SIG_DFL) { + if (function_exists('pcntl_signal_get_handler')) { + $this->signalHandlers[$signo] = pcntl_signal_get_handler($signo); + } else { + $this->signalHandlers[$signo] = SIG_DFL; + } + $this->assertTrue(pcntl_signal($signo, $handler)); + } + + public function testHandleSignal() + { + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $signo = 2; // SIGINT. + $siginfo = array('signo' => $signo, 'errno' => 0, 'code' => 0); + $errHandler->handleSignal($signo, $siginfo); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasCriticalRecords()); + $records = $handler->getRecords(); + $this->assertSame($siginfo, $records[0]['context']); + } + + /** + * @depends testHandleSignal + * @requires extension pcntl + * @requires extension posix + * @requires function pcntl_signal + * @requires function pcntl_signal_dispatch + * @requires function posix_getpid + * @requires function posix_kill + */ + public function testRegisterSignalHandler() + { + // SIGCONT and SIGURG should be ignored by default. + if (!defined('SIGCONT') || !defined('SIGURG')) { + $this->markTestSkipped('This test requires the SIGCONT and SIGURG pcntl constants.'); + } + + $this->setSignalHandler(SIGCONT, SIG_IGN); + $this->setSignalHandler(SIGURG, SIG_IGN); + + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $pid = posix_getpid(); + + $this->assertTrue(posix_kill($pid, SIGURG)); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(0, $handler->getRecords()); + + $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, false); + + $this->assertTrue(posix_kill($pid, SIGCONT)); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(0, $handler->getRecords()); + + $this->assertTrue(posix_kill($pid, SIGURG)); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasInfoThatContains('SIGURG')); + } + + /** + * @dataProvider defaultPreviousProvider + * @depends testRegisterSignalHandler + * @requires function pcntl_fork + * @requires function pcntl_sigprocmask + * @requires function pcntl_waitpid + */ + public function testRegisterDefaultPreviousSignalHandler($signo, $callPrevious, $expected) + { + $this->setSignalHandler($signo, SIG_DFL); + + $path = tempnam(sys_get_temp_dir(), 'monolog-'); + $this->assertNotFalse($path); + + $pid = pcntl_fork(); + if ($pid === 0) { // Child. + $streamHandler = new StreamHandler($path); + $streamHandler->setFormatter($this->getIdentityFormatter()); + $logger = new Logger('test', array($streamHandler)); + $errHandler = new SignalHandler($logger); + $errHandler->registerSignalHandler($signo, LogLevel::INFO, $callPrevious, false, false); + pcntl_sigprocmask(SIG_SETMASK, array(SIGCONT)); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + // If $callPrevious is true, SIGINT should terminate by this line. + pcntl_sigprocmask(SIG_BLOCK, array(), $oldset); + file_put_contents($path, implode(' ', $oldset), FILE_APPEND); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + exit(); + } + + $this->assertNotSame(-1, $pid); + $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); + $this->assertNotSame(-1, $status); + $this->assertSame($expected, file_get_contents($path)); + } + + public function defaultPreviousProvider() + { + if (!defined('SIGCONT') || !defined('SIGINT') || !defined('SIGURG')) { + return array(); + } + + return array( + array(SIGINT, false, 'Program received signal SIGINT'.SIGCONT.'Program received signal SIGINT'), + array(SIGINT, true, 'Program received signal SIGINT'), + array(SIGURG, false, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'), + array(SIGURG, true, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'), + ); + } + + /** + * @dataProvider callablePreviousProvider + * @depends testRegisterSignalHandler + * @requires function pcntl_signal_get_handler + */ + public function testRegisterCallablePreviousSignalHandler($callPrevious) + { + $this->setSignalHandler(SIGURG, SIG_IGN); + + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $previousCalled = 0; + pcntl_signal(SIGURG, function ($signo, array $siginfo = null) use (&$previousCalled) { + ++$previousCalled; + }); + $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, $callPrevious, false, false); + $this->assertTrue(posix_kill(posix_getpid(), SIGURG)); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasInfoThatContains('SIGURG')); + $this->assertSame($callPrevious ? 1 : 0, $previousCalled); + } + + public function callablePreviousProvider() + { + return array( + array(false), + array(true), + ); + } + + /** + * @dataProvider restartSyscallsProvider + * @depends testRegisterDefaultPreviousSignalHandler + * @requires function pcntl_fork + * @requires function pcntl_waitpid + */ + public function testRegisterSyscallRestartingSignalHandler($restartSyscalls) + { + $this->setSignalHandler(SIGURG, SIG_IGN); + + $parentPid = posix_getpid(); + $microtime = microtime(true); + + $pid = pcntl_fork(); + if ($pid === 0) { // Child. + usleep(100000); + posix_kill($parentPid, SIGURG); + usleep(100000); + exit(); + } + + $this->assertNotSame(-1, $pid); + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, $restartSyscalls, false); + if ($restartSyscalls) { + // pcntl_wait is expected to be restarted after the signal handler. + $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); + } else { + // pcntl_wait is expected to be interrupted when the signal handler is invoked. + $this->assertSame(-1, pcntl_waitpid($pid, $status)); + } + $this->assertSame($restartSyscalls, microtime(true) - $microtime > 0.15); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(1, $handler->getRecords()); + if ($restartSyscalls) { + // The child has already exited. + $this->assertSame(-1, pcntl_waitpid($pid, $status)); + } else { + // The child has not exited yet. + $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); + } + } + + public function restartSyscallsProvider() + { + return array( + array(false), + array(true), + array(false), + array(true), + ); + } + + /** + * @dataProvider asyncProvider + * @depends testRegisterDefaultPreviousSignalHandler + * @requires function pcntl_async_signals + */ + public function testRegisterAsyncSignalHandler($initialAsync, $desiredAsync, $expectedBefore, $expectedAfter) + { + $this->setSignalHandler(SIGURG, SIG_IGN); + pcntl_async_signals($initialAsync); + + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, $desiredAsync); + $this->assertTrue(posix_kill(posix_getpid(), SIGURG)); + $this->assertCount($expectedBefore, $handler->getRecords()); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount($expectedAfter, $handler->getRecords()); + } + + public function asyncProvider() + { + return array( + array(false, false, 0, 1), + array(false, null, 0, 1), + array(false, true, 1, 1), + array(true, false, 0, 1), + array(true, null, 1, 1), + array(true, true, 1, 1), + ); + } + +} diff --git a/core/vendor/monolog/monolog/tests/Monolog/TestCase.php b/core/vendor/monolog/monolog/tests/Monolog/TestCase.php new file mode 100644 index 0000000..4eb7b4c --- /dev/null +++ b/core/vendor/monolog/monolog/tests/Monolog/TestCase.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class TestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @return array Record + */ + protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array()) + { + return array( + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))), + 'extra' => array(), + ); + } + + /** + * @return array + */ + protected function getMultipleRecords() + { + return array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ); + } + + /** + * @return Monolog\Formatter\FormatterInterface + */ + protected function getIdentityFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message']; })); + + return $formatter; + } +} diff --git a/core/vendor/morilog/jalali/.gitignore b/core/vendor/morilog/jalali/.gitignore new file mode 100644 index 0000000..38cdbd7 --- /dev/null +++ b/core/vendor/morilog/jalali/.gitignore @@ -0,0 +1,5 @@ +/vendor +composer.phar +composer.lock +.DS_Store +.idea \ No newline at end of file diff --git a/core/vendor/morilog/jalali/.travis.yml b/core/vendor/morilog/jalali/.travis.yml new file mode 100644 index 0000000..f817604 --- /dev/null +++ b/core/vendor/morilog/jalali/.travis.yml @@ -0,0 +1,11 @@ +language: php + +php: + - 5.5 + - 5.6 + +before_script: + - curl -s http://getcomposer.org/installer | php + - php composer.phar install --dev + +script: phpunit diff --git a/core/vendor/morilog/jalali/LICENSE b/core/vendor/morilog/jalali/LICENSE new file mode 100644 index 0000000..8d2fb98 --- /dev/null +++ b/core/vendor/morilog/jalali/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Morilog + +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/core/vendor/morilog/jalali/README.md b/core/vendor/morilog/jalali/README.md new file mode 100644 index 0000000..10027b1 --- /dev/null +++ b/core/vendor/morilog/jalali/README.md @@ -0,0 +1,146 @@ +[![Build Status](https://travis-ci.org/morilog/jalali.svg?branch=master)](https://travis-ci.org/morilog/jalali) +morilog/jalali +====== +- This package compatible with Laravel `>=5` & `< 6.0` + +- This package was forked from [Miladr/Jalali](http://github.com/miladr/jalai) in previous version and fixed bugs a + +- Jalali calendar is a solar calendar that was used in Persia, variants of which today are still in use in Iran as well as Afghanistan. [Read more on Wikipedia](http://en.wikipedia.org/wiki/Jalali_calendar) or see [Calendar Converter](http://www.fourmilab.ch/documents/calendar/). + +- Calendar conversion is based on the [algorithm provided by Kazimierz M. Borkowski](http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm) and has a very good performance. + +- jDateTime class was ported from [jalaali/jalaali-js](https://github.com/jalaali/jalaali-js) + +## Installation Version 2.0 +> If you are using version < 2.0, please read [old docs](https://github.com/morilog/jalali/blob/v1.1/README.md) + +Run the Composer update comand + + $ composer require morilog/jalali + +In your `config/app.php` add `'Morilog\Jalali\JalaliServiceProvider'` to the end of the `$providers` array + +```php +'providers' => [ + + Illuminate\Foundation\Providers\ArtisanServiceProvider::class, + Illuminate\Auth\AuthServiceProvider::class, + ... + Morilog\Jalali\JalaliServiceProvider::class, + +], + + +'alias' => [ + ... + 'jDate' => Morilog\Jalali\Facades\jDate::class +] +``` + + +## Basic Usage + +### jDate +In version >= 1.1, You can use `jdate()` instead of `jDate::forge()`; +#### `forge([$str = '', $timestamp = null])` +``` php +// default timestamp is now +$date = \Morilog\Jalali\jDate::forge(); +// OR +$date = jdate(); + +// pass timestamps +$date = jDate::forge(1333857600); +// OR +$date = jdate(1333857600); + +// pass strings to make timestamps +$date = jDate::forge('last sunday'); + +// get the timestamp +$date = jDate::forge('last sunday')->time(); // 1333857600 + +// format the timestamp +$date = jDate::forge('last sunday')->format('%B %d، %Y'); // دی 02، 1391 + +// get a predefined format +$date = jDate::forge('last sunday')->format('datetime'); // 1391-10-02 00:00:00 +$date = jDate::forge('last sunday')->format('date'); // 1391-10-02 +$date = jDate::forge('last sunday')->format('time'); // 00:00:00 + +// amend the timestamp value, relative to existing value +$date = jDate::forge('2012-10-12')->reforge('+ 3 days')->format('date'); // 1391-07-24 + +// get relative 'ago' format +$date = jDate::forge('now - 10 minutes')->ago() // 10 دقیقه پیش +// OR +$date = jdate('now - 10 minutes')->ago() // 10 دقیقه پیش +``` + +### jDateTime +--- + + +#### `checkDate($year, $month, $day, [$isJalali = true])` +```php +// Check jalali date +\Morilog\Jalali\jDateTime::checkDate(1391, 2, 30, true); // true + +// Check jalali date +\Morilog\Jalali\jDateTime::checkDate(2016, 5, 7); // false + +// Check gregorian date +\Morilog\Jalali\jDateTime::checkDate(2016, 5, 7, false); // true +``` +--- +#### `toJalali($gYear, $gMonth, $gDay)` +```php +\Morilog\Jalali\jDateTime::toJalali(2016, 5, 7); // [1395, 2, 18] +``` +--- +#### `toGregorian($jYear, $jMonth, $jDay)` +```php +\Morilog\Jalali\jDateTime::toGregorian(1395, 2, 18); // [2016, 5, 7] +``` +--- +#### `strftime($format, [$timestamp = false, $timezone = null])` +```php +jDateTime::strftime('Y-m-d', strtotime('2016-05-8')); // 1395-02-19 +``` +--- +#### `createDateTimeFromFormat($format, $jalaiTimeString)` +```php +$jdate = '1394/11/25 15:00:00'; + +// get instance of \DateTime +$dateTime = \Morilog\Jalali\jDatetime::createDatetimeFromFormat('Y/m/d H:i:s', $jdate); + +``` +--- +#### `createCarbonFromFormat($format, $jalaiTimeString)` +```php +$jdate = '1394/11/25 15:00:00'; + +// get instance of \Carbon\Carbon +$carbon = \Morilog\Jalali\jDatetime::createDatetimeFromFormat('Y/m/d H:i:s', $jdate); + +``` +--- +#### `convertNumbers($string)` +```php +$date = \Morilog\Jalali\jDateTime::strftime('Y-m-d', strtotime('2016-05-8'); // 1395-02-19 +\Morilog\Jalali\jDateTime::convertNumbers($date); // ۱۳۹۵-۰۲-۱۹ +``` +--- +## Formatting ## + +For help in building your formats, checkout the [PHP strftime() docs](http://php.net/manual/en/function.strftime.php). + +## Notes ## + +The class relies on ``strtotime()`` to make sense of your strings, and ``strftime()`` to make the format changes. Just always check the ``time()`` output to see if you get false timestamps... which means the class couldn't understand what you were telling it. + +## License ## +- This bundle is created based on [Laravel-Date](https://github.com/swt83/laravel-date) by [Scott Travis](https://github.com/swt83) (MIT Licensed). +- [Jalali (Shamsi) DateTime](https://github.com/sallar/jDateTime) class included in the package is created by [Sallar Kaboli](http://sallar.me) and is released under the MIT License. +- This package was created and modified by [Morteza Parvini](http://morilog.ir) for Laravel >= 5 and is released under the MIT License. diff --git a/core/vendor/morilog/jalali/composer.json b/core/vendor/morilog/jalali/composer.json new file mode 100644 index 0000000..20a45bf --- /dev/null +++ b/core/vendor/morilog/jalali/composer.json @@ -0,0 +1,43 @@ +{ + "name": "morilog/jalali", + "description": "eThis Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in Laravel 5 applications, based on Jalali (Shamsi) DateTime class.", + "license": "MIT", + "authors": [ + { + "name": "Milad Rey", + "email": "miladr@gmail.com" + }, + { + "name": "Morteza Parvini", + "email": "m.parvini@outlook.com" + } + ], + "keywords": ["Laravel","Date","Datetime","Jalali", "Morilog"], + "require": { + "php": ">=5.5", + "illuminate/support": "^5.0", + "nesbot/carbon": "^1.21" + }, + "require-dev" : { + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": { + "Morilog\\Jalali\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "extra": { + "laravel": { + "providers": [ + "Morilog\\Jalali\\JalaliServiceProvider" + ], + "aliases": { + "jDate": "Morilog\\Jalali\\Facades\\jDate" + } + } + }, + "minimum-stability": "dev" +} diff --git a/core/vendor/morilog/jalali/phpunit.xml b/core/vendor/morilog/jalali/phpunit.xml new file mode 100644 index 0000000..4eceac6 --- /dev/null +++ b/core/vendor/morilog/jalali/phpunit.xml @@ -0,0 +1,17 @@ + + + + + ./tests/ + + + \ No newline at end of file diff --git a/core/vendor/morilog/jalali/src/Facades/jDate.php b/core/vendor/morilog/jalali/src/Facades/jDate.php new file mode 100644 index 0000000..df6df7b --- /dev/null +++ b/core/vendor/morilog/jalali/src/Facades/jDate.php @@ -0,0 +1,19 @@ +app->bind('jalali', function ($app) { + return new jDate; + }); + + $this->app->bind('jDateTime', function ($app) { + return new jDateTime; + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('jalali', 'jDateTime'); + } + +} diff --git a/core/vendor/morilog/jalali/src/helpers.php b/core/vendor/morilog/jalali/src/helpers.php new file mode 100644 index 0000000..3bb250d --- /dev/null +++ b/core/vendor/morilog/jalali/src/helpers.php @@ -0,0 +1,13 @@ + + * + * + * Based on Laravel-Date bundle + * by Scott Travis + * http://github.com/swt83/laravel-date + * + * + * @package jDate + * @author Sallar Kaboli + * @author Morteza Parvini + * @link http:// + * @basedon http://github.com/swt83/laravel-date + * @license MIT License + */ +use Carbon\Carbon; + +/** + * Class jDate + * @package Morilog\Jalali + */ +class jDate +{ + /** + * @var \DateTime + */ + protected $dateTime; + + + /** + * @var array + */ + protected $formats = array( + 'datetime' => '%Y-%m-%d %H:%M:%S', + 'date' => '%Y-%m-%d', + 'time' => '%H:%M:%S', + ); + + /** + * @param string|null $str + * @param null $timezone + * @return $this + */ + public static function forge($str = null, $timezone = null) + { + return new static($str, $timezone); + } + + /** + * @param string|null $str + * @param null $timezone + */ + public function __construct($str = null, $timezone = null) + { + $this->dateTime = jDateTime::createDateTime($str, $timezone); + } + + /** + * @return \DateTime|static + */ + public function getDateTime() + { + return $this->dateTime; + } + + /** + * @param $format + * @return bool|string + */ + public function format($format) + { + // convert alias string + if (in_array($format, array_keys($this->formats))) { + $format = $this->formats[$format]; + } + + // if valid unix timestamp... + if ($this->dateTime !== false) { + return jDateTime::strftime($format, $this->dateTime->getTimestamp(), $this->dateTime->getTimezone()); + } else { + return false; + } + } + + /** + * @param string $str + * @return $this + */ + public function reforge($str) + { + $this->dateTime->modify($str); + + return $this; + } + + /** + * @return string + */ + public function ago() + { + $now = time(); + $time = $this->getDateTime()->getTimestamp(); + + // catch error + if (!$time) { + return false; + } + + // build period and length arrays + $periods = array('ثانیه', 'دقیقه', 'ساعت', 'روز', 'هفته', 'ماه', 'سال', 'قرن'); + $lengths = array(60, 60, 24, 7, 4.35, 12, 10); + + // get difference + $difference = $now - $time; + + // set descriptor + if ($difference < 0) { + $difference = abs($difference); // absolute value + $negative = true; + } + + // do math + for ($j = 0; $difference >= $lengths[$j] and $j < count($lengths) - 1; $j++) { + $difference /= $lengths[$j]; + } + + // round difference + $difference = intval(round($difference)); + + // return + return number_format($difference) . ' ' . $periods[$j] . ' ' . (isset($negative) ? '' : 'پیش'); + } + + /** + * @return bool|string + */ + public function until() + { + return $this->ago(); + } + + /** + * @return int + */ + public function time() + { + return $this->dateTime->getTimestamp(); + } + +} diff --git a/core/vendor/morilog/jalali/src/jDateTime.php b/core/vendor/morilog/jalali/src/jDateTime.php new file mode 100644 index 0000000..50a8ecc --- /dev/null +++ b/core/vendor/morilog/jalali/src/jDateTime.php @@ -0,0 +1,868 @@ +setDate($year, $month, $day); + + + return $georgianDate; + } + + /** + * Checks whether a Jalaali date is valid or not. + * + * @param int $jy + * @param int $jm + * @param int $jd + * @return bool + */ + public static function isValidateJalaliDate($jy, $jm, $jd) + { + return $jy >= -61 && $jy <= 3177 + && $jm >= 1 && $jm <= 12 + && $jd >= 1 && $jd <= self::jalaliMonthLength($jy, $jm); + } + + /** + * Checks whether a date is valid or not. + * + * @param $year + * @param $month + * @param $day + * @param bool $isJalali + * @return bool + */ + public static function checkDate($year, $month, $day, $isJalali = true) + { + return $isJalali === true ? self::isValidateJalaliDate($year, $month, $day) : checkdate($month, $day, $year); + } + + /** + * Is this a leap year or not? + * + * @param $jy + * @return bool + */ + public static function isLeapJalaliYear($jy) + { + return self::jalaliCal($jy)['leap'] === 0; + } + + /** + * Number of days in a given month in a Jalaali year. + * + * @param int $jy + * @param int $jm + * @return int + */ + public static function jalaliMonthLength($jy, $jm) + { + if ($jm <= 6) { + return 31; + } + + if ($jm <= 11) { + return 30; + } + + return self::isLeapJalaliYear($jy) ? 30 : 29; + } + + + /** + * This function determines if the Jalaali (Persian) year is + * leap (366-day long) or is the common year (365 days), and + * finds the day in March (Gregorian calendar) of the first + * day of the Jalaali year (jy). + * + * @param int $jy Jalaali calendar year (-61 to 3177) + * @return array + * leap: number of years since the last leap year (0 to 4) + * gy: Gregorian year of the beginning of Jalaali year + * march: the March day of Farvardin the 1st (1st day of jy) + * @see: http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm + * @see: http://www.fourmilab.ch/documents/calendar/ + */ + public static function jalaliCal($jy) + { + $breaks = [-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210 + , 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178 + ]; + + $breaksCount = count($breaks); + + $gy = $jy + 621; + $leapJ = -14; + $jp = $breaks[0]; + + if ($jy < $jp || $jy >= $breaks[$breaksCount - 1]) { + throw new \InvalidArgumentException('Invalid Jalali year : ' . $jy); + } + + $jump = 0; + + for ($i = 1; $i < $breaksCount; $i += 1) { + $jm = $breaks[$i]; + $jump = $jm - $jp; + + if ($jy < $jm) { + break; + } + + $leapJ = $leapJ + self::div($jump, 33) * 8 + self::div(self::mod($jump, 33), 4); + + $jp = $jm; + } + + $n = $jy - $jp; + + $leapJ = $leapJ + self::div($n, 33) * 8 + self::div(self::mod($n, 33) + 3, 4); + + if (self::mod($jump, 33) === 4 && $jump - $n === 4) { + $leapJ += 1; + } + + $leapG = self::div($gy, 4) - self::div((self::div($gy, 100) + 1) * 3, 4) - 150; + + $march = 20 + $leapJ - $leapG; + + if ($jump - $n < 6) { + $n = $n - $jump + self::div($jump + 4, 33) * 33; + } + + $leap = self::mod(self::mod($n + 1, 33) - 1, 4); + + if ($leap === -1) { + $leap = 4; + } + + return [ + 'leap' => $leap, + 'gy' => $gy, + 'march' => $march + ]; + } + + /** + * @param $a + * @param $b + * @return float + */ + public static function div($a, $b) + { + return ~~($a / $b); + } + + /** + * @param $a + * @param $b + * @return mixed + */ + public static function mod($a, $b) + { + return $a - ~~($a / $b) * $b; + } + + /** + * @param $jdn + * @return array + */ + public static function d2g($jdn) + { + $j = 4 * $jdn + 139361631; + $j += self::div(self::div(4 * $jdn + 183187720, 146097) * 3, 4) * 4 - 3908; + $i = self::div(self::mod($j, 1461), 4) * 5 + 308; + + $gd = self::div(self::mod($i, 153), 5) + 1; + $gm = self::mod(self::div($i, 153), 12) + 1; + $gy = self::div($j, 1461) - 100100 + self::div(8 - $gm, 6); + + return [$gy, $gm, $gd]; + } + + /** + * Calculates the Julian Day number from Gregorian or Julian + * calendar dates. This integer number corresponds to the noon of + * the date (i.e. 12 hours of Universal Time). + * The procedure was tested to be good since 1 March, -100100 (of both + * calendars) up to a few million years into the future. + * + * @param int $gy Calendar year (years BC numbered 0, -1, -2, ...) + * @param int $gm Calendar month (1 to 12) + * @param int $gd Calendar day of the month (1 to 28/29/30/31) + * @return int Julian Day number + */ + public static function g2d($gy, $gm, $gd) + { + return ( + self::div(($gy + self::div($gm - 8, 6) + 100100) * 1461, 4) + + self::div(153 * self::mod($gm + 9, 12) + 2, 5) + + $gd - 34840408 + ) - self::div(self::div($gy + 100100 + self::div($gm - 8, 6), 100) * 3, 4) + 752; + + } + + /** + * Converts a date of the Jalaali calendar to the Julian Day number. + * + * @param int $jy Jalaali year (1 to 3100) + * @param int $jm Jalaali month (1 to 12) + * @param int $jd Jalaali day (1 to 29/31) + * @return int Julian Day number + */ + public static function j2d($jy, $jm, $jd) + { + $jCal = self::jalaliCal($jy); + + return self::g2d($jCal['gy'], 3, $jCal['march']) + ($jm - 1) * 31 - self::div($jm, 7) * ($jm - 7) + $jd - 1; + } + + + /** + * Converts the Julian Day number to a date in the Jalaali calendar. + * + * @param int $jdn Julian Day number + * @return array + * 0: Jalaali year (1 to 3100) + * 1: Jalaali month (1 to 12) + * 2: Jalaali day (1 to 29/31) + */ + public static function d2j($jdn) + { + $gy = self::d2g($jdn)[0]; + $jy = $gy - 621; + $jCal = self::jalaliCal($jy); + $jdn1f = self::g2d($gy, 3, $jCal['march']); + + $k = $jdn - $jdn1f; + + if ($k >= 0) { + if ($k <= 185) { + $jm = 1 + self::div($k, 31); + $jd = self::mod($k, 31) + 1; + + return [$jy, $jm, $jd]; + } else { + $k -= 186; + } + } else { + $jy -= 1; + $k += 179; + + if ($jCal['leap'] === 1) { + $k += 1; + } + } + + $jm = 7 + self::div($k, 30); + $jd = self::mod($k, 30) + 1; + + return [$jy, $jm, $jd]; + } + + /** + * @param $format + * @param bool $stamp + * @param bool $timezone + * @return mixed + */ + public static function date($format, $stamp = false, $timezone = null) + { + $stamp = ($stamp !== false) ? $stamp : time(); + $dateTime = static::createDateTime($stamp, $timezone); + + + //Find what to replace + $chars = (preg_match_all('/([a-zA-Z]{1})/', $format, $chars)) ? $chars[0] : array(); + + //Intact Keys + $intact = array('B', 'h', 'H', 'g', 'G', 'i', 's', 'I', 'U', 'u', 'Z', 'O', 'P'); + $intact = self::filterArray($chars, $intact); + $intactValues = array(); + + foreach ($intact as $k => $v) { + $intactValues[$k] = $dateTime->format($v); + } + //End Intact Keys + + //Changed Keys + list($year, $month, $day) = array($dateTime->format('Y'), $dateTime->format('n'), $dateTime->format('j')); + list($jYear, $jMonth, $jDay) = self::toJalali($year, $month, $day); + + $keys = array( + 'd', + 'D', + 'j', + 'l', + 'N', + 'S', + 'w', + 'z', + 'W', + 'F', + 'm', + 'M', + 'n', + 't', + 'L', + 'o', + 'Y', + 'y', + 'a', + 'A', + 'c', + 'r', + 'e', + 'T' + ); + $keys = self::filterArray($chars, $keys, array('z')); + $values = array(); + + foreach ($keys as $k => $key) { + + $v = ''; + switch ($key) { + //Day + case 'd': + $v = sprintf("%02d", $jDay); + break; + case 'D': + $v = self::getDayNames($dateTime->format('D'), true); + break; + case 'j': + $v = $jDay; + break; + case 'l': + $v = self::getDayNames($dateTime->format('l')); + break; + case 'N': + $v = self::getDayNames($dateTime->format('l'), false, 1, true); + break; + case 'S': + $v = 'ام'; + break; + case 'w': + $v = self::getDayNames($dateTime->format('l'), false, 1, true) - 1; + break; + case 'z': + if ($jMonth > 6) { + $v = 186 + (($jMonth - 6 - 1) * 30) + $jDay; + } else { + $v = (($jMonth - 1) * 31) + $jDay; + } + self::$temp['z'] = $v; + break; + //Week + case 'W': + $v = is_int(self::$temp['z'] / 7) ? (self::$temp['z'] / 7) : intval(self::$temp['z'] / 7 + 1); + break; + //Month + case 'F': + $v = self::getMonthNames($jMonth); + break; + case 'm': + $v = sprintf("%02d", $jMonth); + break; + case 'M': + $v = self::getMonthNames($jMonth, true); + break; + case 'n': + $v = $jMonth; + break; + case 't': + $v = ($jMonth == 12) ? 29 : (($jMonth > 6 && $jMonth != 12) ? 30 : 31); + break; + //Year + case 'L': + $tmpObj = static::createDateTime(time() - 31536000, $timezone); + $v = $tmpObj->format('L'); + break; + case 'o': + case 'Y': + $v = $jYear; + break; + case 'y': + $v = $jYear % 100; + break; + //Time + case 'a': + $v = ($dateTime->format('a') == 'am') ? 'ق.ظ' : 'ب.ظ'; + break; + case 'A': + $v = ($dateTime->format('A') == 'AM') ? 'قبل از ظهر' : 'بعد از ظهر'; + break; + //Full Dates + case 'c': + $v = $jYear . '-' . sprintf("%02d", $jMonth) . '-' . sprintf("%02d", $jDay) . 'T'; + $v .= $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . $dateTime->format('P'); + break; + case 'r': + $v = self::getDayNames($dateTime->format('D'), true) . ', ' . sprintf("%02d", + $jDay) . ' ' . self::getMonthNames($jMonth, true); + $v .= ' ' . $jYear . ' ' . $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . ' ' . $dateTime->format('P'); + break; + //Timezone + case 'e': + $v = $dateTime->format('e'); + break; + case 'T': + $v = $dateTime->format('T'); + break; + + } + $values[$k] = $v; + + } + //End Changed Keys + + //Merge + $keys = array_merge($intact, $keys); + $values = array_merge($intactValues, $values); + + return strtr($format, array_combine($keys, $values)); + } + + /** + * @param $format + * @param bool $stamp + * @param null $timezone + * @return mixed + */ + public static function strftime($format, $stamp = false, $timezone = null) + { + $str_format_code = array( + "%a", + "%A", + "%d", + "%e", + "%j", + "%u", + "%w", + "%U", + "%V", + "%W", + "%b", + "%B", + "%h", + "%m", + "%C", + "%g", + "%G", + "%y", + "%Y", + "%H", + "%I", + "%l", + "%M", + "%p", + "%P", + "%r", + "%R", + "%S", + "%T", + "%X", + "%z", + "%Z", + "%c", + "%D", + "%F", + "%s", + "%x", + "%n", + "%t", + "%%", + ); + + $date_format_code = array( + "D", + "l", + "d", + "j", + "z", + "N", + "w", + "W", + "W", + "W", + "M", + "F", + "M", + "m", + "y", + "y", + "y", + "y", + "Y", + "H", + "h", + "g", + "i", + "A", + "a", + "h:i:s A", + "H:i", + "s", + "H:i:s", + "h:i:s", + "H", + "H", + "D j M H:i:s", + "d/m/y", + "Y-m-d", + "U", + "d/m/y", + "\n", + "\t", + "%", + ); + + //Change Strftime format to Date format + $format = str_replace($str_format_code, $date_format_code, $format); + + //Convert to date + return self::date($format, $stamp, $timezone); + } + + private static function getDayNames($day, $shorten = false, $len = 1, $numeric = false) + { + switch (strtolower($day)) { + case 'sat': + case 'saturday': + $ret = 'شنبه'; + $n = 1; + break; + case 'sun': + case 'sunday': + $ret = 'یکشنبه'; + $n = 2; + break; + case 'mon': + case 'monday': + $ret = 'دوشنبه'; + $n = 3; + break; + case 'tue': + case 'tuesday': + $ret = 'سه شنبه'; + $n = 4; + break; + case 'wed': + case 'wednesday': + $ret = 'چهارشنبه'; + $n = 5; + break; + case 'thu': + case 'thursday': + $ret = 'پنجشنبه'; + $n = 6; + break; + case 'fri': + case 'friday': + $ret = 'جمعه'; + $n = 7; + break; + default: + $ret = ''; + $n = -1; + } + + return ($numeric) ? $n : (($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret); + } + + private static function getMonthNames($month, $shorten = false, $len = 3) + { + $ret = ''; + switch ($month) { + case '1': + $ret = 'فروردین'; + break; + case '2': + $ret = 'اردیبهشت'; + break; + case '3': + $ret = 'خرداد'; + break; + case '4': + $ret = 'تیر'; + break; + case '5': + $ret = 'مرداد'; + break; + case '6': + $ret = 'شهریور'; + break; + case '7': + $ret = 'مهر'; + break; + case '8': + $ret = 'آبان'; + break; + case '9': + $ret = 'آذر'; + break; + case '10': + $ret = 'دی'; + break; + case '11': + $ret = 'بهمن'; + break; + case '12': + $ret = 'اسفند'; + break; + } + + return ($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret; + } + + private static function filterArray($needle, $haystack, $always = array()) + { + foreach ($haystack as $k => $v) { + if (!in_array($v, $needle) && !in_array($v, $always)) { + unset($haystack[$k]); + } + + } + + + return $haystack; + } + + + /** + * @param $format + * @param $date + * @return array + */ + public static function parseFromFormat($format, $date) + { + // reverse engineer date formats + $keys = array( + 'Y' => array('year', '\d{4}'), + 'y' => array('year', '\d{2}'), + 'm' => array('month', '\d{2}'), + 'n' => array('month', '\d{1,2}'), + 'M' => array('month', '[A-Z][a-z]{3}'), + 'F' => array('month', '[A-Z][a-z]{2,8}'), + 'd' => array('day', '\d{2}'), + 'j' => array('day', '\d{1,2}'), + 'D' => array('day', '[A-Z][a-z]{2}'), + 'l' => array('day', '[A-Z][a-z]{6,9}'), + 'u' => array('hour', '\d{1,6}'), + 'h' => array('hour', '\d{2}'), + 'H' => array('hour', '\d{2}'), + 'g' => array('hour', '\d{1,2}'), + 'G' => array('hour', '\d{1,2}'), + 'i' => array('minute', '\d{2}'), + 's' => array('second', '\d{2}'), + ); + + // convert format string to regex + $regex = ''; + $chars = str_split($format); + foreach ($chars as $n => $char) { + $lastChar = isset($chars[$n - 1]) ? $chars[$n - 1] : ''; + $skipCurrent = '\\' == $lastChar; + if (!$skipCurrent && isset($keys[$char])) { + $regex .= '(?P<' . $keys[$char][0] . '>' . $keys[$char][1] . ')'; + } else { + if ('\\' == $char) { + $regex .= $char; + } else { + $regex .= preg_quote($char); + } + } + } + + $dt = array(); + $dt['error_count'] = 0; + // now try to match it + if (preg_match('#^' . $regex . '$#', $date, $dt)) { + foreach ($dt as $k => $v) { + if (is_int($k)) { + unset($dt[$k]); + } + } + if (!jDateTime::checkdate($dt['month'], $dt['day'], $dt['year'], false)) { + $dt['error_count'] = 1; + } + } else { + $dt['error_count'] = 1; + } + $dt['errors'] = array(); + $dt['fraction'] = ''; + $dt['warning_count'] = 0; + $dt['warnings'] = array(); + $dt['is_localtime'] = 0; + $dt['zone_type'] = 0; + $dt['zone'] = 0; + $dt['is_dst'] = ''; + + if (strlen($dt['year']) == 2) { + $now = jDate::forge('now'); + $x = $now->format('Y') - $now->format('y'); + $dt['year'] += $x; + } + + $dt['year'] = isset($dt['year']) ? (int)$dt['year'] : 0; + $dt['month'] = isset($dt['month']) ? (int)$dt['month'] : 0; + $dt['day'] = isset($dt['day']) ? (int)$dt['day'] : 0; + $dt['hour'] = isset($dt['hour']) ? (int)$dt['hour'] : 0; + $dt['minute'] = isset($dt['minute']) ? (int)$dt['minute'] : 0; + $dt['second'] = isset($dt['second']) ? (int)$dt['second'] : 0; + + return $dt; + } + + /** + * @param $format + * @param $str + * @param null $timezone + * @return \DateTime + */ + public static function createDatetimeFromFormat($format, $str, $timezone = null) + { + $pd = self::parseFromFormat($format, $str); + $gd = self::toGregorian($pd['year'], $pd['month'], $pd['day']); + $date = self::createDateTime('now', $timezone); + $date->setDate($gd[0], $gd[1], $gd[2]); + $date->setTime($pd['hour'], $pd['minute'], $pd['second']); + + return $date; + } + + /** + * @param $format + * @param $str + * @param null $timezone + * @return Carbon + */ + public static function createCarbonFromFormat($format, $str, $timezone = null) + { + $dateTime = self::createDatetimeFromFormat($format, $str, $timezone); + + return Carbon::createFromTimestamp($dateTime->getTimestamp(), $dateTime->getTimezone()); + } + + /** + * Convert Latin numbers to persian numbers + * + * @param string $string + * @return string + */ + public static function convertNumbers($string) + { + $farsi_array = array("۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"); + $english_array = array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + + return str_replace($english_array, $farsi_array, $string); + } + + /** + * @param $timestamp + * @param null $timezone + * @return \DateTime|static + */ + public static function createDateTime($timestamp = null, $timezone = null) + { + $timezone = static::createTimeZone($timezone); + + if ($timestamp === null) { + return Carbon::now($timezone); + } + + + if ($timestamp instanceof \DateTimeInterface) { + return $timestamp; + } + + if (is_string($timestamp)) { + return new \DateTime($timestamp, $timezone); + } + + if (is_numeric($timestamp)) { + return Carbon::createFromTimestamp($timestamp, $timezone); + } + + + throw new \InvalidArgumentException('timestamp is not valid'); + } + + /** + * @param null $timezone + * @return \DateTimeZone|null + */ + public static function createTimeZone($timezone = null) + { + if ($timezone instanceof \DateTimeZone) { + return $timezone; + } + + if ($timezone === null) { + return new \DateTimeZone(date_default_timezone_get()); + } + + if (is_string($timezone)) { + return new \DateTimeZone($timezone); + } + + + throw new \InvalidArgumentException('timezone is not valid'); + + } +} diff --git a/core/vendor/morilog/jalali/tests/HelperTest.php b/core/vendor/morilog/jalali/tests/HelperTest.php new file mode 100644 index 0000000..1c994a9 --- /dev/null +++ b/core/vendor/morilog/jalali/tests/HelperTest.php @@ -0,0 +1,12 @@ +assertTrue(function_exists('jdate')); + + $jdate = jdate('now'); + $this->assertTrue($jdate instanceof \Morilog\Jalali\jDate); + } +} diff --git a/core/vendor/morilog/jalali/tests/jDateTest.php b/core/vendor/morilog/jalali/tests/jDateTest.php new file mode 100644 index 0000000..2769682 --- /dev/null +++ b/core/vendor/morilog/jalali/tests/jDateTest.php @@ -0,0 +1,58 @@ +assertTrue(is_a($object, jDate::class)); + } + + public function test_it_must_foregable_and_must_work_fine() + { + $object = new jDate(); + $jDate = $object->forge('2015-06-03')->format('Y-m-d'); + + $this->assertNotNull($object->forge()); + $this->assertTrue('1394-03-13' === $jDate); + } + + public function test_it_must_reforgable_most_work_fine() + { + $object = new jDate(); + $jDate = $object->forge('2015-06-03') + ->reforge('+ 3 days') + ->format('Y-m-d'); + + $this->assertTrue('1394-03-16' === $jDate); + } + + public function test_relative_time() + { + $object = new jDate(); + $jDate = $object->forge('- 10 minutes')->ago(); + + $this->assertTrue('10 دقیقه پیش' === $jDate); + } + + public function test_format_with_convert_to_persian() + { + $object = new jDate(); + $jDate = $object->forge('2015-06-13')->format('Y-m-d'); + + $this->assertTrue('۱۳۹۴-۰۳-۲۳' === \Morilog\Jalali\jDateTime::convertNumbers($jDate)); + } + + public function test_time() + { + $time = time(); + $theTime = \Morilog\Jalali\jDate::forge($time)->time(); + + $this->assertTrue($time === $theTime); + + $theTime = \Morilog\Jalali\jDate::forge('now')->time(); + $this->assertTrue($theTime === strtotime('now')); + } +} diff --git a/core/vendor/morilog/jalali/tests/jDateTimeTest.php b/core/vendor/morilog/jalali/tests/jDateTimeTest.php new file mode 100644 index 0000000..298fefe --- /dev/null +++ b/core/vendor/morilog/jalali/tests/jDateTimeTest.php @@ -0,0 +1,117 @@ +assertTrue(jDateTime::checkDate(1391, 2, 30, true)); + $this->assertFalse(jDateTime::checkDate(1395, 13, 10, true)); + $this->assertFalse(jDateTime::checkDate(1395, 12, 31, true)); + $this->assertFalse(jDateTime::checkDate(2015, 12, 31, true)); + } + + public function testToJalali() + { + $this->assertTrue(jDateTime::toJalali(2016, 5, 7) === [1395, 2, 18]); + $this->assertFalse(jDateTime::toJalali(2015, 5, 7) === [1394, 2, 18]); + } + + public function testToGregorian() + { + $this->assertTrue(jDateTime::toGregorian(1395, 2, 18) === [2016, 5, 7]); + $this->assertFalse(jDateTime::toGregorian(1394, 2, 18) === [2015, 5, 7]); + } + + public function testIsLeapJalaliYear() + { + $this->assertTrue(jDateTime::isLeapJalaliYear(1395)); + $this->assertFalse(jDateTime::isLeapJalaliYear(1394)); + } + + public function testStrftime() + { + $this->assertTrue(jDateTime::strftime('Y-m-d', strtotime('2016-05-8')) === '1395-02-19'); + $this->assertTrue(jDateTime::convertNumbers(jDateTime::strftime('Y-m-d', + strtotime('2016-05-8'))) === '۱۳۹۵-۰۲-۱۹'); + $this->assertFalse(jDateTime::strftime('Y-m-d', strtotime('2016-05-8')) === '۱۳۹۵-۰۲-۱۹'); + } + + public function test_parseFromPersian() + { + $jalaliDate = '1393/03/27'; + $date = jDateTime::parseFromFormat('Y/m/d', $jalaliDate); + + $this->assertEquals(1393, $date['year']); + $this->assertEquals(03, $date['month']); + $this->assertEquals(27, $date['day']); + + $date = jDateTime::parseFromFormat('Y-m-d H:i:s', '1395-03-15 21:00:00'); + $this->assertEquals(21, $date['hour']); + $this->assertEquals(0, $date['minute']); + $this->assertEquals(0, $date['second']); + } + + public function testCreateDateTimeFormFormat() + { + $jdate = '1394/11/25 15:00:00'; + $gDateTime = jDatetime::createDatetimeFromFormat('Y/m/d H:i:s', $jdate); + + $this->assertTrue($gDateTime instanceof \DateTime); + + $this->assertTrue('2016-02-14 15:00:00' === $gDateTime->format('Y-m-d H:i:s')); + } + + public function testCreateCarbonFormFormat() + { + $jdate = '1394/11/25 15:00:00'; + $carbon = jDatetime::createCarbonFromFormat('Y/m/d H:i:s', $jdate); + + $this->assertTrue($carbon instanceof \Carbon\Carbon); + $this->assertTrue($carbon->day === 14); + $this->assertTrue('2016-02-14 15:00:00' === $carbon->format('Y-m-d H:i:s')); + + $jalaiDateFormatted = jDate::forge($carbon->toDateString())->format('Y-m-d H:i:s'); + $jalaiDateTimeFormatted = jDate::forge($carbon->toDateTimeString())->format('Y-m-d H:i:s'); + $this->assertFalse($jalaiDateFormatted === '1394-11-25 15:00:00'); + $this->assertTrue($jalaiDateTimeFormatted === '1394-11-25 15:00:00'); + + } + + public function testTimezone() + { + date_default_timezone_set('Asia/Tehran'); + $tehranDate = jDate::forge(); + $tehranHour = $tehranDate->format('H'); + $tehranMin = $tehranDate->format('i'); + + date_default_timezone_set('UTC'); + $utcDate = jDate::forge(); + $utcHour = $utcDate->format('H'); + $utcMin = $utcDate->format('i'); + + $tzOffset = $this->getTimeZoneOffset('Asia/Tehran', 'UTC'); + + $this->assertTrue((((($utcHour * 60) + $utcMin) * 60) - ((($tehranHour * 60) + $tehranMin) * 60)) === $tzOffset); + + } + + + private function getTimeZoneOffset($remote_tz, $origin_tz = null) + { + if ($origin_tz === null) { + if (!is_string($origin_tz = date_default_timezone_get())) { + return false; // A UTC timestamp was returned -- bail out! + } + } + $origin_dtz = new DateTimeZone($origin_tz); + $remote_dtz = new DateTimeZone($remote_tz); + $origin_dt = new DateTime("now", $origin_dtz); + $remote_dt = new DateTime("now", $remote_dtz); + $offset = $origin_dtz->getOffset($origin_dt) - $remote_dtz->getOffset($remote_dt); + + return $offset; + } +} diff --git a/core/vendor/mtdowling/cron-expression/.editorconfig b/core/vendor/mtdowling/cron-expression/.editorconfig new file mode 100644 index 0000000..1492202 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/core/vendor/mtdowling/cron-expression/CHANGELOG.md b/core/vendor/mtdowling/cron-expression/CHANGELOG.md new file mode 100644 index 0000000..8ddab90 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/CHANGELOG.md @@ -0,0 +1,36 @@ +# Change Log + +## [1.2.0] - 2017-01-22 +### Added +- Added IDE, CodeSniffer, and StyleCI.IO support + +### Changed +- Switched to PSR-4 Autoloading + +### Fixed +- 0 step expressions are handled better +- Fixed `DayOfMonth` validation to be more strict +- Typos + +## [1.1.0] - 2016-01-26 +### Added +- Support for non-hourly offset timezones +- Checks for valid expressions + +### Changed +- Max Iterations no longer hardcoded for `getRunDate()` +- Supports DateTimeImmutable for newer PHP verions + +### Fixed +- Fixed looping bug for PHP 7 when determining the last specified weekday of a month + +## [1.0.3] - 2013-11-23 +### Added +- Now supports expressions with any number of extra spaces, tabs, or newlines + +### Changed +- Using static instead of self in `CronExpression::factory` + +### Fixed +- Fixes issue [#28](https://github.com/mtdowling/cron-expression/issues/28) where PHP increments of ranges were failing due to PHP casting hyphens to 0 +- Only set default timezone if the given $currentTime is not a DateTime instance ([#34](https://github.com/mtdowling/cron-expression/issues/34)) diff --git a/core/vendor/mtdowling/cron-expression/LICENSE b/core/vendor/mtdowling/cron-expression/LICENSE new file mode 100644 index 0000000..c6d88ac --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 Michael Dowling and contributors + +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/core/vendor/mtdowling/cron-expression/README.md b/core/vendor/mtdowling/cron-expression/README.md new file mode 100644 index 0000000..c9e3bf3 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/README.md @@ -0,0 +1,71 @@ +PHP Cron Expression Parser +========================== + +[![Latest Stable Version](https://poser.pugx.org/mtdowling/cron-expression/v/stable.png)](https://packagist.org/packages/mtdowling/cron-expression) [![Total Downloads](https://poser.pugx.org/mtdowling/cron-expression/downloads.png)](https://packagist.org/packages/mtdowling/cron-expression) [![Build Status](https://secure.travis-ci.org/mtdowling/cron-expression.png)](http://travis-ci.org/mtdowling/cron-expression) + +The PHP cron expression parser can parse a CRON expression, determine if it is +due to run, calculate the next run date of the expression, and calculate the previous +run date of the expression. You can calculate dates far into the future or past by +skipping n number of matching dates. + +The parser can handle increments of ranges (e.g. */12, 2-59/3), intervals (e.g. 0-9), +lists (e.g. 1,2,3), W to find the nearest weekday for a given day of the month, L to +find the last day of the month, L to find the last given weekday of a month, and hash +(#) to find the nth weekday of a given month. + +Installing +========== + +Add the dependency to your project: + +```bash +composer require mtdowling/cron-expression +``` + +Usage +===== +```php +isDue(); +echo $cron->getNextRunDate()->format('Y-m-d H:i:s'); +echo $cron->getPreviousRunDate()->format('Y-m-d H:i:s'); + +// Works with complex expressions +$cron = Cron\CronExpression::factory('3-59/15 2,6-12 */15 1 2-5'); +echo $cron->getNextRunDate()->format('Y-m-d H:i:s'); + +// Calculate a run date two iterations into the future +$cron = Cron\CronExpression::factory('@daily'); +echo $cron->getNextRunDate(null, 2)->format('Y-m-d H:i:s'); + +// Calculate a run date relative to a specific time +$cron = Cron\CronExpression::factory('@monthly'); +echo $cron->getNextRunDate('2010-01-12 00:00:00')->format('Y-m-d H:i:s'); +``` + +CRON Expressions +================ + +A CRON expression is a string representing the schedule for a particular command to execute. The parts of a CRON schedule are as follows: + + * * * * * * + - - - - - - + | | | | | | + | | | | | + year [optional] + | | | | +----- day of week (0 - 7) (Sunday=0 or 7) + | | | +---------- month (1 - 12) + | | +--------------- day of month (1 - 31) + | +-------------------- hour (0 - 23) + +------------------------- min (0 - 59) + +Requirements +============ + +- PHP 5.3+ +- PHPUnit is required to run the unit tests +- Composer is required to run the unit tests \ No newline at end of file diff --git a/core/vendor/mtdowling/cron-expression/composer.json b/core/vendor/mtdowling/cron-expression/composer.json new file mode 100644 index 0000000..ce42d40 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/composer.json @@ -0,0 +1,28 @@ +{ + "name": "mtdowling/cron-expression", + "type": "library", + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": ["cron", "schedule"], + "license": "MIT", + "authors": [{ + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }], + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/Cron/" + } + } +} \ No newline at end of file diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/AbstractField.php b/core/vendor/mtdowling/cron-expression/src/Cron/AbstractField.php new file mode 100644 index 0000000..cd2410a --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/AbstractField.php @@ -0,0 +1,148 @@ +isIncrementsOfRanges($value)) { + return $this->isInIncrementsOfRanges($dateValue, $value); + } elseif ($this->isRange($value)) { + return $this->isInRange($dateValue, $value); + } + + return $value == '*' || $dateValue == $value; + } + + /** + * Check if a value is a range + * + * @param string $value Value to test + * + * @return bool + */ + public function isRange($value) + { + return strpos($value, '-') !== false; + } + + /** + * Check if a value is an increments of ranges + * + * @param string $value Value to test + * + * @return bool + */ + public function isIncrementsOfRanges($value) + { + return strpos($value, '/') !== false; + } + + /** + * Test if a value is within a range + * + * @param string $dateValue Set date value + * @param string $value Value to test + * + * @return bool + */ + public function isInRange($dateValue, $value) + { + $parts = array_map('trim', explode('-', $value, 2)); + + return $dateValue >= $parts[0] && $dateValue <= $parts[1]; + } + + /** + * Test if a value is within an increments of ranges (offset[-to]/step size) + * + * @param string $dateValue Set date value + * @param string $value Value to test + * + * @return bool + */ + public function isInIncrementsOfRanges($dateValue, $value) + { + $parts = array_map('trim', explode('/', $value, 2)); + $stepSize = isset($parts[1]) ? (int) $parts[1] : 0; + + if ($stepSize === 0) { + return false; + } + + if (($parts[0] == '*' || $parts[0] === '0')) { + return (int) $dateValue % $stepSize == 0; + } + + $range = explode('-', $parts[0], 2); + $offset = $range[0]; + $to = isset($range[1]) ? $range[1] : $dateValue; + // Ensure that the date value is within the range + if ($dateValue < $offset || $dateValue > $to) { + return false; + } + + if ($dateValue > $offset && 0 === $stepSize) { + return false; + } + + for ($i = $offset; $i <= $to; $i+= $stepSize) { + if ($i == $dateValue) { + return true; + } + } + + return false; + } + + /** + * Returns a range of values for the given cron expression + * + * @param string $expression The expression to evaluate + * @param int $max Maximum offset for range + * + * @return array + */ + public function getRangeForExpression($expression, $max) + { + $values = array(); + + if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) { + if (!$this->isIncrementsOfRanges($expression)) { + list ($offset, $to) = explode('-', $expression); + $stepSize = 1; + } + else { + $range = array_map('trim', explode('/', $expression, 2)); + $stepSize = isset($range[1]) ? $range[1] : 0; + $range = $range[0]; + $range = explode('-', $range, 2); + $offset = $range[0]; + $to = isset($range[1]) ? $range[1] : $max; + } + $offset = $offset == '*' ? 0 : $offset; + for ($i = $offset; $i <= $to; $i += $stepSize) { + $values[] = $i; + } + sort($values); + } + else { + $values = array($expression); + } + + return $values; + } + +} diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/CronExpression.php b/core/vendor/mtdowling/cron-expression/src/Cron/CronExpression.php new file mode 100644 index 0000000..d69b415 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/CronExpression.php @@ -0,0 +1,389 @@ + '0 0 1 1 *', + '@annually' => '0 0 1 1 *', + '@monthly' => '0 0 1 * *', + '@weekly' => '0 0 * * 0', + '@daily' => '0 0 * * *', + '@hourly' => '0 * * * *' + ); + + if (isset($mappings[$expression])) { + $expression = $mappings[$expression]; + } + + return new static($expression, $fieldFactory ?: new FieldFactory()); + } + + /** + * Validate a CronExpression. + * + * @param string $expression The CRON expression to validate. + * + * @return bool True if a valid CRON expression was passed. False if not. + * @see \Cron\CronExpression::factory + */ + public static function isValidExpression($expression) + { + try { + self::factory($expression); + } catch (InvalidArgumentException $e) { + return false; + } + + return true; + } + + /** + * Parse a CRON expression + * + * @param string $expression CRON expression (e.g. '8 * * * *') + * @param FieldFactory $fieldFactory Factory to create cron fields + */ + public function __construct($expression, FieldFactory $fieldFactory) + { + $this->fieldFactory = $fieldFactory; + $this->setExpression($expression); + } + + /** + * Set or change the CRON expression + * + * @param string $value CRON expression (e.g. 8 * * * *) + * + * @return CronExpression + * @throws \InvalidArgumentException if not a valid CRON expression + */ + public function setExpression($value) + { + $this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY); + if (count($this->cronParts) < 5) { + throw new InvalidArgumentException( + $value . ' is not a valid CRON expression' + ); + } + + foreach ($this->cronParts as $position => $part) { + $this->setPart($position, $part); + } + + return $this; + } + + /** + * Set part of the CRON expression + * + * @param int $position The position of the CRON expression to set + * @param string $value The value to set + * + * @return CronExpression + * @throws \InvalidArgumentException if the value is not valid for the part + */ + public function setPart($position, $value) + { + if (!$this->fieldFactory->getField($position)->validate($value)) { + throw new InvalidArgumentException( + 'Invalid CRON field value ' . $value . ' at position ' . $position + ); + } + + $this->cronParts[$position] = $value; + + return $this; + } + + /** + * Set max iteration count for searching next run dates + * + * @param int $maxIterationCount Max iteration count when searching for next run date + * + * @return CronExpression + */ + public function setMaxIterationCount($maxIterationCount) + { + $this->maxIterationCount = $maxIterationCount; + + return $this; + } + + /** + * Get a next run date relative to the current date or a specific date + * + * @param string|\DateTime $currentTime Relative calculation date + * @param int $nth Number of matches to skip before returning a + * matching next run date. 0, the default, will return the current + * date and time if the next run date falls on the current date and + * time. Setting this value to 1 will skip the first match and go to + * the second match. Setting this value to 2 will skip the first 2 + * matches and so on. + * @param bool $allowCurrentDate Set to TRUE to return the current date if + * it matches the cron expression. + * + * @return \DateTime + * @throws \RuntimeException on too many iterations + */ + public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false) + { + return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate); + } + + /** + * Get a previous run date relative to the current date or a specific date + * + * @param string|\DateTime $currentTime Relative calculation date + * @param int $nth Number of matches to skip before returning + * @param bool $allowCurrentDate Set to TRUE to return the + * current date if it matches the cron expression + * + * @return \DateTime + * @throws \RuntimeException on too many iterations + * @see \Cron\CronExpression::getNextRunDate + */ + public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false) + { + return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate); + } + + /** + * Get multiple run dates starting at the current date or a specific date + * + * @param int $total Set the total number of dates to calculate + * @param string|\DateTime $currentTime Relative calculation date + * @param bool $invert Set to TRUE to retrieve previous dates + * @param bool $allowCurrentDate Set to TRUE to return the + * current date if it matches the cron expression + * + * @return array Returns an array of run dates + */ + public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false) + { + $matches = array(); + for ($i = 0; $i < max(0, $total); $i++) { + try { + $matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate); + } catch (RuntimeException $e) { + break; + } + } + + return $matches; + } + + /** + * Get all or part of the CRON expression + * + * @param string $part Specify the part to retrieve or NULL to get the full + * cron schedule string. + * + * @return string|null Returns the CRON expression, a part of the + * CRON expression, or NULL if the part was specified but not found + */ + public function getExpression($part = null) + { + if (null === $part) { + return implode(' ', $this->cronParts); + } elseif (array_key_exists($part, $this->cronParts)) { + return $this->cronParts[$part]; + } + + return null; + } + + /** + * Helper method to output the full expression. + * + * @return string Full CRON expression + */ + public function __toString() + { + return $this->getExpression(); + } + + /** + * Determine if the cron is due to run based on the current date or a + * specific date. This method assumes that the current number of + * seconds are irrelevant, and should be called once per minute. + * + * @param string|\DateTime $currentTime Relative calculation date + * + * @return bool Returns TRUE if the cron is due to run or FALSE if not + */ + public function isDue($currentTime = 'now') + { + if ('now' === $currentTime) { + $currentDate = date('Y-m-d H:i'); + $currentTime = strtotime($currentDate); + } elseif ($currentTime instanceof DateTime) { + $currentDate = clone $currentTime; + // Ensure time in 'current' timezone is used + $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get())); + $currentDate = $currentDate->format('Y-m-d H:i'); + $currentTime = strtotime($currentDate); + } elseif ($currentTime instanceof DateTimeImmutable) { + $currentDate = DateTime::createFromFormat('U', $currentTime->format('U')); + $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get())); + $currentDate = $currentDate->format('Y-m-d H:i'); + $currentTime = strtotime($currentDate); + } else { + $currentTime = new DateTime($currentTime); + $currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0); + $currentDate = $currentTime->format('Y-m-d H:i'); + $currentTime = $currentTime->getTimeStamp(); + } + + try { + return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime; + } catch (Exception $e) { + return false; + } + } + + /** + * Get the next or previous run date of the expression relative to a date + * + * @param string|\DateTime $currentTime Relative calculation date + * @param int $nth Number of matches to skip before returning + * @param bool $invert Set to TRUE to go backwards in time + * @param bool $allowCurrentDate Set to TRUE to return the + * current date if it matches the cron expression + * + * @return \DateTime + * @throws \RuntimeException on too many iterations + */ + protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false) + { + if ($currentTime instanceof DateTime) { + $currentDate = clone $currentTime; + } elseif ($currentTime instanceof DateTimeImmutable) { + $currentDate = DateTime::createFromFormat('U', $currentTime->format('U')); + $currentDate->setTimezone($currentTime->getTimezone()); + } else { + $currentDate = new DateTime($currentTime ?: 'now'); + $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get())); + } + + $currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0); + $nextRun = clone $currentDate; + $nth = (int) $nth; + + // We don't have to satisfy * or null fields + $parts = array(); + $fields = array(); + foreach (self::$order as $position) { + $part = $this->getExpression($position); + if (null === $part || '*' === $part) { + continue; + } + $parts[$position] = $part; + $fields[$position] = $this->fieldFactory->getField($position); + } + + // Set a hard limit to bail on an impossible date + for ($i = 0; $i < $this->maxIterationCount; $i++) { + + foreach ($parts as $position => $part) { + $satisfied = false; + // Get the field object used to validate this part + $field = $fields[$position]; + // Check if this is singular or a list + if (strpos($part, ',') === false) { + $satisfied = $field->isSatisfiedBy($nextRun, $part); + } else { + foreach (array_map('trim', explode(',', $part)) as $listPart) { + if ($field->isSatisfiedBy($nextRun, $listPart)) { + $satisfied = true; + break; + } + } + } + + // If the field is not satisfied, then start over + if (!$satisfied) { + $field->increment($nextRun, $invert, $part); + continue 2; + } + } + + // Skip this match if needed + if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) { + $this->fieldFactory->getField(0)->increment($nextRun, $invert, isset($parts[0]) ? $parts[0] : null); + continue; + } + + return $nextRun; + } + + // @codeCoverageIgnoreStart + throw new RuntimeException('Impossible CRON expression'); + // @codeCoverageIgnoreEnd + } +} diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php b/core/vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php new file mode 100644 index 0000000..53e15bc --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php @@ -0,0 +1,173 @@ + + */ +class DayOfMonthField extends AbstractField +{ + /** + * Get the nearest day of the week for a given day in a month + * + * @param int $currentYear Current year + * @param int $currentMonth Current month + * @param int $targetDay Target day of the month + * + * @return \DateTime Returns the nearest date + */ + private static function getNearestWeekday($currentYear, $currentMonth, $targetDay) + { + $tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT); + $target = DateTime::createFromFormat('Y-m-d', "$currentYear-$currentMonth-$tday"); + $currentWeekday = (int) $target->format('N'); + + if ($currentWeekday < 6) { + return $target; + } + + $lastDayOfMonth = $target->format('t'); + + foreach (array(-1, 1, -2, 2) as $i) { + $adjusted = $targetDay + $i; + if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) { + $target->setDate($currentYear, $currentMonth, $adjusted); + if ($target->format('N') < 6 && $target->format('m') == $currentMonth) { + return $target; + } + } + } + } + + public function isSatisfiedBy(DateTime $date, $value) + { + // ? states that the field value is to be skipped + if ($value == '?') { + return true; + } + + $fieldValue = $date->format('d'); + + // Check to see if this is the last day of the month + if ($value == 'L') { + return $fieldValue == $date->format('t'); + } + + // Check to see if this is the nearest weekday to a particular value + if (strpos($value, 'W')) { + // Parse the target day + $targetDay = substr($value, 0, strpos($value, 'W')); + // Find out if the current day is the nearest day of the week + return $date->format('j') == self::getNearestWeekday( + $date->format('Y'), + $date->format('m'), + $targetDay + )->format('j'); + } + + return $this->isSatisfied($date->format('d'), $value); + } + + public function increment(DateTime $date, $invert = false) + { + if ($invert) { + $date->modify('previous day'); + $date->setTime(23, 59); + } else { + $date->modify('next day'); + $date->setTime(0, 0); + } + + return $this; + } + + /** + * Validates that the value is valid for the Day of the Month field + * Days of the month can contain values of 1-31, *, L, or ? by default. This can be augmented with lists via a ',', + * ranges via a '-', or with a '[0-9]W' to specify the closest weekday. + * + * @param string $value + * @return bool + */ + public function validate($value) + { + // Allow wildcards and a single L + if ($value === '?' || $value === '*' || $value === 'L') { + return true; + } + + // If you only contain numbers and are within 1-31 + if ((bool) preg_match('/^\d{1,2}$/', $value) && ($value >= 1 && $value <= 31)) { + return true; + } + + // If you have a -, we will deal with each of your chunks + if ((bool) preg_match('/-/', $value)) { + // We cannot have a range within a list or vice versa + if ((bool) preg_match('/,/', $value)) { + return false; + } + + $chunks = explode('-', $value); + foreach ($chunks as $chunk) { + if (!$this->validate($chunk)) { + return false; + } + } + + return true; + } + + // If you have a comma, we will deal with each value + if ((bool) preg_match('/,/', $value)) { + // We cannot have a range within a list or vice versa + if ((bool) preg_match('/-/', $value)) { + return false; + } + + $chunks = explode(',', $value); + foreach ($chunks as $chunk) { + if (!$this->validate($chunk)) { + return false; + } + } + + return true; + } + + // If you contain a /, we'll deal with it + if ((bool) preg_match('/\//', $value)) { + $chunks = explode('/', $value); + foreach ($chunks as $chunk) { + if (!$this->validate($chunk)) { + return false; + } + } + return true; + } + + // If you end in W, make sure that it has a numeric in front of it + if ((bool) preg_match('/^\d{1,2}W$/', $value)) { + return true; + } + + return false; + } +} diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php b/core/vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php new file mode 100644 index 0000000..83e2f4c --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php @@ -0,0 +1,141 @@ +convertLiterals($value); + + $currentYear = $date->format('Y'); + $currentMonth = $date->format('m'); + $lastDayOfMonth = $date->format('t'); + + // Find out if this is the last specific weekday of the month + if (strpos($value, 'L')) { + $weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L'))); + $tdate = clone $date; + $tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth); + while ($tdate->format('w') != $weekday) { + $tdateClone = new DateTime(); + $tdate = $tdateClone + ->setTimezone($tdate->getTimezone()) + ->setDate($currentYear, $currentMonth, --$lastDayOfMonth); + } + + return $date->format('j') == $lastDayOfMonth; + } + + // Handle # hash tokens + if (strpos($value, '#')) { + list($weekday, $nth) = explode('#', $value); + + // 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601 + if ($weekday === '0') { + $weekday = 7; + } + + // Validate the hash fields + if ($weekday < 0 || $weekday > 7) { + throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given"); + } + if ($nth > 5) { + throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month'); + } + // The current weekday must match the targeted weekday to proceed + if ($date->format('N') != $weekday) { + return false; + } + + $tdate = clone $date; + $tdate->setDate($currentYear, $currentMonth, 1); + $dayCount = 0; + $currentDay = 1; + while ($currentDay < $lastDayOfMonth + 1) { + if ($tdate->format('N') == $weekday) { + if (++$dayCount >= $nth) { + break; + } + } + $tdate->setDate($currentYear, $currentMonth, ++$currentDay); + } + + return $date->format('j') == $currentDay; + } + + // Handle day of the week values + if (strpos($value, '-')) { + $parts = explode('-', $value); + if ($parts[0] == '7') { + $parts[0] = '0'; + } elseif ($parts[1] == '0') { + $parts[1] = '7'; + } + $value = implode('-', $parts); + } + + // Test to see which Sunday to use -- 0 == 7 == Sunday + $format = in_array(7, str_split($value)) ? 'N' : 'w'; + $fieldValue = $date->format($format); + + return $this->isSatisfied($fieldValue, $value); + } + + public function increment(DateTime $date, $invert = false) + { + if ($invert) { + $date->modify('-1 day'); + $date->setTime(23, 59, 0); + } else { + $date->modify('+1 day'); + $date->setTime(0, 0, 0); + } + + return $this; + } + + public function validate($value) + { + $value = $this->convertLiterals($value); + + foreach (explode(',', $value) as $expr) { + if (!preg_match('/^(\*|[0-7](L?|#[1-5]))([\/\,\-][0-7]+)*$/', $expr)) { + return false; + } + } + + return true; + } + + private function convertLiterals($string) + { + return str_ireplace( + array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'), + range(0, 6), + $string + ); + } +} diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php b/core/vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php new file mode 100644 index 0000000..fa0e6fe --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php @@ -0,0 +1,57 @@ +fields[$position])) { + switch ($position) { + case 0: + $this->fields[$position] = new MinutesField(); + break; + case 1: + $this->fields[$position] = new HoursField(); + break; + case 2: + $this->fields[$position] = new DayOfMonthField(); + break; + case 3: + $this->fields[$position] = new MonthField(); + break; + case 4: + $this->fields[$position] = new DayOfWeekField(); + break; + case 5: + $this->fields[$position] = new YearField(); + break; + default: + throw new InvalidArgumentException( + $position . ' is not a valid position' + ); + } + } + + return $this->fields[$position]; + } +} diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php b/core/vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php new file mode 100644 index 0000000..be37b93 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php @@ -0,0 +1,40 @@ +isSatisfied($date->format('H'), $value); + } + + public function increment(DateTime $date, $invert = false, $parts = null) + { + // Change timezone to UTC temporarily. This will + // allow us to go back or forwards and hour even + // if DST will be changed between the hours. + if (is_null($parts) || $parts == '*') { + $timezone = $date->getTimezone(); + $date->setTimezone(new DateTimeZone('UTC')); + if ($invert) { + $date->modify('-1 hour'); + } else { + $date->modify('+1 hour'); + } + $date->setTimezone($timezone); + + $date->setTime($date->format('H'), $invert ? 59 : 0); + return $this; + } + + $parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts); + $hours = array(); + foreach ($parts as $part) { + $hours = array_merge($hours, $this->getRangeForExpression($part, 23)); + } + + $current_hour = $date->format('H'); + $position = $invert ? count($hours) - 1 : 0; + if (count($hours) > 1) { + for ($i = 0; $i < count($hours) - 1; $i++) { + if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) || + ($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) { + $position = $invert ? $i : $i + 1; + break; + } + } + } + + $hour = $hours[$position]; + if ((!$invert && $date->format('H') >= $hour) || ($invert && $date->format('H') <= $hour)) { + $date->modify(($invert ? '-' : '+') . '1 day'); + $date->setTime($invert ? 23 : 0, $invert ? 59 : 0); + } + else { + $date->setTime($hour, $invert ? 59 : 0); + } + + return $this; + } + + public function validate($value) + { + return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value); + } +} diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/MinutesField.php b/core/vendor/mtdowling/cron-expression/src/Cron/MinutesField.php new file mode 100644 index 0000000..d8432b5 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/MinutesField.php @@ -0,0 +1,62 @@ +isSatisfied($date->format('i'), $value); + } + + public function increment(DateTime $date, $invert = false, $parts = null) + { + if (is_null($parts)) { + if ($invert) { + $date->modify('-1 minute'); + } else { + $date->modify('+1 minute'); + } + return $this; + } + + $parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts); + $minutes = array(); + foreach ($parts as $part) { + $minutes = array_merge($minutes, $this->getRangeForExpression($part, 59)); + } + + $current_minute = $date->format('i'); + $position = $invert ? count($minutes) - 1 : 0; + if (count($minutes) > 1) { + for ($i = 0; $i < count($minutes) - 1; $i++) { + if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) || + ($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) { + $position = $invert ? $i : $i + 1; + break; + } + } + } + + if ((!$invert && $current_minute >= $minutes[$position]) || ($invert && $current_minute <= $minutes[$position])) { + $date->modify(($invert ? '-' : '+') . '1 hour'); + $date->setTime($date->format('H'), $invert ? 59 : 0); + } + else { + $date->setTime($date->format('H'), $minutes[$position]); + } + + return $this; + } + + public function validate($value) + { + return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value); + } +} diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/MonthField.php b/core/vendor/mtdowling/cron-expression/src/Cron/MonthField.php new file mode 100644 index 0000000..0205c17 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/MonthField.php @@ -0,0 +1,44 @@ +isSatisfied($date->format('m'), $value); + } + + public function increment(DateTime $date, $invert = false) + { + if ($invert) { + $date->modify('last day of previous month'); + $date->setTime(23, 59); + } else { + $date->modify('first day of next month'); + $date->setTime(0, 0); + } + + return $this; + } + + public function validate($value) + { + return (bool) preg_match('/^[\*,\/\-0-9A-Z]+$/', $value); + } +} diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/YearField.php b/core/vendor/mtdowling/cron-expression/src/Cron/YearField.php new file mode 100644 index 0000000..db244fb --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/src/Cron/YearField.php @@ -0,0 +1,37 @@ +isSatisfied($date->format('Y'), $value); + } + + public function increment(DateTime $date, $invert = false) + { + if ($invert) { + $date->modify('-1 year'); + $date->setDate($date->format('Y'), 12, 31); + $date->setTime(23, 59, 0); + } else { + $date->modify('+1 year'); + $date->setDate($date->format('Y'), 1, 1); + $date->setTime(0, 0, 0); + } + + return $this; + } + + public function validate($value) + { + return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/AbstractFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/AbstractFieldTest.php new file mode 100644 index 0000000..a1d653b --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/AbstractFieldTest.php @@ -0,0 +1,86 @@ + + */ +class AbstractFieldTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\AbstractField::isRange + */ + public function testTestsIfRange() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->isRange('1-2')); + $this->assertFalse($f->isRange('2')); + } + + /** + * @covers Cron\AbstractField::isIncrementsOfRanges + */ + public function testTestsIfIncrementsOfRanges() + { + $f = new DayOfWeekField(); + $this->assertFalse($f->isIncrementsOfRanges('1-2')); + $this->assertTrue($f->isIncrementsOfRanges('1/2')); + $this->assertTrue($f->isIncrementsOfRanges('*/2')); + $this->assertTrue($f->isIncrementsOfRanges('3-12/2')); + } + + /** + * @covers Cron\AbstractField::isInRange + */ + public function testTestsIfInRange() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->isInRange('1', '1-2')); + $this->assertTrue($f->isInRange('2', '1-2')); + $this->assertTrue($f->isInRange('5', '4-12')); + $this->assertFalse($f->isInRange('3', '4-12')); + $this->assertFalse($f->isInRange('13', '4-12')); + } + + /** + * @covers Cron\AbstractField::isInIncrementsOfRanges + */ + public function testTestsIfInIncrementsOfRanges() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->isInIncrementsOfRanges('3', '3-59/2')); + $this->assertTrue($f->isInIncrementsOfRanges('13', '3-59/2')); + $this->assertTrue($f->isInIncrementsOfRanges('15', '3-59/2')); + $this->assertTrue($f->isInIncrementsOfRanges('14', '*/2')); + $this->assertFalse($f->isInIncrementsOfRanges('2', '3-59/13')); + $this->assertFalse($f->isInIncrementsOfRanges('14', '*/13')); + $this->assertFalse($f->isInIncrementsOfRanges('14', '3-59/2')); + $this->assertFalse($f->isInIncrementsOfRanges('3', '2-59')); + $this->assertFalse($f->isInIncrementsOfRanges('3', '2')); + $this->assertFalse($f->isInIncrementsOfRanges('3', '*')); + $this->assertFalse($f->isInIncrementsOfRanges('0', '*/0')); + $this->assertFalse($f->isInIncrementsOfRanges('1', '*/0')); + + $this->assertTrue($f->isInIncrementsOfRanges('4', '4/10')); + $this->assertTrue($f->isInIncrementsOfRanges('14', '4/10')); + $this->assertTrue($f->isInIncrementsOfRanges('34', '4/10')); + } + + /** + * @covers Cron\AbstractField::isSatisfied + */ + public function testTestsIfSatisfied() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->isSatisfied('12', '3-13')); + $this->assertTrue($f->isSatisfied('15', '3-59/12')); + $this->assertTrue($f->isSatisfied('12', '*')); + $this->assertTrue($f->isSatisfied('12', '12')); + $this->assertFalse($f->isSatisfied('12', '3-11')); + $this->assertFalse($f->isSatisfied('12', '3-59/13')); + $this->assertFalse($f->isSatisfied('12', '11')); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/CronExpressionTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/CronExpressionTest.php new file mode 100644 index 0000000..f6fedb9 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/CronExpressionTest.php @@ -0,0 +1,414 @@ + + */ +class CronExpressionTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\CronExpression::factory + */ + public function testFactoryRecognizesTemplates() + { + $this->assertEquals('0 0 1 1 *', CronExpression::factory('@annually')->getExpression()); + $this->assertEquals('0 0 1 1 *', CronExpression::factory('@yearly')->getExpression()); + $this->assertEquals('0 0 * * 0', CronExpression::factory('@weekly')->getExpression()); + } + + /** + * @covers Cron\CronExpression::__construct + * @covers Cron\CronExpression::getExpression + * @covers Cron\CronExpression::__toString + */ + public function testParsesCronSchedule() + { + // '2010-09-10 12:00:00' + $cron = CronExpression::factory('1 2-4 * 4,5,6 */3'); + $this->assertEquals('1', $cron->getExpression(CronExpression::MINUTE)); + $this->assertEquals('2-4', $cron->getExpression(CronExpression::HOUR)); + $this->assertEquals('*', $cron->getExpression(CronExpression::DAY)); + $this->assertEquals('4,5,6', $cron->getExpression(CronExpression::MONTH)); + $this->assertEquals('*/3', $cron->getExpression(CronExpression::WEEKDAY)); + $this->assertEquals('1 2-4 * 4,5,6 */3', $cron->getExpression()); + $this->assertEquals('1 2-4 * 4,5,6 */3', (string) $cron); + $this->assertNull($cron->getExpression('foo')); + + try { + $cron = CronExpression::factory('A 1 2 3 4'); + $this->fail('Validation exception not thrown'); + } catch (InvalidArgumentException $e) { + } + } + + /** + * @covers Cron\CronExpression::__construct + * @covers Cron\CronExpression::getExpression + * @dataProvider scheduleWithDifferentSeparatorsProvider + */ + public function testParsesCronScheduleWithAnySpaceCharsAsSeparators($schedule, array $expected) + { + $cron = CronExpression::factory($schedule); + $this->assertEquals($expected[0], $cron->getExpression(CronExpression::MINUTE)); + $this->assertEquals($expected[1], $cron->getExpression(CronExpression::HOUR)); + $this->assertEquals($expected[2], $cron->getExpression(CronExpression::DAY)); + $this->assertEquals($expected[3], $cron->getExpression(CronExpression::MONTH)); + $this->assertEquals($expected[4], $cron->getExpression(CronExpression::WEEKDAY)); + $this->assertEquals($expected[5], $cron->getExpression(CronExpression::YEAR)); + } + + /** + * Data provider for testParsesCronScheduleWithAnySpaceCharsAsSeparators + * + * @return array + */ + public static function scheduleWithDifferentSeparatorsProvider() + { + return array( + array("*\t*\t*\t*\t*\t*", array('*', '*', '*', '*', '*', '*')), + array("* * * * * *", array('*', '*', '*', '*', '*', '*')), + array("* \t * \t * \t * \t * \t *", array('*', '*', '*', '*', '*', '*')), + array("*\t \t*\t \t*\t \t*\t \t*\t \t*", array('*', '*', '*', '*', '*', '*')), + ); + } + + /** + * @covers Cron\CronExpression::__construct + * @covers Cron\CronExpression::setExpression + * @covers Cron\CronExpression::setPart + * @expectedException InvalidArgumentException + */ + public function testInvalidCronsWillFail() + { + // Only four values + $cron = CronExpression::factory('* * * 1'); + } + + /** + * @covers Cron\CronExpression::setPart + * @expectedException InvalidArgumentException + */ + public function testInvalidPartsWillFail() + { + // Only four values + $cron = CronExpression::factory('* * * * *'); + $cron->setPart(1, 'abc'); + } + + /** + * Data provider for cron schedule + * + * @return array + */ + public function scheduleProvider() + { + return array( + array('*/2 */2 * * *', '2015-08-10 21:47:27', '2015-08-10 22:00:00', false), + array('* * * * *', '2015-08-10 21:50:37', '2015-08-10 21:50:00', true), + array('* 20,21,22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true), + // Handles CSV values + array('* 20,22 * * *', '2015-08-10 21:50:00', '2015-08-10 22:00:00', false), + // CSV values can be complex + array('* 5,21-22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true), + array('7-9 * */9 * *', '2015-08-10 22:02:33', '2015-08-18 00:07:00', false), + // 15th minute, of the second hour, every 15 days, in January, every Friday + array('1 * * * 7', '2015-08-10 21:47:27', '2015-08-16 00:01:00', false), + // Test with exact times + array('47 21 * * *', strtotime('2015-08-10 21:47:30'), '2015-08-10 21:47:00', true), + // Test Day of the week (issue #1) + // According cron implementation, 0|7 = sunday, 1 => monday, etc + array('* * * * 0', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), + array('* * * * 7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), + array('* * * * 1', strtotime('2011-06-15 23:09:00'), '2011-06-20 00:00:00', false), + // Should return the sunday date as 7 equals 0 + array('0 0 * * MON,SUN', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), + array('0 0 * * 1,7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), + array('0 0 * * 0-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false), + array('0 0 * * 7-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false), + array('0 0 * * 4-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false), + array('0 0 * * 7-3', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), + array('0 0 * * 3-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false), + array('0 0 * * 3-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false), + // Test lists of values and ranges (Abhoryo) + array('0 0 * * 2-7', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false), + array('0 0 * * 0,2-6', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false), + array('0 0 * * 2-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false), + array('0 0 * * 4-7', strtotime('2011-07-19 00:00:00'), '2011-07-21 00:00:00', false), + // Test increments of ranges + array('0-12/4 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true), + array('4-59/2 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true), + array('4-59/2 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:06:00', true), + array('4-59/3 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:07:00', false), + //array('0 0 * * 0,2-6', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false), + // Test Day of the Week and the Day of the Month (issue #1) + array('0 0 1 1 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false), + array('0 0 1 JAN 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false), + array('0 0 1 * 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false), + array('0 0 L * *', strtotime('2011-07-15 00:00:00'), '2011-07-31 00:00:00', false), + // Test the W day of the week modifier for day of the month field + array('0 0 2W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true), + array('0 0 1W * *', strtotime('2011-05-01 00:00:00'), '2011-05-02 00:00:00', false), + array('0 0 1W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true), + array('0 0 3W * *', strtotime('2011-07-01 00:00:00'), '2011-07-04 00:00:00', false), + array('0 0 16W * *', strtotime('2011-07-01 00:00:00'), '2011-07-15 00:00:00', false), + array('0 0 28W * *', strtotime('2011-07-01 00:00:00'), '2011-07-28 00:00:00', false), + array('0 0 30W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false), + array('0 0 31W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false), + // Test the year field + array('* * * * * 2012', strtotime('2011-05-01 00:00:00'), '2012-01-01 00:00:00', false), + // Test the last weekday of a month + array('* * * * 5L', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false), + array('* * * * 6L', strtotime('2011-07-01 00:00:00'), '2011-07-30 00:00:00', false), + array('* * * * 7L', strtotime('2011-07-01 00:00:00'), '2011-07-31 00:00:00', false), + array('* * * * 1L', strtotime('2011-07-24 00:00:00'), '2011-07-25 00:00:00', false), + array('* * * * TUEL', strtotime('2011-07-24 00:00:00'), '2011-07-26 00:00:00', false), + array('* * * 1 5L', strtotime('2011-12-25 00:00:00'), '2012-01-27 00:00:00', false), + // Test the hash symbol for the nth weekday of a given month + array('* * * * 5#2', strtotime('2011-07-01 00:00:00'), '2011-07-08 00:00:00', false), + array('* * * * 5#1', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true), + array('* * * * 3#4', strtotime('2011-07-01 00:00:00'), '2011-07-27 00:00:00', false), + ); + } + + /** + * @covers Cron\CronExpression::isDue + * @covers Cron\CronExpression::getNextRunDate + * @covers Cron\DayOfMonthField + * @covers Cron\DayOfWeekField + * @covers Cron\MinutesField + * @covers Cron\HoursField + * @covers Cron\MonthField + * @covers Cron\YearField + * @covers Cron\CronExpression::getRunDate + * @dataProvider scheduleProvider + */ + public function testDeterminesIfCronIsDue($schedule, $relativeTime, $nextRun, $isDue) + { + $relativeTimeString = is_int($relativeTime) ? date('Y-m-d H:i:s', $relativeTime) : $relativeTime; + + // Test next run date + $cron = CronExpression::factory($schedule); + if (is_string($relativeTime)) { + $relativeTime = new DateTime($relativeTime); + } elseif (is_int($relativeTime)) { + $relativeTime = date('Y-m-d H:i:s', $relativeTime); + } + $this->assertEquals($isDue, $cron->isDue($relativeTime)); + $next = $cron->getNextRunDate($relativeTime, 0, true); + $this->assertEquals(new DateTime($nextRun), $next); + } + + /** + * @covers Cron\CronExpression::isDue + */ + public function testIsDueHandlesDifferentDates() + { + $cron = CronExpression::factory('* * * * *'); + $this->assertTrue($cron->isDue()); + $this->assertTrue($cron->isDue('now')); + $this->assertTrue($cron->isDue(new DateTime('now'))); + $this->assertTrue($cron->isDue(date('Y-m-d H:i'))); + } + + /** + * @covers Cron\CronExpression::isDue + */ + public function testIsDueHandlesDifferentTimezones() + { + $cron = CronExpression::factory('0 15 * * 3'); //Wednesday at 15:00 + $date = '2014-01-01 15:00'; //Wednesday + $utc = new DateTimeZone('UTC'); + $amsterdam = new DateTimeZone('Europe/Amsterdam'); + $tokyo = new DateTimeZone('Asia/Tokyo'); + + date_default_timezone_set('UTC'); + $this->assertTrue($cron->isDue(new DateTime($date, $utc))); + $this->assertFalse($cron->isDue(new DateTime($date, $amsterdam))); + $this->assertFalse($cron->isDue(new DateTime($date, $tokyo))); + + date_default_timezone_set('Europe/Amsterdam'); + $this->assertFalse($cron->isDue(new DateTime($date, $utc))); + $this->assertTrue($cron->isDue(new DateTime($date, $amsterdam))); + $this->assertFalse($cron->isDue(new DateTime($date, $tokyo))); + + date_default_timezone_set('Asia/Tokyo'); + $this->assertFalse($cron->isDue(new DateTime($date, $utc))); + $this->assertFalse($cron->isDue(new DateTime($date, $amsterdam))); + $this->assertTrue($cron->isDue(new DateTime($date, $tokyo))); + } + + /** + * @covers Cron\CronExpression::getPreviousRunDate + */ + public function testCanGetPreviousRunDates() + { + $cron = CronExpression::factory('* * * * *'); + $next = $cron->getNextRunDate('now'); + $two = $cron->getNextRunDate('now', 1); + $this->assertEquals($next, $cron->getPreviousRunDate($two)); + + $cron = CronExpression::factory('* */2 * * *'); + $next = $cron->getNextRunDate('now'); + $two = $cron->getNextRunDate('now', 1); + $this->assertEquals($next, $cron->getPreviousRunDate($two)); + + $cron = CronExpression::factory('* * * */2 *'); + $next = $cron->getNextRunDate('now'); + $two = $cron->getNextRunDate('now', 1); + $this->assertEquals($next, $cron->getPreviousRunDate($two)); + } + + /** + * @covers Cron\CronExpression::getMultipleRunDates + */ + public function testProvidesMultipleRunDates() + { + $cron = CronExpression::factory('*/2 * * * *'); + $this->assertEquals(array( + new DateTime('2008-11-09 00:00:00'), + new DateTime('2008-11-09 00:02:00'), + new DateTime('2008-11-09 00:04:00'), + new DateTime('2008-11-09 00:06:00') + ), $cron->getMultipleRunDates(4, '2008-11-09 00:00:00', false, true)); + } + + /** + * @covers Cron\CronExpression::getMultipleRunDates + * @covers Cron\CronExpression::setMaxIterationCount + */ + public function testProvidesMultipleRunDatesForTheFarFuture() { + // Fails with the default 1000 iteration limit + $cron = CronExpression::factory('0 0 12 1 * */2'); + $cron->setMaxIterationCount(2000); + $this->assertEquals(array( + new DateTime('2016-01-12 00:00:00'), + new DateTime('2018-01-12 00:00:00'), + new DateTime('2020-01-12 00:00:00'), + new DateTime('2022-01-12 00:00:00'), + new DateTime('2024-01-12 00:00:00'), + new DateTime('2026-01-12 00:00:00'), + new DateTime('2028-01-12 00:00:00'), + new DateTime('2030-01-12 00:00:00'), + new DateTime('2032-01-12 00:00:00'), + ), $cron->getMultipleRunDates(9, '2015-04-28 00:00:00', false, true)); + } + + /** + * @covers Cron\CronExpression + */ + public function testCanIterateOverNextRuns() + { + $cron = CronExpression::factory('@weekly'); + $nextRun = $cron->getNextRunDate("2008-11-09 08:00:00"); + $this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00")); + + // true is cast to 1 + $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", true, true); + $this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00")); + + // You can iterate over them + $nextRun = $cron->getNextRunDate($cron->getNextRunDate("2008-11-09 00:00:00", 1, true), 1, true); + $this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00")); + + // You can skip more than one + $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 2, true); + $this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00")); + $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 3, true); + $this->assertEquals($nextRun, new DateTime("2008-11-30 00:00:00")); + } + + /** + * @covers Cron\CronExpression::getRunDate + */ + public function testSkipsCurrentDateByDefault() + { + $cron = CronExpression::factory('* * * * *'); + $current = new DateTime('now'); + $next = $cron->getNextRunDate($current); + $nextPrev = $cron->getPreviousRunDate($next); + $this->assertEquals($current->format('Y-m-d H:i:00'), $nextPrev->format('Y-m-d H:i:s')); + } + + /** + * @covers Cron\CronExpression::getRunDate + * @ticket 7 + */ + public function testStripsForSeconds() + { + $cron = CronExpression::factory('* * * * *'); + $current = new DateTime('2011-09-27 10:10:54'); + $this->assertEquals('2011-09-27 10:11:00', $cron->getNextRunDate($current)->format('Y-m-d H:i:s')); + } + + /** + * @covers Cron\CronExpression::getRunDate + */ + public function testFixesPhpBugInDateIntervalMonth() + { + $cron = CronExpression::factory('0 0 27 JAN *'); + $this->assertEquals('2011-01-27 00:00:00', $cron->getPreviousRunDate('2011-08-22 00:00:00')->format('Y-m-d H:i:s')); + } + + public function testIssue29() + { + $cron = CronExpression::factory('@weekly'); + $this->assertEquals( + '2013-03-10 00:00:00', + $cron->getPreviousRunDate('2013-03-17 00:00:00')->format('Y-m-d H:i:s') + ); + } + + /** + * @see https://github.com/mtdowling/cron-expression/issues/20 + */ + public function testIssue20() { + $e = CronExpression::factory('* * * * MON#1'); + $this->assertTrue($e->isDue(new DateTime('2014-04-07 00:00:00'))); + $this->assertFalse($e->isDue(new DateTime('2014-04-14 00:00:00'))); + $this->assertFalse($e->isDue(new DateTime('2014-04-21 00:00:00'))); + + $e = CronExpression::factory('* * * * SAT#2'); + $this->assertFalse($e->isDue(new DateTime('2014-04-05 00:00:00'))); + $this->assertTrue($e->isDue(new DateTime('2014-04-12 00:00:00'))); + $this->assertFalse($e->isDue(new DateTime('2014-04-19 00:00:00'))); + + $e = CronExpression::factory('* * * * SUN#3'); + $this->assertFalse($e->isDue(new DateTime('2014-04-13 00:00:00'))); + $this->assertTrue($e->isDue(new DateTime('2014-04-20 00:00:00'))); + $this->assertFalse($e->isDue(new DateTime('2014-04-27 00:00:00'))); + } + + /** + * @covers Cron\CronExpression::getRunDate + */ + public function testKeepOriginalTime() + { + $now = new \DateTime; + $strNow = $now->format(DateTime::ISO8601); + $cron = CronExpression::factory('0 0 * * *'); + $cron->getPreviousRunDate($now); + $this->assertEquals($strNow, $now->format(DateTime::ISO8601)); + } + + /** + * @covers Cron\CronExpression::__construct + * @covers Cron\CronExpression::factory + * @covers Cron\CronExpression::isValidExpression + * @covers Cron\CronExpression::setExpression + * @covers Cron\CronExpression::setPart + */ + public function testValidationWorks() + { + // Invalid. Only four values + $this->assertFalse(CronExpression::isValidExpression('* * * 1')); + // Valid + $this->assertTrue(CronExpression::isValidExpression('* * * * 1')); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfMonthFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfMonthFieldTest.php new file mode 100644 index 0000000..eff0455 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfMonthFieldTest.php @@ -0,0 +1,61 @@ + + */ +class DayOfMonthFieldTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\DayOfMonthField::validate + */ + public function testValidatesField() + { + $f = new DayOfMonthField(); + $this->assertTrue($f->validate('1')); + $this->assertTrue($f->validate('*')); + $this->assertTrue($f->validate('5W,L')); + $this->assertFalse($f->validate('1.')); + } + + /** + * @covers Cron\DayOfMonthField::isSatisfiedBy + */ + public function testChecksIfSatisfied() + { + $f = new DayOfMonthField(); + $this->assertTrue($f->isSatisfiedBy(new DateTime(), '?')); + } + + /** + * @covers Cron\DayOfMonthField::increment + */ + public function testIncrementsDate() + { + $d = new DateTime('2011-03-15 11:15:00'); + $f = new DayOfMonthField(); + $f->increment($d); + $this->assertEquals('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s')); + + $d = new DateTime('2011-03-15 11:15:00'); + $f->increment($d, true); + $this->assertEquals('2011-03-14 23:59:00', $d->format('Y-m-d H:i:s')); + } + + /** + * Day of the month cannot accept a 0 value, it must be between 1 and 31 + * See Github issue #120 + * + * @since 2017-01-22 + */ + public function testDoesNotAccept0Date() + { + $f = new DayOfMonthField(); + $this->assertFalse($f->validate(0)); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfWeekFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfWeekFieldTest.php new file mode 100644 index 0000000..182d5e9 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfWeekFieldTest.php @@ -0,0 +1,117 @@ + + */ +class DayOfWeekFieldTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\DayOfWeekField::validate + */ + public function testValidatesField() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->validate('1')); + $this->assertTrue($f->validate('*')); + $this->assertTrue($f->validate('*/3,1,1-12')); + $this->assertTrue($f->validate('SUN-2')); + $this->assertFalse($f->validate('1.')); + } + + /** + * @covers Cron\DayOfWeekField::isSatisfiedBy + */ + public function testChecksIfSatisfied() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->isSatisfiedBy(new DateTime(), '?')); + } + + /** + * @covers Cron\DayOfWeekField::increment + */ + public function testIncrementsDate() + { + $d = new DateTime('2011-03-15 11:15:00'); + $f = new DayOfWeekField(); + $f->increment($d); + $this->assertEquals('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s')); + + $d = new DateTime('2011-03-15 11:15:00'); + $f->increment($d, true); + $this->assertEquals('2011-03-14 23:59:00', $d->format('Y-m-d H:i:s')); + } + + /** + * @covers Cron\DayOfWeekField::isSatisfiedBy + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Weekday must be a value between 0 and 7. 12 given + */ + public function testValidatesHashValueWeekday() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->isSatisfiedBy(new DateTime(), '12#1')); + } + + /** + * @covers Cron\DayOfWeekField::isSatisfiedBy + * @expectedException InvalidArgumentException + * @expectedExceptionMessage There are never more than 5 of a given weekday in a month + */ + public function testValidatesHashValueNth() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->isSatisfiedBy(new DateTime(), '3#6')); + } + + /** + * @covers Cron\DayOfWeekField::validate + */ + public function testValidateWeekendHash() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->validate('MON#1')); + $this->assertTrue($f->validate('TUE#2')); + $this->assertTrue($f->validate('WED#3')); + $this->assertTrue($f->validate('THU#4')); + $this->assertTrue($f->validate('FRI#5')); + $this->assertTrue($f->validate('SAT#1')); + $this->assertTrue($f->validate('SUN#3')); + $this->assertTrue($f->validate('MON#1,MON#3')); + } + + /** + * @covers Cron\DayOfWeekField::isSatisfiedBy + */ + public function testHandlesZeroAndSevenDayOfTheWeekValues() + { + $f = new DayOfWeekField(); + $this->assertTrue($f->isSatisfiedBy(new DateTime('2011-09-04 00:00:00'), '0-2')); + $this->assertTrue($f->isSatisfiedBy(new DateTime('2011-09-04 00:00:00'), '6-0')); + + $this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), 'SUN')); + $this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), 'SUN#3')); + $this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), '0#3')); + $this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), '7#3')); + } + + /** + * @see https://github.com/mtdowling/cron-expression/issues/47 + */ + public function testIssue47() { + $f = new DayOfWeekField(); + $this->assertFalse($f->validate('mon,')); + $this->assertFalse($f->validate('mon-')); + $this->assertFalse($f->validate('*/2,')); + $this->assertFalse($f->validate('-mon')); + $this->assertFalse($f->validate(',1')); + $this->assertFalse($f->validate('*-')); + $this->assertFalse($f->validate(',-')); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/FieldFactoryTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/FieldFactoryTest.php new file mode 100644 index 0000000..f34cc9b --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/FieldFactoryTest.php @@ -0,0 +1,43 @@ + + */ +class FieldFactoryTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\FieldFactory::getField + */ + public function testRetrievesFieldInstances() + { + $mappings = array( + 0 => 'Cron\MinutesField', + 1 => 'Cron\HoursField', + 2 => 'Cron\DayOfMonthField', + 3 => 'Cron\MonthField', + 4 => 'Cron\DayOfWeekField', + 5 => 'Cron\YearField' + ); + + $f = new FieldFactory(); + + foreach ($mappings as $position => $class) { + $this->assertEquals($class, get_class($f->getField($position))); + } + } + + /** + * @covers Cron\FieldFactory::getField + * @expectedException InvalidArgumentException + */ + public function testValidatesFieldPosition() + { + $f = new FieldFactory(); + $f->getField(-1); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/HoursFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/HoursFieldTest.php new file mode 100644 index 0000000..d2d8a22 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/HoursFieldTest.php @@ -0,0 +1,75 @@ + + */ +class HoursFieldTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\HoursField::validate + */ + public function testValidatesField() + { + $f = new HoursField(); + $this->assertTrue($f->validate('1')); + $this->assertTrue($f->validate('*')); + $this->assertTrue($f->validate('*/3,1,1-12')); + } + + /** + * @covers Cron\HoursField::increment + */ + public function testIncrementsDate() + { + $d = new DateTime('2011-03-15 11:15:00'); + $f = new HoursField(); + $f->increment($d); + $this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s')); + + $d->setTime(11, 15, 0); + $f->increment($d, true); + $this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s')); + } + + /** + * @covers Cron\HoursField::increment + */ + public function testIncrementsDateWithThirtyMinuteOffsetTimezone() + { + $tz = date_default_timezone_get(); + date_default_timezone_set('America/St_Johns'); + $d = new DateTime('2011-03-15 11:15:00'); + $f = new HoursField(); + $f->increment($d); + $this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s')); + + $d->setTime(11, 15, 0); + $f->increment($d, true); + $this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s')); + date_default_timezone_set($tz); + } + + /** + * @covers Cron\HoursField::increment + */ + public function testIncrementDateWithFifteenMinuteOffsetTimezone() + { + $tz = date_default_timezone_get(); + date_default_timezone_set('Asia/Kathmandu'); + $d = new DateTime('2011-03-15 11:15:00'); + $f = new HoursField(); + $f->increment($d); + $this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s')); + + $d->setTime(11, 15, 0); + $f->increment($d, true); + $this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s')); + date_default_timezone_set($tz); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/MinutesFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/MinutesFieldTest.php new file mode 100644 index 0000000..af3fef7 --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/MinutesFieldTest.php @@ -0,0 +1,37 @@ + + */ +class MinutesFieldTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\MinutesField::validate + */ + public function testValidatesField() + { + $f = new MinutesField(); + $this->assertTrue($f->validate('1')); + $this->assertTrue($f->validate('*')); + $this->assertTrue($f->validate('*/3,1,1-12')); + } + + /** + * @covers Cron\MinutesField::increment + */ + public function testIncrementsDate() + { + $d = new DateTime('2011-03-15 11:15:00'); + $f = new MinutesField(); + $f->increment($d); + $this->assertEquals('2011-03-15 11:16:00', $d->format('Y-m-d H:i:s')); + $f->increment($d, true); + $this->assertEquals('2011-03-15 11:15:00', $d->format('Y-m-d H:i:s')); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/MonthFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/MonthFieldTest.php new file mode 100644 index 0000000..2d9b0ad --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/MonthFieldTest.php @@ -0,0 +1,81 @@ + + */ +class MonthFieldTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\MonthField::validate + */ + public function testValidatesField() + { + $f = new MonthField(); + $this->assertTrue($f->validate('12')); + $this->assertTrue($f->validate('*')); + $this->assertTrue($f->validate('*/10,2,1-12')); + $this->assertFalse($f->validate('1.fix-regexp')); + } + + /** + * @covers Cron\MonthField::increment + */ + public function testIncrementsDate() + { + $d = new DateTime('2011-03-15 11:15:00'); + $f = new MonthField(); + $f->increment($d); + $this->assertEquals('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s')); + + $d = new DateTime('2011-03-15 11:15:00'); + $f->increment($d, true); + $this->assertEquals('2011-02-28 23:59:00', $d->format('Y-m-d H:i:s')); + } + + /** + * @covers Cron\MonthField::increment + */ + public function testIncrementsDateWithThirtyMinuteTimezone() + { + $tz = date_default_timezone_get(); + date_default_timezone_set('America/St_Johns'); + $d = new DateTime('2011-03-31 11:59:59'); + $f = new MonthField(); + $f->increment($d); + $this->assertEquals('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s')); + + $d = new DateTime('2011-03-15 11:15:00'); + $f->increment($d, true); + $this->assertEquals('2011-02-28 23:59:00', $d->format('Y-m-d H:i:s')); + date_default_timezone_set($tz); + } + + + /** + * @covers Cron\MonthField::increment + */ + public function testIncrementsYearAsNeeded() + { + $f = new MonthField(); + $d = new DateTime('2011-12-15 00:00:00'); + $f->increment($d); + $this->assertEquals('2012-01-01 00:00:00', $d->format('Y-m-d H:i:s')); + } + + /** + * @covers Cron\MonthField::increment + */ + public function testDecrementsYearAsNeeded() + { + $f = new MonthField(); + $d = new DateTime('2011-01-15 00:00:00'); + $f->increment($d, true); + $this->assertEquals('2010-12-31 23:59:00', $d->format('Y-m-d H:i:s')); + } +} diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/YearFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/YearFieldTest.php new file mode 100644 index 0000000..b5059ec --- /dev/null +++ b/core/vendor/mtdowling/cron-expression/tests/Cron/YearFieldTest.php @@ -0,0 +1,37 @@ + + */ +class YearFieldTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers Cron\YearField::validate + */ + public function testValidatesField() + { + $f = new YearField(); + $this->assertTrue($f->validate('2011')); + $this->assertTrue($f->validate('*')); + $this->assertTrue($f->validate('*/10,2012,1-12')); + } + + /** + * @covers Cron\YearField::increment + */ + public function testIncrementsDate() + { + $d = new DateTime('2011-03-15 11:15:00'); + $f = new YearField(); + $f->increment($d); + $this->assertEquals('2012-01-01 00:00:00', $d->format('Y-m-d H:i:s')); + $f->increment($d, true); + $this->assertEquals('2011-12-31 23:59:00', $d->format('Y-m-d H:i:s')); + } +} diff --git a/core/vendor/nesbot/carbon/LICENSE b/core/vendor/nesbot/carbon/LICENSE new file mode 100644 index 0000000..6de45eb --- /dev/null +++ b/core/vendor/nesbot/carbon/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) Brian Nesbitt + +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/core/vendor/nesbot/carbon/bin/upgrade-carbon b/core/vendor/nesbot/carbon/bin/upgrade-carbon new file mode 100644 index 0000000..49c4c9a --- /dev/null +++ b/core/vendor/nesbot/carbon/bin/upgrade-carbon @@ -0,0 +1,34 @@ +#!/usr/bin/env php +=5.3.9", + "kylekatarnls/update-helper": "^1.1", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" + }, + "require-dev": { + "composer/composer": "^1.2", + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "autoload": { + "psr-4": { + "": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "config": { + "sort-packages": true + }, + "scripts": { + "test": [ + "@phpunit", + "@phpcs" + ], + "phpunit": "phpunit --verbose --coverage-clover=coverage.xml", + "phpcs": "php-cs-fixer fix -v --diff --dry-run", + "phpstan": "phpstan analyse --configuration phpstan.neon --level 3 src tests", + "post-autoload-dump": [ + "UpdateHelper\\UpdateHelper::check" + ], + "upgrade-carbon": [ + "Carbon\\Upgrade::upgrade" + ] + }, + "extra": { + "update-helper": "Carbon\\Upgrade", + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + } + } +} diff --git a/core/vendor/nesbot/carbon/readme.md b/core/vendor/nesbot/carbon/readme.md new file mode 100644 index 0000000..5e9d1cc --- /dev/null +++ b/core/vendor/nesbot/carbon/readme.md @@ -0,0 +1,94 @@ +# Carbon + +[![Latest Stable Version](https://poser.pugx.org/nesbot/carbon/v/stable.png)](https://packagist.org/packages/nesbot/carbon) +[![Total Downloads](https://poser.pugx.org/nesbot/carbon/downloads.png)](https://packagist.org/packages/nesbot/carbon) +[![Build Status](https://travis-ci.org/briannesbitt/Carbon.svg?branch=master)](https://travis-ci.org/briannesbitt/Carbon) +[![StyleCI](https://styleci.io/repos/5724990/shield?style=flat)](https://styleci.io/repos/5724990) +[![codecov.io](https://codecov.io/github/briannesbitt/Carbon/coverage.svg?branch=master)](https://codecov.io/github/briannesbitt/Carbon?branch=master) +[![PHP-Eye](https://php-eye.com/badge/nesbot/carbon/tested.svg?style=flat)](https://php-eye.com/package/nesbot/carbon) +[![PHPStan](https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat)](https://github.com/phpstan/phpstan) + +A simple PHP API extension for DateTime. [http://carbon.nesbot.com](http://carbon.nesbot.com) + +```php +use Carbon\Carbon; + +printf("Right now is %s", Carbon::now()->toDateTimeString()); +printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver')); //implicit __toString() +$tomorrow = Carbon::now()->addDay(); +$lastWeek = Carbon::now()->subWeek(); +$nextSummerOlympics = Carbon::createFromDate(2016)->addYears(4); + +$officialDate = Carbon::now()->toRfc2822String(); + +$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age; + +$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London'); + +$internetWillBlowUpOn = Carbon::create(2038, 01, 19, 3, 14, 7, 'GMT'); + +// Don't really want this to happen so mock now +Carbon::setTestNow(Carbon::createFromDate(2000, 1, 1)); + +// comparisons are always done in UTC +if (Carbon::now()->gte($internetWillBlowUpOn)) { + die(); +} + +// Phew! Return to normal behaviour +Carbon::setTestNow(); + +if (Carbon::now()->isWeekend()) { + echo 'Party!'; +} +echo Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago' + +// ... but also does 'from now', 'after' and 'before' +// rolling up to seconds, minutes, hours, days, months, years + +$daysSinceEpoch = Carbon::createFromTimestamp(0)->diffInDays(); +``` + +## Installation + +### With Composer + +``` +$ composer require nesbot/carbon +``` + +```json +{ + "require": { + "nesbot/carbon": "~1.21" + } +} +``` + +```php + + +### Without Composer + +Why are you not using [composer](http://getcomposer.org/)? Download [Carbon.php](https://github.com/briannesbitt/Carbon/blob/master/src/Carbon/Carbon.php) from the repo and save the file into your project path somewhere. + +```php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\InvalidDateException; +use Closure; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use JsonSerializable; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * A simple API extension for DateTime + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $timestamp seconds since the Unix Epoch + * @property \DateTimeZone $timezone the current timezone + * @property \DateTimeZone $tz alias of timezone + * @property-read int $micro + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $dayOfYear 0 through 365 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read int $age does a diffInYears() with default parameters + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $offset the timezone offset in seconds from UTC + * @property-read int $offsetHours the timezone offset in hours from UTC + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName + * @property-read string $tzName + * @property-read string $englishDayOfWeek the day of week in English + * @property-read string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property-read string $englishMonth the day of week in English + * @property-read string $shortEnglishMonth the abbreviated day of week in English + * @property-read string $localeDayOfWeek the day of week in current locale LC_TIME + * @property-read string $shortLocaleDayOfWeek the abbreviated day of week in current locale LC_TIME + * @property-read string $localeMonth the month in current locale LC_TIME + * @property-read string $shortLocaleMonth the abbreviated month in current locale LC_TIME + */ +class Carbon extends DateTime implements JsonSerializable +{ + const NO_ZERO_DIFF = 01; + const JUST_NOW = 02; + const ONE_DAY_WORDS = 04; + const TWO_DAY_WORDS = 010; + + // Substitutes for Carbon 2 modes + const DIFF_RELATIVE_TO_NOW = 'relative-to-now'; + const DIFF_RELATIVE_TO_OTHER = 'relative-to-other'; + + /** + * The day constants. + */ + const SUNDAY = 0; + const MONDAY = 1; + const TUESDAY = 2; + const WEDNESDAY = 3; + const THURSDAY = 4; + const FRIDAY = 5; + const SATURDAY = 6; + + /** + * Names of days of the week. + * + * @var array + */ + protected static $days = array( + self::SUNDAY => 'Sunday', + self::MONDAY => 'Monday', + self::TUESDAY => 'Tuesday', + self::WEDNESDAY => 'Wednesday', + self::THURSDAY => 'Thursday', + self::FRIDAY => 'Friday', + self::SATURDAY => 'Saturday', + ); + + /** + * Number of X in Y. + */ + const YEARS_PER_MILLENNIUM = 1000; + const YEARS_PER_CENTURY = 100; + const YEARS_PER_DECADE = 10; + const MONTHS_PER_YEAR = 12; + const MONTHS_PER_QUARTER = 3; + const WEEKS_PER_YEAR = 52; + const WEEKS_PER_MONTH = 4; + const DAYS_PER_WEEK = 7; + const HOURS_PER_DAY = 24; + const MINUTES_PER_HOUR = 60; + const SECONDS_PER_MINUTE = 60; + const MICROSECONDS_PER_MILLISECOND = 1000; + const MICROSECONDS_PER_SECOND = 1000000; + + /** + * RFC7231 DateTime format. + * + * @var string + */ + const RFC7231_FORMAT = 'D, d M Y H:i:s \G\M\T'; + + /** + * Default format to use for __toString method when type juggling occurs. + * + * @var string + */ + const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; + + /** + * Format for converting mocked time, includes microseconds. + * + * @var string + */ + const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; + + /** + * Customizable PHP_INT_SIZE override. + * + * @var int + */ + public static $PHPIntSize = PHP_INT_SIZE; + + /** + * Format to use for __toString method when type juggling occurs. + * + * @var string + */ + protected static $toStringFormat = self::DEFAULT_TO_STRING_FORMAT; + + /** + * First day of week. + * + * @var int + */ + protected static $weekStartsAt = self::MONDAY; + + /** + * Last day of week. + * + * @var int + */ + protected static $weekEndsAt = self::SUNDAY; + + /** + * Days of weekend. + * + * @var array + */ + protected static $weekendDays = array( + self::SATURDAY, + self::SUNDAY, + ); + + /** + * Midday/noon hour. + * + * @var int + */ + protected static $midDayAt = 12; + + /** + * Format regex patterns. + * + * @var array + */ + protected static $regexFormats = array( + 'd' => '(3[01]|[12][0-9]|0[1-9])', + 'D' => '([a-zA-Z]{3})', + 'j' => '([123][0-9]|[1-9])', + 'l' => '([a-zA-Z]{2,})', + 'N' => '([1-7])', + 'S' => '([a-zA-Z]{2})', + 'w' => '([0-6])', + 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])', + 'W' => '(5[012]|[1-4][0-9]|[1-9])', + 'F' => '([a-zA-Z]{2,})', + 'm' => '(1[012]|0[1-9])', + 'M' => '([a-zA-Z]{3})', + 'n' => '(1[012]|[1-9])', + 't' => '(2[89]|3[01])', + 'L' => '(0|1)', + 'o' => '([1-9][0-9]{0,4})', + 'Y' => '([1-9]?[0-9]{4})', + 'y' => '([0-9]{2})', + 'a' => '(am|pm)', + 'A' => '(AM|PM)', + 'B' => '([0-9]{3})', + 'g' => '(1[012]|[1-9])', + 'G' => '(2[0-3]|1?[0-9])', + 'h' => '(1[012]|0[1-9])', + 'H' => '(2[0-3]|[01][0-9])', + 'i' => '([0-5][0-9])', + 's' => '([0-5][0-9])', + 'u' => '([0-9]{1,6})', + 'v' => '([0-9]{1,3})', + 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\/[a-zA-Z]*)', + 'I' => '(0|1)', + 'O' => '([\+\-](1[012]|0[0-9])[0134][05])', + 'P' => '([\+\-](1[012]|0[0-9]):[0134][05])', + 'T' => '([a-zA-Z]{1,5})', + 'Z' => '(-?[1-5]?[0-9]{1,4})', + 'U' => '([0-9]*)', + + // The formats below are combinations of the above formats. + 'c' => '(([1-9]?[0-9]{4})\-(1[012]|0[1-9])\-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[\+\-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP + 'r' => '(([a-zA-Z]{3}), ([123][0-9]|[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [\+\-](1[012]|0[0-9])([0134][05]))', // D, j M Y H:i:s O + ); + + /** + * A test Carbon instance to be returned when now instances are created. + * + * @var \Carbon\Carbon + */ + protected static $testNow; + + /** + * A translator to ... er ... translate stuff. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * The errors that can occur. + * + * @var array + */ + protected static $lastErrors; + + /** + * The custom Carbon JSON serializer. + * + * @var callable|null + */ + protected static $serializer; + + /** + * The registered string macros. + * + * @var array + */ + protected static $localMacros = array(); + + /** + * Will UTF8 encoding be used to print localized date/time ? + * + * @var bool + */ + protected static $utf8 = false; + + /** + * Add microseconds to now on PHP < 7.1 and 7.1.3. true by default. + * + * @var bool + */ + protected static $microsecondsFallback = true; + + /** + * Indicates if months should be calculated with overflow. + * + * @var bool + */ + protected static $monthsOverflow = true; + + /** + * Indicates if years should be calculated with overflow. + * + * @var bool + */ + protected static $yearsOverflow = true; + + /** + * Indicates if years are compared with month by default so isSameMonth and isSameQuarter have $ofSameYear set + * to true by default. + * + * @var bool + */ + protected static $compareYearWithMonth = false; + + /** + * Options for diffForHumans(). + * + * @var int + */ + protected static $humanDiffOptions = self::NO_ZERO_DIFF; + + /** + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions) + { + static::$humanDiffOptions = $humanDiffOptions; + } + + /** + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; + } + + /** + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; + } + + /** + * @return int + */ + public static function getHumanDiffOptions() + { + return static::$humanDiffOptions; + } + + /** + * Add microseconds to now on PHP < 7.1 and 7.1.3 if set to true, + * let microseconds to 0 on those PHP versions if false. + * + * @param bool $microsecondsFallback + */ + public static function useMicrosecondsFallback($microsecondsFallback = true) + { + static::$microsecondsFallback = $microsecondsFallback; + } + + /** + * Return true if microseconds fallback on PHP < 7.1 and 7.1.3 is + * enabled. false if disabled. + * + * @return bool + */ + public static function isMicrosecondsFallbackEnabled() + { + return static::$microsecondsFallback; + } + + /** + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true) + { + static::$monthsOverflow = $monthsOverflow; + } + + /** + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow() + { + static::$monthsOverflow = true; + } + + /** + * Get the month overflow behavior. + * + * @return bool + */ + public static function shouldOverflowMonths() + { + return static::$monthsOverflow; + } + + /** + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true) + { + static::$yearsOverflow = $yearsOverflow; + } + + /** + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow() + { + static::$yearsOverflow = true; + } + + /** + * Get the month overflow behavior. + * + * @return bool + */ + public static function shouldOverflowYears() + { + return static::$yearsOverflow; + } + + /** + * Get the month comparison default behavior. + * + * @return bool + */ + public static function compareYearWithMonth($compareYearWithMonth = true) + { + static::$compareYearWithMonth = $compareYearWithMonth; + } + + /** + * Get the month comparison default behavior. + * + * @return bool + */ + public static function shouldCompareYearWithMonth() + { + return static::$compareYearWithMonth; + } + + /** + * Creates a DateTimeZone from a string, DateTimeZone or integer offset. + * + * @param \DateTimeZone|string|int|null $object + * + * @throws \InvalidArgumentException + * + * @return \DateTimeZone + */ + protected static function safeCreateDateTimeZone($object) + { + if ($object === null) { + // Don't return null... avoid Bug #52063 in PHP <5.3.6 + return new DateTimeZone(date_default_timezone_get()); + } + + if ($object instanceof DateTimeZone) { + return $object; + } + + if (is_numeric($object)) { + $tzName = timezone_name_from_abbr(null, $object * 3600, true); + + if ($tzName === false) { + throw new InvalidArgumentException('Unknown or bad timezone ('.$object.')'); + } + + $object = $tzName; + } + + $tz = @timezone_open($object = (string) $object); + + if ($tz !== false) { + return $tz; + } + + // Work-around for a bug fixed in PHP 5.5.10 https://bugs.php.net/bug.php?id=45528 + // See: https://stackoverflow.com/q/14068594/2646927 + // @codeCoverageIgnoreStart + if (strpos($object, ':') !== false) { + try { + return static::createFromFormat('O', $object)->getTimezone(); + } catch (InvalidArgumentException $e) { + // + } + } + // @codeCoverageIgnoreEnd + + throw new InvalidArgumentException('Unknown or bad timezone ('.$object.')'); + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Create a new Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param string|null $time + * @param \DateTimeZone|string|null $tz + */ + public function __construct($time = null, $tz = null) + { + // If the class has a test now set and we are trying to create a now() + // instance then override as required + $isNow = empty($time) || $time === 'now'; + if (static::hasTestNow() && ($isNow || static::hasRelativeKeywords($time))) { + $testInstance = clone static::getTestNow(); + + //shift the time according to the given time zone + if ($tz !== null && $tz !== static::getTestNow()->getTimezone()) { + $testInstance->setTimezone($tz); + } else { + $tz = $testInstance->getTimezone(); + } + + if (static::hasRelativeKeywords($time)) { + $testInstance->modify($time); + } + + $time = $testInstance->format(static::MOCK_DATETIME_FORMAT); + } + + $timezone = static::safeCreateDateTimeZone($tz); + // @codeCoverageIgnoreStart + if ($isNow && !isset($testInstance) && static::isMicrosecondsFallbackEnabled() && ( + version_compare(PHP_VERSION, '7.1.0-dev', '<') + || + version_compare(PHP_VERSION, '7.1.3-dev', '>=') && version_compare(PHP_VERSION, '7.1.4-dev', '<') + ) + ) { + // Get microseconds from microtime() if "now" asked and PHP < 7.1 and PHP 7.1.3 if fallback enabled. + list($microTime, $timeStamp) = explode(' ', microtime()); + $dateTime = new DateTime('now', $timezone); + $dateTime->setTimestamp($timeStamp); // Use the timestamp returned by microtime as now can happen in the next second + $time = $dateTime->format(static::DEFAULT_TO_STRING_FORMAT).substr($microTime, 1, 7); + } + // @codeCoverageIgnoreEnd + + // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 + if (strpos((string) .1, '.') === false) { + $locale = setlocale(LC_NUMERIC, '0'); + setlocale(LC_NUMERIC, 'C'); + } + parent::__construct($time, $timezone); + if (isset($locale)) { + setlocale(LC_NUMERIC, $locale); + } + static::setLastErrors(parent::getLastErrors()); + } + + /** + * Create a Carbon instance from a DateTime one. + * + * @param \DateTime|\DateTimeInterface $date + * + * @return static + */ + public static function instance($date) + { + if ($date instanceof static) { + return clone $date; + } + + static::expectDateTime($date); + + return new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + } + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|null $time + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function parse($time = null, $tz = null) + { + return new static($time, $tz); + } + + /** + * Get a Carbon instance for the current date and time. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null) + { + return new static(null, $tz); + } + + /** + * Create a Carbon instance for today. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null) + { + return static::parse('today', $tz); + } + + /** + * Create a Carbon instance for tomorrow. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null) + { + return static::parse('tomorrow', $tz); + } + + /** + * Create a Carbon instance for yesterday. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null) + { + return static::parse('yesterday', $tz); + } + + /** + * Create a Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(9999, 12, 31, 23, 59, 59); + } + + /** + * Create a Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(~PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(1, 1, 1, 0, 0, 0); + } + + /** + * Create a new Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function create($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $now = static::hasTestNow() ? static::getTestNow() : static::now($tz); + + $defaults = array_combine(array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ), explode('-', $now->format('Y-n-j-G-i-s'))); + + $year = $year === null ? $defaults['year'] : $year; + $month = $month === null ? $defaults['month'] : $month; + $day = $day === null ? $defaults['day'] : $day; + + if ($hour === null) { + $hour = $defaults['hour']; + $minute = $minute === null ? $defaults['minute'] : $minute; + $second = $second === null ? $defaults['second'] : $second; + } else { + $minute = $minute === null ? 0 : $minute; + $second = $second === null ? 0 : $second; + } + + $fixYear = null; + + if ($year < 0) { + $fixYear = $year; + $year = 0; + } elseif ($year > 9999) { + $fixYear = $year - 9999; + $year = 9999; + } + + $instance = static::createFromFormat('!Y-n-j G:i:s', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + + if ($fixYear !== null) { + $instance->addYears($fixYear); + } + + return $instance; + } + + /** + * Create a new safe Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an \InvalidArgumentException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \Carbon\Exceptions\InvalidDateException|\InvalidArgumentException + * + * @return static + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $fields = array( + 'year' => array(0, 9999), + 'month' => array(0, 12), + 'day' => array(0, 31), + 'hour' => array(0, 24), + 'minute' => array(0, 59), + 'second' => array(0, 59), + ); + + foreach ($fields as $field => $range) { + if ($$field !== null && (!is_int($$field) || $$field < $range[0] || $$field > $range[1])) { + throw new InvalidDateException($field, $$field); + } + } + + $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz); + + foreach (array_reverse($fields) as $field => $range) { + if ($$field !== null && (!is_int($$field) || $$field !== $instance->$field)) { + throw new InvalidDateException($field, $$field); + } + } + + return $instance; + } + + /** + * Create a Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, null, null, null, $tz); + } + + /** + * Create a Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, 0, 0, 0, $tz); + } + + /** + * Create a Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromTime($hour = null, $minute = null, $second = null, $tz = null) + { + return static::create(null, null, null, $hour, $minute, $second, $tz); + } + + /** + * Create a Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null) + { + return static::today($tz)->setTimeFromTimeString($time); + } + + private static function createFromFormatAndTimezone($format, $time, $tz) + { + return $tz !== null + ? parent::createFromFormat($format, $time, static::safeCreateDateTimeZone($tz)) + : parent::createFromFormat($format, $time); + } + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param \DateTimeZone|string|null $tz + * + * @throws InvalidArgumentException + * + * @return static + */ + public static function createFromFormat($format, $time, $tz = null) + { + // First attempt to create an instance, so that error messages are based on the unmodified format. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + $lastErrors = parent::getLastErrors(); + + if (($mock = static::getTestNow()) && ($date instanceof DateTime || $date instanceof DateTimeInterface)) { + // Set timezone from mock if custom timezone was neither given directly nor as a part of format. + // First let's skip the part that will be ignored by the parser. + $nonEscaped = '(?getTimezone(); + } + + // Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag. + if (!preg_match("/{$nonEscaped}[!|]/", $format)) { + $format = static::MOCK_DATETIME_FORMAT.' '.$format; + $time = $mock->format(static::MOCK_DATETIME_FORMAT).' '.$time; + } + + // Regenerate date from the modified format to base result on the mocked instance instead of now. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + } + + if ($date instanceof DateTime || $date instanceof DateTimeInterface) { + $instance = static::instance($date); + $instance::setLastErrors($lastErrors); + + return $instance; + } + + throw new InvalidArgumentException(implode(PHP_EOL, $lastErrors['errors'])); + } + + /** + * Set last errors. + * + * @param array $lastErrors + * + * @return void + */ + private static function setLastErrors(array $lastErrors) + { + static::$lastErrors = $lastErrors; + } + + /** + * {@inheritdoc} + */ + public static function getLastErrors() + { + return static::$lastErrors; + } + + /** + * Create a Carbon instance from a timestamp. + * + * @param int $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null) + { + return static::today($tz)->setTimestamp($timestamp); + } + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * @param int $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null) + { + return static::createFromFormat('U.u', sprintf('%F', $timestamp / 1000)) + ->setTimezone($tz); + } + + /** + * Create a Carbon instance from an UTC timestamp. + * + * @param int $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp) + { + return new static('@'.$timestamp); + } + + /** + * Make a Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof DateTime || $var instanceof DateTimeInterface) { + return static::instance($var); + } + + if (is_string($var)) { + $var = trim($var); + $first = substr($var, 0, 1); + + if (is_string($var) && $first !== 'P' && $first !== 'R' && preg_match('/[a-z0-9]/i', $var)) { + return static::parse($var); + } + } + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz() + { + return static::now($this->getTimezone()); + } + + /** + * Throws an exception if the given object is not a DateTime and does not implement DateTimeInterface + * and not in $other. + * + * @param mixed $date + * @param string|array $other + * + * @throws \InvalidArgumentException + */ + protected static function expectDateTime($date, $other = array()) + { + $message = 'Expected '; + foreach ((array) $other as $expect) { + $message .= "{$expect}, "; + } + + if (!$date instanceof DateTime && !$date instanceof DateTimeInterface) { + throw new InvalidArgumentException( + $message.'DateTime or DateTimeInterface, '. + (is_object($date) ? get_class($date) : gettype($date)).' given' + ); + } + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveCarbon($date = null) + { + if (!$date) { + return $this->nowWithSameTz(); + } + + if (is_string($date)) { + return static::parse($date, $this->getTimezone()); + } + + static::expectDateTime($date, array('null', 'string')); + + return $date instanceof self ? $date : static::instance($date); + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return string|int|bool|\DateTimeZone + */ + public function __get($name) + { + static $formats = array( + 'year' => 'Y', + 'yearIso' => 'o', + 'month' => 'n', + 'day' => 'j', + 'hour' => 'G', + 'minute' => 'i', + 'second' => 's', + 'micro' => 'u', + 'dayOfWeek' => 'w', + 'dayOfWeekIso' => 'N', + 'dayOfYear' => 'z', + 'weekOfYear' => 'W', + 'daysInMonth' => 't', + 'timestamp' => 'U', + 'englishDayOfWeek' => 'l', + 'shortEnglishDayOfWeek' => 'D', + 'englishMonth' => 'F', + 'shortEnglishMonth' => 'M', + 'localeDayOfWeek' => '%A', + 'shortLocaleDayOfWeek' => '%a', + 'localeMonth' => '%B', + 'shortLocaleMonth' => '%b', + ); + + switch (true) { + case isset($formats[$name]): + $format = $formats[$name]; + $method = substr($format, 0, 1) === '%' ? 'formatLocalized' : 'format'; + $value = $this->$method($format); + + return is_numeric($value) ? (int) $value : $value; + + case $name === 'weekOfMonth': + return (int) ceil($this->day / static::DAYS_PER_WEEK); + + case $name === 'weekNumberInMonth': + return (int) ceil(($this->day + $this->copy()->startOfMonth()->dayOfWeek - 1) / static::DAYS_PER_WEEK); + + case $name === 'age': + return $this->diffInYears(); + + case $name === 'quarter': + return (int) ceil($this->month / static::MONTHS_PER_QUARTER); + + case $name === 'offset': + return $this->getOffset(); + + case $name === 'offsetHours': + return $this->getOffset() / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR; + + case $name === 'dst': + return $this->format('I') === '1'; + + case $name === 'local': + return $this->getOffset() === $this->copy()->setTimezone(date_default_timezone_get())->getOffset(); + + case $name === 'utc': + return $this->getOffset() === 0; + + case $name === 'timezone' || $name === 'tz': + return $this->getTimezone(); + + case $name === 'timezoneName' || $name === 'tzName': + return $this->getTimezone()->getName(); + + default: + throw new InvalidArgumentException(sprintf("Unknown getter '%s'", $name)); + } + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + try { + $this->__get($name); + } catch (InvalidArgumentException $e) { + return false; + } + + return true; + } + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|\DateTimeZone $value + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function __set($name, $value) + { + switch ($name) { + case 'year': + case 'month': + case 'day': + case 'hour': + case 'minute': + case 'second': + list($year, $month, $day, $hour, $minute, $second) = explode('-', $this->format('Y-n-j-G-i-s')); + $$name = $value; + $this->setDateTime($year, $month, $day, $hour, $minute, $second); + break; + + case 'timestamp': + parent::setTimestamp($value); + break; + + case 'timezone': + case 'tz': + $this->setTimezone($value); + break; + + default: + throw new InvalidArgumentException(sprintf("Unknown setter '%s'", $name)); + } + } + + /** + * Set the instance's year + * + * @param int $value + * + * @return static + */ + public function year($value) + { + $this->year = $value; + + return $this; + } + + /** + * Set the instance's month + * + * @param int $value + * + * @return static + */ + public function month($value) + { + $this->month = $value; + + return $this; + } + + /** + * Set the instance's day + * + * @param int $value + * + * @return static + */ + public function day($value) + { + $this->day = $value; + + return $this; + } + + /** + * Set the instance's hour + * + * @param int $value + * + * @return static + */ + public function hour($value) + { + $this->hour = $value; + + return $this; + } + + /** + * Set the instance's minute + * + * @param int $value + * + * @return static + */ + public function minute($value) + { + $this->minute = $value; + + return $this; + } + + /** + * Set the instance's second + * + * @param int $value + * + * @return static + */ + public function second($value) + { + $this->second = $value; + + return $this; + } + + /** + * Sets the current date of the DateTime object to a different date. + * Calls modify as a workaround for a php bug + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + * + * @see https://github.com/briannesbitt/Carbon/issues/539 + * @see https://bugs.php.net/bug.php?id=63863 + */ + public function setDate($year, $month, $day) + { + $this->modify('+0 day'); + + return parent::setDate($year, $month, $day); + } + + /** + * Set the date and time all together + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0) + { + return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second); + } + + /** + * Set the time by time string + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time) + { + if (strpos($time, ':') === false) { + $time .= ':0'; + } + + return $this->modify($time); + } + + /** + * Set the instance's timestamp + * + * @param int $value + * + * @return static + */ + public function timestamp($value) + { + return $this->setTimestamp($value); + } + + /** + * Alias for setTimezone() + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function timezone($value) + { + return $this->setTimezone($value); + } + + /** + * Alias for setTimezone() + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function tz($value) + { + return $this->setTimezone($value); + } + + /** + * Set the instance's timezone from a string or object + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function setTimezone($value) + { + parent::setTimezone(static::safeCreateDateTimeZone($value)); + // https://bugs.php.net/bug.php?id=72338 + // just workaround on this bug + $this->getTimestamp(); + + return $this; + } + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param \Carbon\Carbon|\DateTimeInterface $date + * + * @return static + */ + public function setDateFrom($date) + { + $date = static::instance($date); + + $this->setDate($date->year, $date->month, $date->day); + + return $this; + } + + /** + * Set the hour, day, and time for this instance to that of the passed instance. + * + * @param \Carbon\Carbon|\DateTimeInterface $date + * + * @return static + */ + public function setTimeFrom($date) + { + $date = static::instance($date); + + $this->setTime($date->hour, $date->minute, $date->second); + + return $this; + } + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays() + { + return static::$days; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// WEEK SPECIAL DAYS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt() + { + return static::$weekStartsAt; + } + + /** + * Set the first day of week + * + * @param int $day week start day + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function setWeekStartsAt($day) + { + if ($day > static::SATURDAY || $day < static::SUNDAY) { + throw new InvalidArgumentException('Day of a week should be greater than or equal to 0 and less than or equal to 6.'); + } + + static::$weekStartsAt = $day; + } + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt() + { + return static::$weekEndsAt; + } + + /** + * Set the last day of week + * + * @param int $day + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function setWeekEndsAt($day) + { + if ($day > static::SATURDAY || $day < static::SUNDAY) { + throw new InvalidArgumentException('Day of a week should be greater than or equal to 0 and less than or equal to 6.'); + } + + static::$weekEndsAt = $day; + } + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays() + { + return static::$weekendDays; + } + + /** + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days) + { + static::$weekendDays = $days; + } + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt() + { + return static::$midDayAt; + } + + /** + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour) + { + static::$midDayAt = $hour; + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// TESTING AIDS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * @param \Carbon\Carbon|null $testNow real or mock Carbon instance + * @param \Carbon\Carbon|string|null $testNow + */ + public static function setTestNow($testNow = null) + { + static::$testNow = is_string($testNow) ? static::parse($testNow) : $testNow; + } + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return static the current instance used for testing + */ + public static function getTestNow() + { + return static::$testNow; + } + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow() + { + return static::getTestNow() !== null; + } + + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time) + { + if (strtotime($time) === false) { + return false; + } + + $date1 = new DateTime('2000-01-01T00:00:00Z'); + $date1->modify($time); + $date2 = new DateTime('2001-12-25T00:00:00Z'); + $date2->modify($time); + + return $date1 != $date2; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// LOCALIZATION ////////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Initialize the translator instance if necessary. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + protected static function translator() + { + if (static::$translator === null) { + static::$translator = Translator::get(); + } + + return static::$translator; + } + + /** + * Get the translator instance in use + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + + /** + * Set the translator instance to use + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator) + { + static::$translator = $translator; + } + + /** + * Get the current translator locale + * + * @return string + */ + public static function getLocale() + { + return static::translator()->getLocale(); + } + + /** + * Set the current translator locale and indicate if the source locale file exists + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale) + { + return static::translator()->setLocale($locale) !== false; + } + + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * + * @return mixed + */ + public static function executeWithLocale($locale, $func) + { + $currentLocale = static::getLocale(); + $result = call_user_func($func, static::setLocale($locale) ? static::getLocale() : false, static::translator()); + static::setLocale($currentLocale); + + return $result; + } + + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + ( + ($y = $translator->trans('y')) !== 'y' && + $y !== $translator->trans('year') + ) || ( + ($y = $translator->trans('d')) !== 'd' && + $y !== $translator->trans('day') + ) || ( + ($y = $translator->trans('h')) !== 'h' && + $y !== $translator->trans('hour') + ); + }); + } + + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('ago') !== 'ago' && + $translator->trans('from_now') !== 'from_now' && + $translator->trans('before') !== 'before' && + $translator->trans('after') !== 'after'; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_now') !== 'diff_now' && + $translator->trans('diff_yesterday') !== 'diff_yesterday' && + $translator->trans('diff_tomorrow') !== 'diff_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' && + $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('period_recurrences') !== 'period_recurrences' && + $translator->trans('period_interval') !== 'period_interval' && + $translator->trans('period_start_date') !== 'period_start_date' && + $translator->trans('period_end_date') !== 'period_end_date'; + }); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales() + { + $translator = static::translator(); + $locales = array(); + if ($translator instanceof Translator) { + foreach (glob(__DIR__.'/Lang/*.php') as $file) { + $locales[] = substr($file, strrpos($file, '/') + 1, -4); + } + + $locales = array_unique(array_merge($locales, array_keys($translator->getMessages()))); + } + + return $locales; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// STRING FORMATTING ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Set if UTF8 will be used for localized date/time + * + * @param bool $utf8 + */ + public static function setUtf8($utf8) + { + static::$utf8 = $utf8; + } + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() http://php.net/setlocale. + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format) + { + // Check for Windows to find and replace the %e modifier correctly. + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $format = preg_replace('#(?toDateTimeString())); + + return static::$utf8 ? utf8_encode($formatted) : $formatted; + } + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat() + { + static::setToStringFormat(static::DEFAULT_TO_STRING_FORMAT); + } + + /** + * Set the default format used when type juggling a Carbon instance to a string + * + * @param string|Closure $format + * + * @return void + */ + public static function setToStringFormat($format) + { + static::$toStringFormat = $format; + } + + /** + * Format the instance as a string using the set format + * + * @return string + */ + public function __toString() + { + $format = static::$toStringFormat; + + return $this->format($format instanceof Closure ? $format($this) : $format); + } + + /** + * Format the instance as date + * + * @return string + */ + public function toDateString() + { + return $this->format('Y-m-d'); + } + + /** + * Format the instance as a readable date + * + * @return string + */ + public function toFormattedDateString() + { + return $this->format('M j, Y'); + } + + /** + * Format the instance as time + * + * @return string + */ + public function toTimeString() + { + return $this->format('H:i:s'); + } + + /** + * Format the instance as date and time + * + * @return string + */ + public function toDateTimeString() + { + return $this->format('Y-m-d H:i:s'); + } + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * ``` + * + * @return string + */ + public function toDateTimeLocalString() + { + return $this->format('Y-m-d\TH:i:s'); + } + + /** + * Format the instance with day, date and time + * + * @return string + */ + public function toDayDateTimeString() + { + return $this->format('D, M j, Y g:i A'); + } + + /** + * Format the instance as ATOM + * + * @return string + */ + public function toAtomString() + { + return $this->format(static::ATOM); + } + + /** + * Format the instance as COOKIE + * + * @return string + */ + public function toCookieString() + { + return $this->format(static::COOKIE); + } + + /** + * Format the instance as ISO8601 + * + * @return string + */ + public function toIso8601String() + { + return $this->toAtomString(); + } + + /** + * Format the instance as RFC822 + * + * @return string + */ + public function toRfc822String() + { + return $this->format(static::RFC822); + } + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @return string + */ + public function toIso8601ZuluString() + { + return $this->copy()->setTimezone('UTC')->format('Y-m-d\TH:i:s\Z'); + } + + /** + * Format the instance as RFC850 + * + * @return string + */ + public function toRfc850String() + { + return $this->format(static::RFC850); + } + + /** + * Format the instance as RFC1036 + * + * @return string + */ + public function toRfc1036String() + { + return $this->format(static::RFC1036); + } + + /** + * Format the instance as RFC1123 + * + * @return string + */ + public function toRfc1123String() + { + return $this->format(static::RFC1123); + } + + /** + * Format the instance as RFC2822 + * + * @return string + */ + public function toRfc2822String() + { + return $this->format(static::RFC2822); + } + + /** + * Format the instance as RFC3339 + * + * @return string + */ + public function toRfc3339String() + { + return $this->format(static::RFC3339); + } + + /** + * Format the instance as RSS + * + * @return string + */ + public function toRssString() + { + return $this->format(static::RSS); + } + + /** + * Format the instance as W3C + * + * @return string + */ + public function toW3cString() + { + return $this->format(static::W3C); + } + + /** + * Format the instance as RFC7231 + * + * @return string + */ + public function toRfc7231String() + { + return $this->copy() + ->setTimezone('GMT') + ->format(static::RFC7231_FORMAT); + } + + /** + * Get default array representation + * + * @return array + */ + public function toArray() + { + return array( + 'year' => $this->year, + 'month' => $this->month, + 'day' => $this->day, + 'dayOfWeek' => $this->dayOfWeek, + 'dayOfYear' => $this->dayOfYear, + 'hour' => $this->hour, + 'minute' => $this->minute, + 'second' => $this->second, + 'micro' => $this->micro, + 'timestamp' => $this->timestamp, + 'formatted' => $this->format(self::DEFAULT_TO_STRING_FORMAT), + 'timezone' => $this->timezone, + ); + } + + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject() + { + return (object) $this->toArray(); + } + + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString() + { + return $this->format('D M j Y H:i:s \G\M\TO'); + } + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept: + * 1977-04-22T01:00:00-05:00). + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toISOString() . "\n"; + * echo Carbon::now('America/Toronto')->toISOString(true) . "\n"; + * ``` + * + * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. + * + * @return null|string + */ + public function toISOString($keepOffset = false) + { + if ($this->year === 0) { + return null; + } + + $year = $this->year < 0 || $this->year > 9999 + ? ($this->year < 0 ? '-' : '+').str_pad(abs($this->year), 6, '0', STR_PAD_LEFT) + : str_pad($this->year, 4, '0', STR_PAD_LEFT); + $tz = $keepOffset ? $this->format('P') : 'Z'; + $date = $keepOffset ? $this : $this->copy()->setTimezone('UTC'); + + return $year.$date->format('-m-d\TH:i:s.u').$tz; + } + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toJSON(); + * ``` + * + * @return null|string + */ + public function toJSON() + { + return $this->toISOString(); + } + + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime() + { + return new DateTime($this->format('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate() + { + return $this->toDateTime(); + } + + /////////////////////////////////////////////////////////////////// + ////////////////////////// COMPARISONS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Determines if the instance is equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function eq($date) + { + return $this == $date; + } + + /** + * Determines if the instance is equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see eq() + * + * @return bool + */ + public function equalTo($date) + { + return $this->eq($date); + } + + /** + * Determines if the instance is not equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function ne($date) + { + return !$this->eq($date); + } + + /** + * Determines if the instance is not equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see ne() + * + * @return bool + */ + public function notEqualTo($date) + { + return $this->ne($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function gt($date) + { + return $this > $date; + } + + /** + * Determines if the instance is greater (after) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see gt() + * + * @return bool + */ + public function greaterThan($date) + { + return $this->gt($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see gt() + * + * @return bool + */ + public function isAfter($date) + { + return $this->gt($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function gte($date) + { + return $this >= $date; + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see gte() + * + * @return bool + */ + public function greaterThanOrEqualTo($date) + { + return $this->gte($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lt($date) + { + return $this < $date; + } + + /** + * Determines if the instance is less (before) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lt() + * + * @return bool + */ + public function lessThan($date) + { + return $this->lt($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lt() + * + * @return bool + */ + public function isBefore($date) + { + return $this->lt($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lte($date) + { + return $this <= $date; + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lte() + * + * @return bool + */ + public function lessThanOrEqualTo($date) + { + return $this->lte($date); + } + + /** + * Determines if the instance is between two others + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = true) + { + if ($date1->gt($date2)) { + $temp = $date1; + $date1 = $date2; + $date2 = $temp; + } + + if ($equal) { + return $this->gte($date1) && $this->lte($date2); + } + + return $this->gt($date1) && $this->lt($date2); + } + + protected function floatDiffInSeconds($date) + { + $date = $this->resolveCarbon($date); + + return abs($this->diffInRealSeconds($date, false) + ($date->micro - $this->micro) / 1000000); + } + + /** + * Determines if the instance is between two others + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if a > and < comparison should be used or <= or >= + * + * @return bool + */ + public function isBetween($date1, $date2, $equal = true) + { + return $this->between($date1, $date2, $equal); + } + + /** + * Get the closest date from the instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2) + { + return $this->floatDiffInSeconds($date1) < $this->floatDiffInSeconds($date2) ? $date1 : $date2; + } + + /** + * Get the farthest date from the instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2) + { + return $this->floatDiffInSeconds($date1) > $this->floatDiffInSeconds($date2) ? $date1 : $date2; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function min($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->lt($date) ? $this : $date; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null) + { + return $this->min($date); + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function max($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->gt($date) ? $this : $date; + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null) + { + return $this->max($date); + } + + /** + * Determines if the instance is a weekday. + * + * @return bool + */ + public function isWeekday() + { + return !$this->isWeekend(); + } + + /** + * Determines if the instance is a weekend day. + * + * @return bool + */ + public function isWeekend() + { + return in_array($this->dayOfWeek, static::$weekendDays); + } + + /** + * Determines if the instance is yesterday. + * + * @return bool + */ + public function isYesterday() + { + return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is today. + * + * @return bool + */ + public function isToday() + { + return $this->toDateString() === $this->nowWithSameTz()->toDateString(); + } + + /** + * Determines if the instance is tomorrow. + * + * @return bool + */ + public function isTomorrow() + { + return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is within the next week. + * + * @return bool + */ + public function isNextWeek() + { + return $this->weekOfYear === $this->nowWithSameTz()->addWeek()->weekOfYear; + } + + /** + * Determines if the instance is within the last week. + * + * @return bool + */ + public function isLastWeek() + { + return $this->weekOfYear === $this->nowWithSameTz()->subWeek()->weekOfYear; + } + + /** + * Determines if the instance is within the next quarter. + * + * @return bool + */ + public function isNextQuarter() + { + return $this->quarter === $this->nowWithSameTz()->addQuarter()->quarter; + } + + /** + * Determines if the instance is within the last quarter. + * + * @return bool + */ + public function isLastQuarter() + { + return $this->quarter === $this->nowWithSameTz()->subQuarter()->quarter; + } + + /** + * Determines if the instance is within the next month. + * + * @return bool + */ + public function isNextMonth() + { + return $this->month === $this->nowWithSameTz()->addMonthNoOverflow()->month; + } + + /** + * Determines if the instance is within the last month. + * + * @return bool + */ + public function isLastMonth() + { + return $this->month === $this->nowWithSameTz()->subMonthNoOverflow()->month; + } + + /** + * Determines if the instance is within next year. + * + * @return bool + */ + public function isNextYear() + { + return $this->year === $this->nowWithSameTz()->addYear()->year; + } + + /** + * Determines if the instance is within the previous year. + * + * @return bool + */ + public function isLastYear() + { + return $this->year === $this->nowWithSameTz()->subYear()->year; + } + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @return bool + */ + public function isFuture() + { + return $this->gt($this->nowWithSameTz()); + } + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @return bool + */ + public function isPast() + { + return $this->lt($this->nowWithSameTz()); + } + + /** + * Determines if the instance is a leap year. + * + * @return bool + */ + public function isLeapYear() + { + return $this->format('L') === '1'; + } + + /** + * Determines if the instance is a long year + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear() + { + return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + + /** + * Compares the formatted values of the two dates. + * + * @param string $format The date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @throws \InvalidArgumentException + * + * @return bool + */ + public function isSameAs($format, $date = null) + { + $date = $date ?: static::now($this->tz); + + static::expectDateTime($date, 'null'); + + return $this->format($format) === $date->format($format); + } + + /** + * Determines if the instance is in the current year. + * + * @return bool + */ + public function isCurrentYear() + { + return $this->isSameYear(); + } + + /** + * Checks if the passed in date is in the same year as the instance year. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameYear($date = null) + { + return $this->isSameAs('Y', $date); + } + + /** + * Determines if the instance is in the current month. + * + * @return bool + */ + public function isCurrentQuarter() + { + return $this->isSameQuarter(); + } + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = null) + { + $date = $date ? static::instance($date) : static::now($this->tz); + + static::expectDateTime($date, 'null'); + + $ofSameYear = is_null($ofSameYear) ? static::shouldCompareYearWithMonth() : $ofSameYear; + + return $this->quarter === $date->quarter && (!$ofSameYear || $this->isSameYear($date)); + } + + /** + * Determines if the instance is in the current month. + * + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isCurrentMonth($ofSameYear = null) + { + return $this->isSameMonth(null, $ofSameYear); + } + + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * Note that this defaults to only comparing the month while ignoring the year. + * To test if it is the same exact month of the same year, pass in true as the second parameter. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = null) + { + $ofSameYear = is_null($ofSameYear) ? static::shouldCompareYearWithMonth() : $ofSameYear; + + return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date); + } + + /** + * Determines if the instance is in the current day. + * + * @return bool + */ + public function isCurrentDay() + { + return $this->isSameDay(); + } + + /** + * Checks if the passed in date is the same exact day as the instance´s day. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameDay($date = null) + { + return $this->isSameAs('Y-m-d', $date); + } + + /** + * Determines if the instance is in the current hour. + * + * @return bool + */ + public function isCurrentHour() + { + return $this->isSameHour(); + } + + /** + * Checks if the passed in date is the same exact hour as the instance´s hour. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameHour($date = null) + { + return $this->isSameAs('Y-m-d H', $date); + } + + /** + * Determines if the instance is in the current minute. + * + * @return bool + */ + public function isCurrentMinute() + { + return $this->isSameMinute(); + } + + /** + * Checks if the passed in date is the same exact minute as the instance´s minute. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameMinute($date = null) + { + return $this->isSameAs('Y-m-d H:i', $date); + } + + /** + * Determines if the instance is in the current second. + * + * @return bool + */ + public function isCurrentSecond() + { + return $this->isSameSecond(); + } + + /** + * Checks if the passed in date is the same exact second as the instance´s second. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameSecond($date = null) + { + return $this->isSameAs('Y-m-d H:i:s', $date); + } + + /** + * Checks if this day is a specific day of the week. + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek) + { + return $this->dayOfWeek === $dayOfWeek; + } + + /** + * Checks if this day is a Sunday. + * + * @return bool + */ + public function isSunday() + { + return $this->dayOfWeek === static::SUNDAY; + } + + /** + * Checks if this day is a Monday. + * + * @return bool + */ + public function isMonday() + { + return $this->dayOfWeek === static::MONDAY; + } + + /** + * Checks if this day is a Tuesday. + * + * @return bool + */ + public function isTuesday() + { + return $this->dayOfWeek === static::TUESDAY; + } + + /** + * Checks if this day is a Wednesday. + * + * @return bool + */ + public function isWednesday() + { + return $this->dayOfWeek === static::WEDNESDAY; + } + + /** + * Checks if this day is a Thursday. + * + * @return bool + */ + public function isThursday() + { + return $this->dayOfWeek === static::THURSDAY; + } + + /** + * Checks if this day is a Friday. + * + * @return bool + */ + public function isFriday() + { + return $this->dayOfWeek === static::FRIDAY; + } + + /** + * Checks if this day is a Saturday. + * + * @return bool + */ + public function isSaturday() + { + return $this->dayOfWeek === static::SATURDAY; + } + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null) + { + return $this->isSameAs('md', $date); + } + + /** + * Check if today is the last day of the Month + * + * @return bool + */ + public function isLastOfMonth() + { + return $this->day === $this->daysInMonth; + } + + /** + * Check if the instance is start of day / midnight. + * + * @param bool $checkMicroseconds check time at microseconds precision + * /!\ Warning, this is not reliable with PHP < 7.1.4 + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false) + { + return $checkMicroseconds + ? $this->format('H:i:s.u') === '00:00:00.000000' + : $this->format('H:i:s') === '00:00:00'; + } + + /** + * Check if the instance is end of day. + * + * @param bool $checkMicroseconds check time at microseconds precision + * /!\ Warning, this is not reliable with PHP < 7.1.4 + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false) + { + return $checkMicroseconds + ? $this->format('H:i:s.u') === '23:59:59.999999' + : $this->format('H:i:s') === '23:59:59'; + } + + /** + * Check if the instance is start of day / midnight. + * + * @return bool + */ + public function isMidnight() + { + return $this->isStartOfDay(); + } + + /** + * Check if the instance is midday. + * + * @return bool + */ + public function isMidday() + { + return $this->format('G:i:s') === static::$midDayAt.':00:00'; + } + + /** + * Checks if the (date)time string is in a given format. + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format) + { + try { + // Try to create a DateTime object. Throws an InvalidArgumentException if the provided time string + // doesn't match the format in any way. + static::createFromFormat($format, $date); + + // createFromFormat() is known to handle edge cases silently. + // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format. + // To ensure we're really testing against our desired format, perform an additional regex validation. + $regex = strtr( + preg_quote($format, '/'), + static::$regexFormats + ); + + return (bool) preg_match('/^'.$regex.'$/', $date); + } catch (InvalidArgumentException $e) { + } + + return false; + } + + /////////////////////////////////////////////////////////////////// + /////////////////// ADDITIONS AND SUBTRACTIONS //////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Add centuries to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addCenturies($value) + { + return $this->addYears(static::YEARS_PER_CENTURY * $value); + } + + /** + * Add a century to the instance + * + * @param int $value + * + * @return static + */ + public function addCentury($value = 1) + { + return $this->addCenturies($value); + } + + /** + * Remove centuries from the instance + * + * @param int $value + * + * @return static + */ + public function subCenturies($value) + { + return $this->addCenturies(-1 * $value); + } + + /** + * Remove a century from the instance + * + * @param int $value + * + * @return static + */ + public function subCentury($value = 1) + { + return $this->subCenturies($value); + } + + /** + * Add years to the instance. Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYears($value) + { + if ($this->shouldOverflowYears()) { + return $this->addYearsWithOverflow($value); + } + + return $this->addYearsNoOverflow($value); + } + + /** + * Add a year to the instance + * + * @param int $value + * + * @return static + */ + public function addYear($value = 1) + { + return $this->addYears($value); + } + + /** + * Add years to the instance with no overflow of months + * Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYearsNoOverflow($value) + { + return $this->addMonthsNoOverflow($value * static::MONTHS_PER_YEAR); + } + + /** + * Add year with overflow months set to false + * + * @param int $value + * + * @return static + */ + public function addYearNoOverflow($value = 1) + { + return $this->addYearsNoOverflow($value); + } + + /** + * Add years to the instance. + * Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYearsWithOverflow($value) + { + return $this->modify((int) $value.' year'); + } + + /** + * Add year with overflow. + * + * @param int $value + * + * @return static + */ + public function addYearWithOverflow($value = 1) + { + return $this->addYearsWithOverflow($value); + } + + /** + * Remove years from the instance. + * + * @param int $value + * + * @return static + */ + public function subYears($value) + { + return $this->addYears(-1 * $value); + } + + /** + * Remove a year from the instance + * + * @param int $value + * + * @return static + */ + public function subYear($value = 1) + { + return $this->subYears($value); + } + + /** + * Remove years from the instance with no month overflow. + * + * @param int $value + * + * @return static + */ + public function subYearsNoOverflow($value) + { + return $this->subMonthsNoOverflow($value * static::MONTHS_PER_YEAR); + } + + /** + * Remove year from the instance with no month overflow + * + * @param int $value + * + * @return static + */ + public function subYearNoOverflow($value = 1) + { + return $this->subYearsNoOverflow($value); + } + + /** + * Remove years from the instance. + * + * @param int $value + * + * @return static + */ + public function subYearsWithOverflow($value) + { + return $this->subMonthsWithOverflow($value * static::MONTHS_PER_YEAR); + } + + /** + * Remove year from the instance. + * + * @param int $value + * + * @return static + */ + public function subYearWithOverflow($value = 1) + { + return $this->subYearsWithOverflow($value); + } + + /** + * Add quarters to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addQuarters($value) + { + return $this->addMonths(static::MONTHS_PER_QUARTER * $value); + } + + /** + * Add a quarter to the instance + * + * @param int $value + * + * @return static + */ + public function addQuarter($value = 1) + { + return $this->addQuarters($value); + } + + /** + * Remove quarters from the instance + * + * @param int $value + * + * @return static + */ + public function subQuarters($value) + { + return $this->addQuarters(-1 * $value); + } + + /** + * Remove a quarter from the instance + * + * @param int $value + * + * @return static + */ + public function subQuarter($value = 1) + { + return $this->subQuarters($value); + } + + /** + * Add months to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonths($value) + { + if (static::shouldOverflowMonths()) { + return $this->addMonthsWithOverflow($value); + } + + return $this->addMonthsNoOverflow($value); + } + + /** + * Add a month to the instance + * + * @param int $value + * + * @return static + */ + public function addMonth($value = 1) + { + return $this->addMonths($value); + } + + /** + * Remove months from the instance + * + * @param int $value + * + * @return static + */ + public function subMonths($value) + { + return $this->addMonths(-1 * $value); + } + + /** + * Remove a month from the instance + * + * @param int $value + * + * @return static + */ + public function subMonth($value = 1) + { + return $this->subMonths($value); + } + + /** + * Add months to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonthsWithOverflow($value) + { + return $this->modify((int) $value.' month'); + } + + /** + * Add a month to the instance + * + * @param int $value + * + * @return static + */ + public function addMonthWithOverflow($value = 1) + { + return $this->addMonthsWithOverflow($value); + } + + /** + * Remove months from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthsWithOverflow($value) + { + return $this->addMonthsWithOverflow(-1 * $value); + } + + /** + * Remove a month from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthWithOverflow($value = 1) + { + return $this->subMonthsWithOverflow($value); + } + + /** + * Add months without overflowing to the instance. Positive $value + * travels forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonthsNoOverflow($value) + { + $day = $this->day; + + $this->modify((int) $value.' month'); + + if ($day !== $this->day) { + $this->modify('last day of previous month'); + } + + return $this; + } + + /** + * Add a month with no overflow to the instance + * + * @param int $value + * + * @return static + */ + public function addMonthNoOverflow($value = 1) + { + return $this->addMonthsNoOverflow($value); + } + + /** + * Remove months with no overflow from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthsNoOverflow($value) + { + return $this->addMonthsNoOverflow(-1 * $value); + } + + /** + * Remove a month with no overflow from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthNoOverflow($value = 1) + { + return $this->subMonthsNoOverflow($value); + } + + /** + * Add days to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addDays($value) + { + return $this->modify((int) $value.' day'); + } + + /** + * Add a day to the instance + * + * @param int $value + * + * @return static + */ + public function addDay($value = 1) + { + return $this->addDays($value); + } + + /** + * Remove days from the instance + * + * @param int $value + * + * @return static + */ + public function subDays($value) + { + return $this->addDays(-1 * $value); + } + + /** + * Remove a day from the instance + * + * @param int $value + * + * @return static + */ + public function subDay($value = 1) + { + return $this->subDays($value); + } + + /** + * Add weekdays to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addWeekdays($value) + { + // Fix for weekday bug https://bugs.php.net/bug.php?id=54909 + $t = $this->toTimeString(); + $this->modify((int) $value.' weekday'); + + return $this->setTimeFromTimeString($t); + } + + /** + * Add a weekday to the instance + * + * @param int $value + * + * @return static + */ + public function addWeekday($value = 1) + { + return $this->addWeekdays($value); + } + + /** + * Remove weekdays from the instance + * + * @param int $value + * + * @return static + */ + public function subWeekdays($value) + { + return $this->addWeekdays(-1 * $value); + } + + /** + * Remove a weekday from the instance + * + * @param int $value + * + * @return static + */ + public function subWeekday($value = 1) + { + return $this->subWeekdays($value); + } + + /** + * Add weeks to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addWeeks($value) + { + return $this->modify((int) $value.' week'); + } + + /** + * Add a week to the instance + * + * @param int $value + * + * @return static + */ + public function addWeek($value = 1) + { + return $this->addWeeks($value); + } + + /** + * Remove weeks to the instance + * + * @param int $value + * + * @return static + */ + public function subWeeks($value) + { + return $this->addWeeks(-1 * $value); + } + + /** + * Remove a week from the instance + * + * @param int $value + * + * @return static + */ + public function subWeek($value = 1) + { + return $this->subWeeks($value); + } + + /** + * Add hours to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addHours($value) + { + return $this->modify((int) $value.' hour'); + } + + /** + * Add hours to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealHours($value) + { + return $this->addRealMinutes($value * static::MINUTES_PER_HOUR); + } + + /** + * Add an hour to the instance. + * + * @param int $value + * + * @return static + */ + public function addHour($value = 1) + { + return $this->addHours($value); + } + + /** + * Add an hour to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealHour($value = 1) + { + return $this->addRealHours($value); + } + + /** + * Remove hours from the instance. + * + * @param int $value + * + * @return static + */ + public function subHours($value) + { + return $this->addHours(-1 * $value); + } + + /** + * Remove hours from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealHours($value) + { + return $this->addRealHours(-1 * $value); + } + + /** + * Remove an hour from the instance. + * + * @param int $value + * + * @return static + */ + public function subHour($value = 1) + { + return $this->subHours($value); + } + + /** + * Remove an hour from the instance. + * + * @param int $value + * + * @return static + */ + public function subRealHour($value = 1) + { + return $this->subRealHours($value); + } + + /** + * Add minutes to the instance using timestamp. Positive $value + * travels forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMinutes($value) + { + return $this->modify((int) $value.' minute'); + } + + /** + * Add minutes to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealMinutes($value) + { + return $this->addRealSeconds($value * static::SECONDS_PER_MINUTE); + } + + /** + * Add a minute to the instance. + * + * @param int $value + * + * @return static + */ + public function addMinute($value = 1) + { + return $this->addMinutes($value); + } + + /** + * Add a minute to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealMinute($value = 1) + { + return $this->addRealMinutes($value); + } + + /** + * Remove a minute from the instance. + * + * @param int $value + * + * @return static + */ + public function subMinute($value = 1) + { + return $this->subMinutes($value); + } + + /** + * Remove a minute from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealMinute($value = 1) + { + return $this->addRealMinutes(-1 * $value); + } + + /** + * Remove minutes from the instance. + * + * @param int $value + * + * @return static + */ + public function subMinutes($value) + { + return $this->addMinutes(-1 * $value); + } + + /** + * Remove a minute from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealMinutes($value = 1) + { + return $this->subRealMinute($value); + } + + /** + * Add seconds to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addSeconds($value) + { + return $this->modify((int) $value.' second'); + } + + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealSeconds($value) + { + return $this->setTimestamp($this->getTimestamp() + $value); + } + + /** + * Add a second to the instance. + * + * @param int $value + * + * @return static + */ + public function addSecond($value = 1) + { + return $this->addSeconds($value); + } + + /** + * Add a second to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealSecond($value = 1) + { + return $this->addRealSeconds($value); + } + + /** + * Remove seconds from the instance. + * + * @param int $value + * + * @return static + */ + public function subSeconds($value) + { + return $this->addSeconds(-1 * $value); + } + + /** + * Remove seconds from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealSeconds($value) + { + return $this->addRealSeconds(-1 * $value); + } + + /** + * Remove a second from the instance + * + * @param int $value + * + * @return static + */ + public function subSecond($value = 1) + { + return $this->subSeconds($value); + } + + /** + * Remove a second from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealSecond($value = 1) + { + return $this->subRealSeconds($value); + } + + /////////////////////////////////////////////////////////////////// + /////////////////////////// DIFFERENCES /////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * @param DateInterval $diff + * @param bool $absolute + * @param bool $trimMicroseconds + * + * @return CarbonInterval + */ + protected static function fixDiffInterval(DateInterval $diff, $absolute, $trimMicroseconds) + { + $diff = CarbonInterval::instance($diff, $trimMicroseconds); + + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '7.1.0-dev', '<')) { + return $diff; + } + + // Work-around for https://bugs.php.net/bug.php?id=77145 + if ($diff->f > 0 && $diff->y === -1 && $diff->m === 11 && $diff->d >= 27 && $diff->h === 23 && $diff->i === 59 && $diff->s === 59) { + $diff->y = 0; + $diff->m = 0; + $diff->d = 0; + $diff->h = 0; + $diff->i = 0; + $diff->s = 0; + $diff->f = (1000000 - round($diff->f * 1000000)) / 1000000; + $diff->invert(); + } elseif ($diff->f < 0) { + if ($diff->s !== 0 || $diff->i !== 0 || $diff->h !== 0 || $diff->d !== 0 || $diff->m !== 0 || $diff->y !== 0) { + $diff->f = (round($diff->f * 1000000) + 1000000) / 1000000; + $diff->s--; + if ($diff->s < 0) { + $diff->s += 60; + $diff->i--; + if ($diff->i < 0) { + $diff->i += 60; + $diff->h--; + if ($diff->h < 0) { + $diff->h += 24; + $diff->d--; + if ($diff->d < 0) { + $diff->d += 30; + $diff->m--; + if ($diff->m < 0) { + $diff->m += 12; + $diff->y--; + } + } + } + } + } + } else { + $diff->f *= -1; + $diff->invert(); + } + } + // @codeCoverageIgnoreEnd + if ($absolute && $diff->invert) { + $diff->invert(); + } + + return $diff; + } + + /** + * Get the difference as a CarbonInterval instance. + * + * Pass false as second argument to get a microseconds-precise interval. Else + * microseconds in the original interval will not be kept. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $trimMicroseconds (true by default) + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true, $trimMicroseconds = true) + { + $from = $this; + $to = $this->resolveCarbon($date); + + if ($trimMicroseconds) { + $from = $from->copy()->startOfSecond(); + $to = $to->copy()->startOfSecond(); + } + + return static::fixDiffInterval($from->diff($to, $absolute), $absolute, $trimMicroseconds); + } + + /** + * Get the difference in years + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%y'); + } + + /** + * Get the difference in months + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true) + { + $date = $this->resolveCarbon($date); + + return $this->diffInYears($date, $absolute) * static::MONTHS_PER_YEAR + (int) $this->diff($date, $absolute)->format('%r%m'); + } + + /** + * Get the difference in weeks + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true) + { + return (int) ($this->diffInDays($date, $absolute) / static::DAYS_PER_WEEK); + } + + /** + * Get the difference in days + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%a'); + } + + /** + * Get the difference in days using a filter closure + * + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::day(), $callback, $date, $absolute); + } + + /** + * Get the difference in hours using a filter closure + * + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::hour(), $callback, $date, $absolute); + } + + /** + * Get the difference by the given interval using a filter closure + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $inverse = false; + + if ($end < $start) { + $start = $end; + $end = $this; + $inverse = true; + } + + $period = new DatePeriod($start, $ci, $end); + $values = array_filter(iterator_to_array($period), function ($date) use ($callback) { + return call_user_func($callback, Carbon::instance($date)); + }); + + $diff = count($values); + + return $inverse && !$absolute ? -$diff : $diff; + } + + /** + * Get the difference in weekdays + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(function (Carbon $date) { + return $date->isWeekday(); + }, $date, $absolute); + } + + /** + * Get the difference in weekend days using a filter + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(function (Carbon $date) { + return $date->isWeekend(); + }, $date, $absolute); + } + + /** + * Get the difference in hours. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in hours using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in minutes. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in minutes using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in seconds. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true) + { + $diff = $this->diff($this->resolveCarbon($date)); + if (!$diff->days && version_compare(PHP_VERSION, '5.4.0-dev', '>=')) { + $diff = static::fixDiffInterval($diff, $absolute, false); + } + $value = $diff->days * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE + + $diff->h * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE + + $diff->i * static::SECONDS_PER_MINUTE + + $diff->s; + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in seconds using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true) + { + $date = $this->resolveCarbon($date); + $value = $date->getTimestamp() - $this->getTimestamp(); + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in milliseconds. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = true) + { + return (int) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + + /** + * Get the difference in milliseconds using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = true) + { + return (int) ($this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + + /** + * Get the difference in microseconds. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = true) + { + $diff = $this->diff($this->resolveCarbon($date)); + $micro = isset($diff->f) ? $diff->f : 0; + $value = (int) round((((($diff->days * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + ($micro + $diff->s)) * static::MICROSECONDS_PER_SECOND); + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in microseconds using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true) + { + /** @var Carbon $date */ + $date = $this->resolveCarbon($date); + $value = ($date->timestamp - $this->timestamp) * static::MICROSECONDS_PER_SECOND + + $date->micro - $this->micro; + + return $absolute ? abs($value) : $value; + } + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight() + { + return $this->diffInSeconds($this->copy()->startOfDay()); + } + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay() + { + return $this->diffInSeconds($this->copy()->endOfDay()); + } + + /** + * Get the difference in a human readable format in the current locale. + * + * When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the past to another value: + * 1 hour before + * 5 months before + * + * When comparing a value in the future to another value: + * 1 hour after + * 5 months after + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function diffForHumans($other = null, $absolute = false, $short = false, $parts = 1) + { + $isNow = $other === null; + $relativeToNow = $isNow; + + if ($absolute === static::DIFF_RELATIVE_TO_NOW) { + $absolute = false; + $relativeToNow = true; + } elseif ($absolute === static::DIFF_RELATIVE_TO_OTHER) { + $absolute = false; + $relativeToNow = false; + } + + $interval = array(); + + $parts = min(6, max(1, (int) $parts)); + $count = 1; + $unit = $short ? 's' : 'second'; + + if ($isNow) { + $other = $this->nowWithSameTz(); + } elseif (!$other instanceof DateTime && !$other instanceof DateTimeInterface) { + $other = static::parse($other); + } + + $diffInterval = $this->diff($other); + + $diffIntervalArray = array( + array('value' => $diffInterval->y, 'unit' => 'year', 'unitShort' => 'y'), + array('value' => $diffInterval->m, 'unit' => 'month', 'unitShort' => 'm'), + array('value' => $diffInterval->d, 'unit' => 'day', 'unitShort' => 'd'), + array('value' => $diffInterval->h, 'unit' => 'hour', 'unitShort' => 'h'), + array('value' => $diffInterval->i, 'unit' => 'minute', 'unitShort' => 'min'), + array('value' => $diffInterval->s, 'unit' => 'second', 'unitShort' => 's'), + ); + + foreach ($diffIntervalArray as $diffIntervalData) { + if ($diffIntervalData['value'] > 0) { + $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit']; + $count = $diffIntervalData['value']; + + if ($diffIntervalData['unit'] === 'day' && $count >= static::DAYS_PER_WEEK) { + $unit = $short ? 'w' : 'week'; + $count = (int) ($count / static::DAYS_PER_WEEK); + + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + + // get the count days excluding weeks (might be zero) + $numOfDaysCount = (int) ($diffIntervalData['value'] - ($count * static::DAYS_PER_WEEK)); + + if ($numOfDaysCount > 0 && count($interval) < $parts) { + $unit = $short ? 'd' : 'day'; + $count = $numOfDaysCount; + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + } else { + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + } + + // break the loop after we get the required number of parts in array + if (count($interval) >= $parts) { + break; + } + } + + if (count($interval) === 0) { + if ($isNow && static::getHumanDiffOptions() & self::JUST_NOW) { + $key = 'diff_now'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + $count = static::getHumanDiffOptions() & self::NO_ZERO_DIFF ? 1 : 0; + $unit = $short ? 's' : 'second'; + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + + // join the interval parts by a space + $time = implode(' ', $interval); + + unset($diffIntervalArray, $interval); + + if ($absolute) { + return $time; + } + + $isFuture = $diffInterval->invert === 1; + + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + + if ($parts === 1) { + if ($isNow && $unit === 'day') { + if ($count === 1 && static::getHumanDiffOptions() & self::ONE_DAY_WORDS) { + $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + if ($count === 2 && static::getHumanDiffOptions() & self::TWO_DAY_WORDS) { + $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + } + // Some languages have special pluralization for past and future tense. + $key = $unit.'_'.$transId; + if ($key !== static::translator()->transChoice($key, $count)) { + $time = static::translator()->transChoice($key, $count, array(':count' => $count)); + } + } + + return static::translator()->trans($transId, array(':time' => $time)); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale. + * + * When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the past to another value: + * 1 hour before + * 5 months before + * + * When comparing a value in the future to another value: + * 1 hour after + * 5 months after + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function from($other = null, $absolute = false, $short = false, $parts = 1) + { + if (!$other && !$absolute) { + $absolute = static::DIFF_RELATIVE_TO_NOW; + } + + return $this->diffForHumans($other, $absolute, $short, $parts); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale. + * + * When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the past to another value: + * 1 hour before + * 5 months before + * + * When comparing a value in the future to another value: + * 1 hour after + * 5 months after + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function since($other = null, $absolute = false, $short = false, $parts = 1) + { + return $this->diffForHumans($other, $absolute, $short, $parts); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * When comparing a value in the past to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the future to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the past to another value: + * 1 hour after + * 5 months after + * + * When comparing a value in the future to another value: + * 1 hour before + * 5 months before + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function to($other = null, $absolute = false, $short = false, $parts = 1) + { + if (!$other && !$absolute) { + $absolute = static::DIFF_RELATIVE_TO_NOW; + } + + return $this->resolveCarbon($other)->diffForHumans($this, $absolute, $short, $parts); + } + + /** + * @alias to + * + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function until($other = null, $absolute = false, $short = false, $parts = 1) + { + return $this->to($other, $absolute, $short, $parts); + } + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function fromNow($absolute = null, $short = false, $parts = 1) + { + $other = null; + + if ($absolute instanceof DateTimeInterface) { + list($other, $absolute, $short, $parts) = array_pad(func_get_args(), 5, null); + } + + return $this->from($other, $absolute, $short, $parts); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function toNow($absolute = null, $short = false, $parts = 1) + { + return $this->to(null, $absolute, $short, $parts); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function ago($absolute = null, $short = false, $parts = 1) + { + $other = null; + + if ($absolute instanceof DateTimeInterface) { + list($other, $absolute, $short, $parts) = array_pad(func_get_args(), 5, null); + } + + return $this->from($other, $absolute, $short, $parts); + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// MODIFIERS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Resets the time to 00:00:00 start of day + * + * @return static + */ + public function startOfDay() + { + return $this->modify('00:00:00.000000'); + } + + /** + * Resets the time to 23:59:59 end of day + * + * @return static + */ + public function endOfDay() + { + return $this->modify('23:59:59.999999'); + } + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @return static + */ + public function startOfMonth() + { + return $this->setDate($this->year, $this->month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the month and time to 23:59:59 + * + * @return static + */ + public function endOfMonth() + { + return $this->setDate($this->year, $this->month, $this->daysInMonth)->endOfDay(); + } + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @return static + */ + public function startOfQuarter() + { + $month = ($this->quarter - 1) * static::MONTHS_PER_QUARTER + 1; + + return $this->setDate($this->year, $month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the quarter and time to 23:59:59 + * + * @return static + */ + public function endOfQuarter() + { + return $this->startOfQuarter()->addMonths(static::MONTHS_PER_QUARTER - 1)->endOfMonth(); + } + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @return static + */ + public function startOfYear() + { + return $this->setDate($this->year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the year and time to 23:59:59 + * + * @return static + */ + public function endOfYear() + { + return $this->setDate($this->year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @return static + */ + public function startOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the decade and time to 23:59:59 + * + * @return static + */ + public function endOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE + static::YEARS_PER_DECADE - 1; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @return static + */ + public function startOfCentury() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_CENTURY; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the century and time to 23:59:59 + * + * @return static + */ + public function endOfCentury() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_CENTURY + static::YEARS_PER_CENTURY; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @return static + */ + public function startOfMillennium() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_MILLENNIUM; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the century and time to 23:59:59 + * + * @return static + */ + public function endOfMillennium() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_MILLENNIUM + static::YEARS_PER_MILLENNIUM; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @return static + */ + public function startOfWeek() + { + while ($this->dayOfWeek !== static::$weekStartsAt) { + $this->subDay(); + } + + return $this->startOfDay(); + } + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59 + * + * @return static + */ + public function endOfWeek() + { + while ($this->dayOfWeek !== static::$weekEndsAt) { + $this->addDay(); + } + + return $this->endOfDay(); + } + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @return static + */ + public function startOfHour() + { + return $this->setTime($this->hour, 0, 0); + } + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @return static + */ + public function endOfHour() + { + return $this->modify("$this->hour:59:59.999999"); + } + + /** + * Modify to start of current minute, seconds become 0 + * + * @return static + */ + public function startOfMinute() + { + return $this->setTime($this->hour, $this->minute, 0); + } + + /** + * Modify to end of current minute, seconds become 59 + * + * @return static + */ + public function endOfMinute() + { + return $this->modify("$this->hour:$this->minute:59.999999"); + } + + /** + * Modify to start of current minute, seconds become 0 + * + * @return static + */ + public function startOfSecond() + { + return $this->modify("$this->hour:$this->minute:$this->second.0"); + } + + /** + * Modify to end of current minute, seconds become 59 + * + * @return static + */ + public function endOfSecond() + { + return $this->modify("$this->hour:$this->minute:$this->second.999999"); + } + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay() + { + return $this->setTime(self::$midDayAt, 0, 0); + } + + /** + * Modify to the next occurrence of a given day of the week. + * If no dayOfWeek is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function next($dayOfWeek = null) + { + if ($dayOfWeek === null) { + $dayOfWeek = $this->dayOfWeek; + } + + return $this->startOfDay()->modify('next '.static::$days[$dayOfWeek]); + } + + /** + * Go forward or backward to the next week- or weekend-day. + * + * @param bool $weekday + * @param bool $forward + * + * @return $this + */ + private function nextOrPreviousDay($weekday = true, $forward = true) + { + $step = $forward ? 1 : -1; + + do { + $this->addDay($step); + } while ($weekday ? $this->isWeekend() : $this->isWeekday()); + + return $this; + } + + /** + * Go forward to the next weekday. + * + * @return $this + */ + public function nextWeekday() + { + return $this->nextOrPreviousDay(); + } + + /** + * Go backward to the previous weekday. + * + * @return $this + */ + public function previousWeekday() + { + return $this->nextOrPreviousDay(true, false); + } + + /** + * Go forward to the next weekend day. + * + * @return $this + */ + public function nextWeekendDay() + { + return $this->nextOrPreviousDay(false); + } + + /** + * Go backward to the previous weekend day. + * + * @return $this + */ + public function previousWeekendDay() + { + return $this->nextOrPreviousDay(false, false); + } + + /** + * Modify to the previous occurrence of a given day of the week. + * If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function previous($dayOfWeek = null) + { + if ($dayOfWeek === null) { + $dayOfWeek = $this->dayOfWeek; + } + + return $this->startOfDay()->modify('last '.static::$days[$dayOfWeek]); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null) + { + $this->startOfDay(); + + if ($dayOfWeek === null) { + return $this->day(1); + } + + return $this->modify('first '.static::$days[$dayOfWeek].' of '.$this->format('F').' '.$this->year); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null) + { + $this->startOfDay(); + + if ($dayOfWeek === null) { + return $this->day($this->daysInMonth); + } + + return $this->modify('last '.static::$days[$dayOfWeek].' of '.$this->format('F').' '.$this->year); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek) + { + $date = $this->copy()->firstOfMonth(); + $check = $date->format('Y-m'); + $date->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $date->format('Y-m') === $check ? $this->modify($date) : false; + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER - 2, 1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER, 1)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek) + { + $date = $this->copy()->day(1)->month($this->quarter * static::MONTHS_PER_QUARTER); + $lastMonth = $date->month; + $year = $date->year; + $date->firstOfQuarter()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return ($lastMonth < $date->month || $year !== $date->year) ? false : $this->modify($date); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null) + { + return $this->month(1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null) + { + return $this->month(static::MONTHS_PER_YEAR)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek) + { + $date = $this->copy()->firstOfYear()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $this->year === $date->year ? $this->modify($date) : false; + } + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function average($date = null) + { + $date = $this->resolveCarbon($date); + $increment = $this->diffInRealSeconds($date, false) / 2; + $intIncrement = floor($increment); + $microIncrement = (int) (($date->micro - $this->micro) / 2 + 1000000 * ($increment - $intIncrement)); + $micro = (int) ($this->micro + $microIncrement); + while ($micro >= 1000000) { + $micro -= 1000000; + $intIncrement++; + } + $this->addSeconds($intIncrement); + + if (version_compare(PHP_VERSION, '7.1.8-dev', '>=')) { + $this->setTime($this->hour, $this->minute, $this->second, $micro); + } + + return $this; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////////// SERIALIZATION ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize() + { + return serialize($this); + } + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function fromSerialized($value) + { + $instance = @unserialize($value); + + if (!$instance instanceof static) { + throw new InvalidArgumentException('Invalid serialized value.'); + } + + return $instance; + } + + /** + * The __set_state handler. + * + * @param array $array + * + * @return static + */ + public static function __set_state($array) + { + return static::instance(parent::__set_state($array)); + } + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + public function jsonSerialize() + { + if (static::$serializer) { + return call_user_func(static::$serializer, $this); + } + + $carbon = $this; + + return call_user_func(function () use ($carbon) { + return get_object_vars($carbon); + }); + } + + /** + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback) + { + static::$serializer = $callback; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////////////// MACRO ///////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Register a custom macro. + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$localMacros[$name] = $macro; + } + + /** + * Remove all macros. + */ + public static function resetMacros() + { + static::$localMacros = array(); + } + + /** + * Mix another object into the class. + * + * @param object $mixin + * + * @return void + */ + public static function mixin($mixin) + { + $reflection = new \ReflectionClass($mixin); + $methods = $reflection->getMethods( + \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + + /** + * Checks if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$localMacros[$name]); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * + * @throws \BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + if (!static::hasMacro($method)) { + throw new \BadMethodCallException("Method $method does not exist."); + } + + if (static::$localMacros[$method] instanceof Closure && method_exists('Closure', 'bind')) { + return call_user_func_array(Closure::bind(static::$localMacros[$method], null, get_called_class()), $parameters); + } + + return call_user_func_array(static::$localMacros[$method], $parameters); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * + * @throws \BadMethodCallException|\ReflectionException + * + * @return mixed + */ + public function __call($method, $parameters) + { + if (!static::hasMacro($method)) { + throw new \BadMethodCallException("Method $method does not exist."); + } + + $macro = static::$localMacros[$method]; + + $reflexion = new \ReflectionFunction($macro); + $reflectionParameters = $reflexion->getParameters(); + $expectedCount = count($reflectionParameters); + $actualCount = count($parameters); + if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') { + for ($i = $actualCount; $i < $expectedCount - 1; $i++) { + $parameters[] = $reflectionParameters[$i]->getDefaultValue(); + } + $parameters[] = $this; + } + + if ($macro instanceof Closure && method_exists($macro, 'bindTo')) { + return call_user_func_array($macro->bindTo($this, get_class($this)), $parameters); + } + + return call_user_func_array($macro, $parameters); + } + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + return array_filter(get_object_vars($this), function ($var) { + return $var; + }); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return object + */ + public function cast($className) + { + if (!method_exists($className, 'instance')) { + throw new \InvalidArgumentException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } +} diff --git a/core/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php b/core/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php new file mode 100644 index 0000000..e8c6032 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php @@ -0,0 +1,1163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; +use DateInterval; +use InvalidArgumentException; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * A simple API extension for DateInterval. + * The implementation provides helpers to handle weeks but only days are saved. + * Weeks are calculated based on the total days of the current instance. + * + * @property int $years Total years of the current interval. + * @property int $months Total months of the current interval. + * @property int $weeks Total weeks of the current interval calculated from the days. + * @property int $dayz Total days of the current interval (weeks * 7 + days). + * @property int $hours Total hours of the current interval. + * @property int $minutes Total minutes of the current interval. + * @property int $seconds Total seconds of the current interval. + * @property-read int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7). + * @property-read int $daysExcludeWeeks alias of dayzExcludeWeeks + * @property-read float $totalYears Number of years equivalent to the interval. + * @property-read float $totalMonths Number of months equivalent to the interval. + * @property-read float $totalWeeks Number of weeks equivalent to the interval. + * @property-read float $totalDays Number of days equivalent to the interval. + * @property-read float $totalDayz Alias for totalDays. + * @property-read float $totalHours Number of hours equivalent to the interval. + * @property-read float $totalMinutes Number of minutes equivalent to the interval. + * @property-read float $totalSeconds Number of seconds equivalent to the interval. + * + * @method static CarbonInterval years($years = 1) Create instance specifying a number of years. + * @method static CarbonInterval year($years = 1) Alias for years() + * @method static CarbonInterval months($months = 1) Create instance specifying a number of months. + * @method static CarbonInterval month($months = 1) Alias for months() + * @method static CarbonInterval weeks($weeks = 1) Create instance specifying a number of weeks. + * @method static CarbonInterval week($weeks = 1) Alias for weeks() + * @method static CarbonInterval days($days = 1) Create instance specifying a number of days. + * @method static CarbonInterval dayz($days = 1) Alias for days() + * @method static CarbonInterval day($days = 1) Alias for days() + * @method static CarbonInterval hours($hours = 1) Create instance specifying a number of hours. + * @method static CarbonInterval hour($hours = 1) Alias for hours() + * @method static CarbonInterval minutes($minutes = 1) Create instance specifying a number of minutes. + * @method static CarbonInterval minute($minutes = 1) Alias for minutes() + * @method static CarbonInterval seconds($seconds = 1) Create instance specifying a number of seconds. + * @method static CarbonInterval second($seconds = 1) Alias for seconds() + * @method CarbonInterval years($years = 1) Set the years portion of the current interval. + * @method CarbonInterval year($years = 1) Alias for years(). + * @method CarbonInterval months($months = 1) Set the months portion of the current interval. + * @method CarbonInterval month($months = 1) Alias for months(). + * @method CarbonInterval weeks($weeks = 1) Set the weeks portion of the current interval. Will overwrite dayz value. + * @method CarbonInterval week($weeks = 1) Alias for weeks(). + * @method CarbonInterval days($days = 1) Set the days portion of the current interval. + * @method CarbonInterval dayz($days = 1) Alias for days(). + * @method CarbonInterval day($days = 1) Alias for days(). + * @method CarbonInterval hours($hours = 1) Set the hours portion of the current interval. + * @method CarbonInterval hour($hours = 1) Alias for hours(). + * @method CarbonInterval minutes($minutes = 1) Set the minutes portion of the current interval. + * @method CarbonInterval minute($minutes = 1) Alias for minutes(). + * @method CarbonInterval seconds($seconds = 1) Set the seconds portion of the current interval. + * @method CarbonInterval second($seconds = 1) Alias for seconds(). + */ +class CarbonInterval extends DateInterval +{ + /** + * Interval spec period designators + */ + const PERIOD_PREFIX = 'P'; + const PERIOD_YEARS = 'Y'; + const PERIOD_MONTHS = 'M'; + const PERIOD_DAYS = 'D'; + const PERIOD_TIME_PREFIX = 'T'; + const PERIOD_HOURS = 'H'; + const PERIOD_MINUTES = 'M'; + const PERIOD_SECONDS = 'S'; + + /** + * A translator to ... er ... translate stuff + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * @var array|null + */ + protected static $cascadeFactors; + + /** + * @var array|null + */ + private static $flipCascadeFactors; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = array(); + + /** + * Before PHP 5.4.20/5.5.4 instead of FALSE days will be set to -99999 when the interval instance + * was created by DateTime::diff(). + */ + const PHP_DAYS_FALSE = -99999; + + /** + * Mapping of units and factors for cascading. + * + * Should only be modified by changing the factors or referenced constants. + * + * @return array + */ + public static function getCascadeFactors() + { + return static::$cascadeFactors ?: array( + 'minutes' => array(Carbon::SECONDS_PER_MINUTE, 'seconds'), + 'hours' => array(Carbon::MINUTES_PER_HOUR, 'minutes'), + 'dayz' => array(Carbon::HOURS_PER_DAY, 'hours'), + 'months' => array(Carbon::DAYS_PER_WEEK * Carbon::WEEKS_PER_MONTH, 'dayz'), + 'years' => array(Carbon::MONTHS_PER_YEAR, 'months'), + ); + } + + private static function standardizeUnit($unit) + { + $unit = rtrim($unit, 'sz').'s'; + + return $unit === 'days' ? 'dayz' : $unit; + } + + private static function getFlipCascadeFactors() + { + if (!self::$flipCascadeFactors) { + self::$flipCascadeFactors = array(); + foreach (static::getCascadeFactors() as $to => $tuple) { + list($factor, $from) = $tuple; + + self::$flipCascadeFactors[self::standardizeUnit($from)] = array(self::standardizeUnit($to), $factor); + } + } + + return self::$flipCascadeFactors; + } + + /** + * @param array $cascadeFactors + */ + public static function setCascadeFactors(array $cascadeFactors) + { + self::$flipCascadeFactors = null; + static::$cascadeFactors = $cascadeFactors; + } + + /** + * Determine if the interval was created via DateTime:diff() or not. + * + * @param DateInterval $interval + * + * @return bool + */ + private static function wasCreatedFromDiff(DateInterval $interval) + { + return $interval->days !== false && $interval->days !== static::PHP_DAYS_FALSE; + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Create a new CarbonInterval instance. + * + * @param int $years + * @param int $months + * @param int $weeks + * @param int $days + * @param int $hours + * @param int $minutes + * @param int $seconds + */ + public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null) + { + $spec = $years; + + if (!is_string($spec) || floatval($years) || preg_match('/^[0-9.]/', $years)) { + $spec = static::PERIOD_PREFIX; + + $spec .= $years > 0 ? $years.static::PERIOD_YEARS : ''; + $spec .= $months > 0 ? $months.static::PERIOD_MONTHS : ''; + + $specDays = 0; + $specDays += $weeks > 0 ? $weeks * static::getDaysPerWeek() : 0; + $specDays += $days > 0 ? $days : 0; + + $spec .= $specDays > 0 ? $specDays.static::PERIOD_DAYS : ''; + + if ($hours > 0 || $minutes > 0 || $seconds > 0) { + $spec .= static::PERIOD_TIME_PREFIX; + $spec .= $hours > 0 ? $hours.static::PERIOD_HOURS : ''; + $spec .= $minutes > 0 ? $minutes.static::PERIOD_MINUTES : ''; + $spec .= $seconds > 0 ? $seconds.static::PERIOD_SECONDS : ''; + } + + if ($spec === static::PERIOD_PREFIX) { + // Allow the zero interval. + $spec .= '0'.static::PERIOD_YEARS; + } + } + + parent::__construct($spec); + } + + /** + * Returns the factor for a given source-to-target couple. + * + * @param string $source + * @param string $target + * + * @return int|null + */ + public static function getFactor($source, $target) + { + $source = self::standardizeUnit($source); + $target = self::standardizeUnit($target); + $factors = static::getFlipCascadeFactors(); + if (isset($factors[$source])) { + list($to, $factor) = $factors[$source]; + if ($to === $target) { + return $factor; + } + } + + return null; + } + + /** + * Returns current config for days per week. + * + * @return int + */ + public static function getDaysPerWeek() + { + return static::getFactor('dayz', 'weeks') ?: Carbon::DAYS_PER_WEEK; + } + + /** + * Returns current config for hours per day. + * + * @return int + */ + public static function getHoursPerDay() + { + return static::getFactor('hours', 'dayz') ?: Carbon::HOURS_PER_DAY; + } + + /** + * Returns current config for minutes per hour. + * + * @return int + */ + public static function getMinutesPerHours() + { + return static::getFactor('minutes', 'hours') ?: Carbon::MINUTES_PER_HOUR; + } + + /** + * Returns current config for seconds per minute. + * + * @return int + */ + public static function getSecondsPerMinutes() + { + return static::getFactor('seconds', 'minutes') ?: Carbon::SECONDS_PER_MINUTE; + } + + /** + * Create a new CarbonInterval instance from specific values. + * This is an alias for the constructor that allows better fluent + * syntax as it allows you to do CarbonInterval::create(1)->fn() rather than + * (new CarbonInterval(1))->fn(). + * + * @param int $years + * @param int $months + * @param int $weeks + * @param int $days + * @param int $hours + * @param int $minutes + * @param int $seconds + * + * @return static + */ + public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null) + { + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds); + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + $date = new static($this->spec()); + $date->invert = $this->invert; + + return $date; + } + + /** + * Provide static helpers to create instances. Allows CarbonInterval::years(3). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $name + * @param array $args + * + * @return static + */ + public static function __callStatic($name, $args) + { + $arg = count($args) === 0 ? 1 : $args[0]; + + switch ($name) { + case 'years': + case 'year': + return new static($arg); + + case 'months': + case 'month': + return new static(null, $arg); + + case 'weeks': + case 'week': + return new static(null, null, $arg); + + case 'days': + case 'dayz': + case 'day': + return new static(null, null, null, $arg); + + case 'hours': + case 'hour': + return new static(null, null, null, null, $arg); + + case 'minutes': + case 'minute': + return new static(null, null, null, null, null, $arg); + + case 'seconds': + case 'second': + return new static(null, null, null, null, null, null, $arg); + } + + if (static::hasMacro($name)) { + return call_user_func_array( + array(new static(0), $name), $args + ); + } + } + + /** + * Creates a CarbonInterval from string. + * + * Format: + * + * Suffix | Unit | Example | DateInterval expression + * -------|---------|---------|------------------------ + * y | years | 1y | P1Y + * mo | months | 3mo | P3M + * w | weeks | 2w | P2W + * d | days | 28d | P28D + * h | hours | 4h | PT4H + * m | minutes | 12m | PT12M + * s | seconds | 59s | PT59S + * + * e. g. `1w 3d 4h 32m 23s` is converted to 10 days 4 hours 32 minutes and 23 seconds. + * + * Special cases: + * - An empty string will return a zero interval + * - Fractions are allowed for weeks, days, hours and minutes and will be converted + * and rounded to the next smaller value (caution: 0.5w = 4d) + * + * @param string $intervalDefinition + * + * @return static + */ + public static function fromString($intervalDefinition) + { + if (empty($intervalDefinition)) { + return new static(0); + } + + $years = 0; + $months = 0; + $weeks = 0; + $days = 0; + $hours = 0; + $minutes = 0; + $seconds = 0; + + $pattern = '/(\d+(?:\.\d+)?)\h*([^\d\h]*)/i'; + preg_match_all($pattern, $intervalDefinition, $parts, PREG_SET_ORDER); + while ($match = array_shift($parts)) { + list($part, $value, $unit) = $match; + $intValue = intval($value); + $fraction = floatval($value) - $intValue; + switch (strtolower($unit)) { + case 'year': + case 'years': + case 'y': + $years += $intValue; + break; + + case 'month': + case 'months': + case 'mo': + $months += $intValue; + break; + + case 'week': + case 'weeks': + case 'w': + $weeks += $intValue; + if ($fraction) { + $parts[] = array(null, $fraction * static::getDaysPerWeek(), 'd'); + } + break; + + case 'day': + case 'days': + case 'd': + $days += $intValue; + if ($fraction) { + $parts[] = array(null, $fraction * static::getHoursPerDay(), 'h'); + } + break; + + case 'hour': + case 'hours': + case 'h': + $hours += $intValue; + if ($fraction) { + $parts[] = array(null, $fraction * static::getMinutesPerHours(), 'm'); + } + break; + + case 'minute': + case 'minutes': + case 'm': + $minutes += $intValue; + if ($fraction) { + $seconds += round($fraction * static::getSecondsPerMinutes()); + } + break; + + case 'second': + case 'seconds': + case 's': + $seconds += $intValue; + break; + + default: + throw new InvalidArgumentException( + sprintf('Invalid part %s in definition %s', $part, $intervalDefinition) + ); + } + } + + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds); + } + + /** + * Create a CarbonInterval instance from a DateInterval one. Can not instance + * DateInterval objects created from DateTime::diff() as you can't externally + * set the $days field. + * + * Pass false as second argument to get a microseconds-precise interval. Else + * microseconds in the original interval will not be kept. + * + * @param DateInterval $di + * @param bool $trimMicroseconds (true by default) + * + * @return static + */ + public static function instance(DateInterval $di, $trimMicroseconds = true) + { + $microseconds = $trimMicroseconds || version_compare(PHP_VERSION, '7.1.0-dev', '<') ? 0 : $di->f; + $instance = new static(static::getDateIntervalSpec($di)); + if ($microseconds) { + $instance->f = $microseconds; + } + $instance->invert = $di->invert; + foreach (array('y', 'm', 'd', 'h', 'i', 's') as $unit) { + if ($di->$unit < 0) { + $instance->$unit *= -1; + } + } + + return $instance; + } + + /** + * Make a CarbonInterval instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be intervals (skip dates + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof DateInterval) { + return static::instance($var); + } + + if (is_string($var)) { + $var = trim($var); + + if (substr($var, 0, 1) === 'P') { + return new static($var); + } + + if (preg_match('/^(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+$/i', $var)) { + return static::fromString($var); + } + } + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// LOCALIZATION ////////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Initialize the translator instance if necessary. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + protected static function translator() + { + if (static::$translator === null) { + static::$translator = Translator::get(); + } + + return static::$translator; + } + + /** + * Get the translator instance in use. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + + /** + * Set the translator instance to use. + * + * @param TranslatorInterface $translator + */ + public static function setTranslator(TranslatorInterface $translator) + { + static::$translator = $translator; + } + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale() + { + return static::translator()->getLocale(); + } + + /** + * Set the current translator locale. + * + * @param string $locale + */ + public static function setLocale($locale) + { + return static::translator()->setLocale($locale) !== false; + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return int|float + */ + public function __get($name) + { + if (substr($name, 0, 5) === 'total') { + return $this->total(substr($name, 5)); + } + + switch ($name) { + case 'years': + return $this->y; + + case 'months': + return $this->m; + + case 'dayz': + return $this->d; + + case 'hours': + return $this->h; + + case 'minutes': + return $this->i; + + case 'seconds': + return $this->s; + + case 'weeks': + return (int) floor($this->d / static::getDaysPerWeek()); + + case 'daysExcludeWeeks': + case 'dayzExcludeWeeks': + return $this->d % static::getDaysPerWeek(); + + default: + throw new InvalidArgumentException(sprintf("Unknown getter '%s'", $name)); + } + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string $name + * @param int $val + * + * @throws \InvalidArgumentException + */ + public function __set($name, $val) + { + switch ($name) { + case 'years': + $this->y = $val; + break; + + case 'months': + $this->m = $val; + break; + + case 'weeks': + $this->d = $val * static::getDaysPerWeek(); + break; + + case 'dayz': + $this->d = $val; + break; + + case 'hours': + $this->h = $val; + break; + + case 'minutes': + $this->i = $val; + break; + + case 'seconds': + $this->s = $val; + break; + } + } + + /** + * Allow setting of weeks and days to be cumulative. + * + * @param int $weeks Number of weeks to set + * @param int $days Number of days to set + * + * @return static + */ + public function weeksAndDays($weeks, $days) + { + $this->dayz = ($weeks * static::getDaysPerWeek()) + $days; + + return $this; + } + + /** + * Register a custom macro. + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Remove all macros. + */ + public static function resetMacros() + { + static::$macros = array(); + } + + /** + * Register macros from a mixin object. + * + * @param object $mixin + * + * @throws \ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + $reflection = new ReflectionClass($mixin); + + $methods = $reflection->getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + $reflection = new ReflectionFunction($macro); + + $reflectionParameters = $reflection->getParameters(); + + $expectedCount = count($reflectionParameters); + $actualCount = count($parameters); + + if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') { + for ($i = $actualCount; $i < $expectedCount - 1; $i++) { + $parameters[] = $reflectionParameters[$i]->getDefaultValue(); + } + + $parameters[] = $this; + } + + if ($macro instanceof Closure && method_exists($macro, 'bindTo')) { + $macro = $macro->bindTo($this, get_class($this)); + } + + return call_user_func_array($macro, $parameters); + } + + /** + * Allow fluent calls on the setters... CarbonInterval::years(3)->months(5)->day(). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $name + * @param array $args + * + * @return static + */ + public function __call($name, $args) + { + if (static::hasMacro($name)) { + return $this->callMacro($name, $args); + } + + $arg = count($args) === 0 ? 1 : $args[0]; + + switch ($name) { + case 'years': + case 'year': + $this->years = $arg; + break; + + case 'months': + case 'month': + $this->months = $arg; + break; + + case 'weeks': + case 'week': + $this->dayz = $arg * static::getDaysPerWeek(); + break; + + case 'days': + case 'dayz': + case 'day': + $this->dayz = $arg; + break; + + case 'hours': + case 'hour': + $this->hours = $arg; + break; + + case 'minutes': + case 'minute': + $this->minutes = $arg; + break; + + case 'seconds': + case 'second': + $this->seconds = $arg; + break; + } + + return $this; + } + + /** + * Get the current interval in a human readable format in the current locale. + * + * @param bool $short (false by default), returns short units if true + * + * @return string + */ + public function forHumans($short = false) + { + $periods = array( + 'year' => array('y', $this->years), + 'month' => array('m', $this->months), + 'week' => array('w', $this->weeks), + 'day' => array('d', $this->daysExcludeWeeks), + 'hour' => array('h', $this->hours), + 'minute' => array('min', $this->minutes), + 'second' => array('s', $this->seconds), + ); + + $parts = array(); + foreach ($periods as $unit => $options) { + list($shortUnit, $count) = $options; + if ($count > 0) { + $parts[] = static::translator()->transChoice($short ? $shortUnit : $unit, $count, array(':count' => $count)); + } + } + + return implode(' ', $parts); + } + + /** + * Format the instance as a string using the forHumans() function. + * + * @return string + */ + public function __toString() + { + return $this->forHumans(); + } + + /** + * Convert the interval to a CarbonPeriod. + * + * @return CarbonPeriod + */ + public function toPeriod() + { + return CarbonPeriod::createFromArray( + array_merge(array($this), func_get_args()) + ); + } + + /** + * Invert the interval. + * + * @return $this + */ + public function invert() + { + $this->invert = $this->invert ? 0 : 1; + + return $this; + } + + /** + * Add the passed interval to the current instance. + * + * @param DateInterval $interval + * + * @return static + */ + public function add(DateInterval $interval) + { + $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1; + + if (static::wasCreatedFromDiff($interval)) { + $this->dayz += $interval->days * $sign; + } else { + $this->years += $interval->y * $sign; + $this->months += $interval->m * $sign; + $this->dayz += $interval->d * $sign; + $this->hours += $interval->h * $sign; + $this->minutes += $interval->i * $sign; + $this->seconds += $interval->s * $sign; + } + + if (($this->years || $this->months || $this->dayz || $this->hours || $this->minutes || $this->seconds) && + $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0 + ) { + $this->years *= -1; + $this->months *= -1; + $this->dayz *= -1; + $this->hours *= -1; + $this->minutes *= -1; + $this->seconds *= -1; + $this->invert(); + } + + return $this; + } + + /** + * Multiply current instance given number of times + * + * @param float $factor + * + * @return $this + */ + public function times($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + + $this->years = (int) round($this->years * $factor); + $this->months = (int) round($this->months * $factor); + $this->dayz = (int) round($this->dayz * $factor); + $this->hours = (int) round($this->hours * $factor); + $this->minutes = (int) round($this->minutes * $factor); + $this->seconds = (int) round($this->seconds * $factor); + + return $this; + } + + /** + * Get the interval_spec string of a date interval. + * + * @param DateInterval $interval + * + * @return string + */ + public static function getDateIntervalSpec(DateInterval $interval) + { + $date = array_filter(array( + static::PERIOD_YEARS => abs($interval->y), + static::PERIOD_MONTHS => abs($interval->m), + static::PERIOD_DAYS => abs($interval->d), + )); + + $time = array_filter(array( + static::PERIOD_HOURS => abs($interval->h), + static::PERIOD_MINUTES => abs($interval->i), + static::PERIOD_SECONDS => abs($interval->s), + )); + + $specString = static::PERIOD_PREFIX; + + foreach ($date as $key => $value) { + $specString .= $value.$key; + } + + if (count($time) > 0) { + $specString .= static::PERIOD_TIME_PREFIX; + foreach ($time as $key => $value) { + $specString .= $value.$key; + } + } + + return $specString === static::PERIOD_PREFIX ? 'PT0S' : $specString; + } + + /** + * Get the interval_spec string. + * + * @return string + */ + public function spec() + { + return static::getDateIntervalSpec($this); + } + + /** + * Comparing 2 date intervals. + * + * @param DateInterval $a + * @param DateInterval $b + * + * @return int + */ + public static function compareDateIntervals(DateInterval $a, DateInterval $b) + { + $current = Carbon::now(); + $passed = $current->copy()->add($b); + $current->add($a); + + if ($current < $passed) { + return -1; + } + if ($current > $passed) { + return 1; + } + + return 0; + } + + /** + * Comparing with passed interval. + * + * @param DateInterval $interval + * + * @return int + */ + public function compare(DateInterval $interval) + { + return static::compareDateIntervals($this, $interval); + } + + /** + * Convert overflowed values into bigger units. + * + * @return $this + */ + public function cascade() + { + foreach (static::getFlipCascadeFactors() as $source => $cascade) { + list($target, $factor) = $cascade; + + if ($source === 'dayz' && $target === 'weeks') { + continue; + } + + $value = $this->$source; + $this->$source = $modulo = $value % $factor; + $this->$target += ($value - $modulo) / $factor; + } + + return $this; + } + + /** + * Get amount of given unit equivalent to the interval. + * + * @param string $unit + * + * @throws \InvalidArgumentException + * + * @return float + */ + public function total($unit) + { + $realUnit = $unit = strtolower($unit); + + if (in_array($unit, array('days', 'weeks'))) { + $realUnit = 'dayz'; + } elseif (!in_array($unit, array('seconds', 'minutes', 'hours', 'dayz', 'months', 'years'))) { + throw new InvalidArgumentException("Unknown unit '$unit'."); + } + + $result = 0; + $cumulativeFactor = 0; + $unitFound = false; + + foreach (static::getFlipCascadeFactors() as $source => $cascade) { + list($target, $factor) = $cascade; + + if ($source === $realUnit) { + $unitFound = true; + $result += $this->$source; + $cumulativeFactor = 1; + } + + if ($factor === false) { + if ($unitFound) { + break; + } + + $result = 0; + $cumulativeFactor = 0; + + continue; + } + + if ($target === $realUnit) { + $unitFound = true; + } + + if ($cumulativeFactor) { + $cumulativeFactor *= $factor; + $result += $this->$target * $cumulativeFactor; + + continue; + } + + $result = ($result + $this->$source) / $factor; + } + + if (isset($target) && !$cumulativeFactor) { + $result += $this->$target; + } + + if (!$unitFound) { + throw new \InvalidArgumentException("Unit $unit have no configuration to get total from other units."); + } + + if ($unit === 'weeks') { + return $result / static::getDaysPerWeek(); + } + + return $result; + } +} diff --git a/core/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php b/core/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php new file mode 100644 index 0000000..cb4e0f8 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php @@ -0,0 +1,1453 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use BadMethodCallException; +use Closure; +use Countable; +use DateInterval; +use DateTime; +use DateTimeInterface; +use InvalidArgumentException; +use Iterator; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use RuntimeException; + +/** + * Substitution of DatePeriod with some modifications and many more features. + * Fully compatible with PHP 5.3+! + * + * @method static CarbonPeriod start($date, $inclusive = null) Create instance specifying start date. + * @method static CarbonPeriod since($date, $inclusive = null) Alias for start(). + * @method static CarbonPeriod sinceNow($inclusive = null) Create instance with start date set to now. + * @method static CarbonPeriod end($date = null, $inclusive = null) Create instance specifying end date. + * @method static CarbonPeriod until($date = null, $inclusive = null) Alias for end(). + * @method static CarbonPeriod untilNow($inclusive = null) Create instance with end date set to now. + * @method static CarbonPeriod dates($start, $end = null) Create instance with start and end date. + * @method static CarbonPeriod between($start, $end = null) Create instance with start and end date. + * @method static CarbonPeriod recurrences($recurrences = null) Create instance with maximum number of recurrences. + * @method static CarbonPeriod times($recurrences = null) Alias for recurrences(). + * @method static CarbonPeriod options($options = null) Create instance with options. + * @method static CarbonPeriod toggle($options, $state = null) Create instance with options toggled on or off. + * @method static CarbonPeriod filter($callback, $name = null) Create instance with filter added to the stack. + * @method static CarbonPeriod push($callback, $name = null) Alias for filter(). + * @method static CarbonPeriod prepend($callback, $name = null) Create instance with filter prepened to the stack. + * @method static CarbonPeriod filters(array $filters) Create instance with filters stack. + * @method static CarbonPeriod interval($interval) Create instance with given date interval. + * @method static CarbonPeriod each($interval) Create instance with given date interval. + * @method static CarbonPeriod every($interval) Create instance with given date interval. + * @method static CarbonPeriod step($interval) Create instance with given date interval. + * @method static CarbonPeriod stepBy($interval) Create instance with given date interval. + * @method static CarbonPeriod invert() Create instance with inverted date interval. + * @method static CarbonPeriod years($years = 1) Create instance specifying a number of years for date interval. + * @method static CarbonPeriod year($years = 1) Alias for years(). + * @method static CarbonPeriod months($months = 1) Create instance specifying a number of months for date interval. + * @method static CarbonPeriod month($months = 1) Alias for months(). + * @method static CarbonPeriod weeks($weeks = 1) Create instance specifying a number of weeks for date interval. + * @method static CarbonPeriod week($weeks = 1) Alias for weeks(). + * @method static CarbonPeriod days($days = 1) Create instance specifying a number of days for date interval. + * @method static CarbonPeriod dayz($days = 1) Alias for days(). + * @method static CarbonPeriod day($days = 1) Alias for days(). + * @method static CarbonPeriod hours($hours = 1) Create instance specifying a number of hours for date interval. + * @method static CarbonPeriod hour($hours = 1) Alias for hours(). + * @method static CarbonPeriod minutes($minutes = 1) Create instance specifying a number of minutes for date interval. + * @method static CarbonPeriod minute($minutes = 1) Alias for minutes(). + * @method static CarbonPeriod seconds($seconds = 1) Create instance specifying a number of seconds for date interval. + * @method static CarbonPeriod second($seconds = 1) Alias for seconds(). + * @method CarbonPeriod start($date, $inclusive = null) Change the period start date. + * @method CarbonPeriod since($date, $inclusive = null) Alias for start(). + * @method CarbonPeriod sinceNow($inclusive = null) Change the period start date to now. + * @method CarbonPeriod end($date = null, $inclusive = null) Change the period end date. + * @method CarbonPeriod until($date = null, $inclusive = null) Alias for end(). + * @method CarbonPeriod untilNow($inclusive = null) Change the period end date to now. + * @method CarbonPeriod dates($start, $end = null) Change the period start and end date. + * @method CarbonPeriod recurrences($recurrences = null) Change the maximum number of recurrences. + * @method CarbonPeriod times($recurrences = null) Alias for recurrences(). + * @method CarbonPeriod options($options = null) Change the period options. + * @method CarbonPeriod toggle($options, $state = null) Toggle given options on or off. + * @method CarbonPeriod filter($callback, $name = null) Add a filter to the stack. + * @method CarbonPeriod push($callback, $name = null) Alias for filter(). + * @method CarbonPeriod prepend($callback, $name = null) Prepend a filter to the stack. + * @method CarbonPeriod filters(array $filters = array()) Set filters stack. + * @method CarbonPeriod interval($interval) Change the period date interval. + * @method CarbonPeriod invert() Invert the period date interval. + * @method CarbonPeriod years($years = 1) Set the years portion of the date interval. + * @method CarbonPeriod year($years = 1) Alias for years(). + * @method CarbonPeriod months($months = 1) Set the months portion of the date interval. + * @method CarbonPeriod month($months = 1) Alias for months(). + * @method CarbonPeriod weeks($weeks = 1) Set the weeks portion of the date interval. + * @method CarbonPeriod week($weeks = 1) Alias for weeks(). + * @method CarbonPeriod days($days = 1) Set the days portion of the date interval. + * @method CarbonPeriod dayz($days = 1) Alias for days(). + * @method CarbonPeriod day($days = 1) Alias for days(). + * @method CarbonPeriod hours($hours = 1) Set the hours portion of the date interval. + * @method CarbonPeriod hour($hours = 1) Alias for hours(). + * @method CarbonPeriod minutes($minutes = 1) Set the minutes portion of the date interval. + * @method CarbonPeriod minute($minutes = 1) Alias for minutes(). + * @method CarbonPeriod seconds($seconds = 1) Set the seconds portion of the date interval. + * @method CarbonPeriod second($seconds = 1) Alias for seconds(). + */ +class CarbonPeriod implements Iterator, Countable +{ + /** + * Built-in filters. + * + * @var string + */ + const RECURRENCES_FILTER = 'Carbon\CarbonPeriod::filterRecurrences'; + const END_DATE_FILTER = 'Carbon\CarbonPeriod::filterEndDate'; + + /** + * Special value which can be returned by filters to end iteration. Also a filter. + * + * @var string + */ + const END_ITERATION = 'Carbon\CarbonPeriod::endIteration'; + + /** + * Available options. + * + * @var int + */ + const EXCLUDE_START_DATE = 1; + const EXCLUDE_END_DATE = 2; + + /** + * Number of maximum attempts before giving up on finding next valid date. + * + * @var int + */ + const NEXT_MAX_ATTEMPTS = 1000; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = array(); + + /** + * Underlying date interval instance. Always present, one day by default. + * + * @var CarbonInterval + */ + protected $dateInterval; + + /** + * Whether current date interval was set by default. + * + * @var bool + */ + protected $isDefaultInterval; + + /** + * The filters stack. + * + * @var array + */ + protected $filters = array(); + + /** + * Period start date. Applied on rewind. Always present, now by default. + * + * @var Carbon + */ + protected $startDate; + + /** + * Period end date. For inverted interval should be before the start date. Applied via a filter. + * + * @var Carbon|null + */ + protected $endDate; + + /** + * Limit for number of recurrences. Applied via a filter. + * + * @var int|null + */ + protected $recurrences; + + /** + * Iteration options. + * + * @var int + */ + protected $options; + + /** + * Index of current date. Always sequential, even if some dates are skipped by filters. + * Equal to null only before the first iteration. + * + * @var int + */ + protected $key; + + /** + * Current date. May temporarily hold unaccepted value when looking for a next valid date. + * Equal to null only before the first iteration. + * + * @var Carbon + */ + protected $current; + + /** + * Timezone of current date. Taken from the start date. + * + * @var \DateTimeZone|null + */ + protected $timezone; + + /** + * The cached validation result for current date. + * + * @var bool|string|null + */ + protected $validationResult; + + /** + * Create a new instance. + * + * @return static + */ + public static function create() + { + return static::createFromArray(func_get_args()); + } + + /** + * Create a new instance from an array of parameters. + * + * @param array $params + * + * @return static + */ + public static function createFromArray(array $params) + { + // PHP 5.3 equivalent of new static(...$params). + $reflection = new ReflectionClass(get_class()); + /** @var static $instance */ + $instance = $reflection->newInstanceArgs($params); + + return $instance; + } + + /** + * Create CarbonPeriod from ISO 8601 string. + * + * @param string $iso + * @param int|null $options + * + * @return static + */ + public static function createFromIso($iso, $options = null) + { + $params = static::parseIso8601($iso); + + $instance = static::createFromArray($params); + + if ($options !== null) { + $instance->setOptions($options); + } + + return $instance; + } + + /** + * Return whether given interval contains non zero value of any time unit. + * + * @param \DateInterval $interval + * + * @return bool + */ + protected static function intervalHasTime(DateInterval $interval) + { + // The array_key_exists and get_object_vars are used as a workaround to check microsecond support. + // Both isset and property_exists will fail on PHP 7.0.14 - 7.0.21 due to the following bug: + // https://bugs.php.net/bug.php?id=74852 + return $interval->h || $interval->i || $interval->s || array_key_exists('f', get_object_vars($interval)) && $interval->f; + } + + /** + * Return whether given callable is a string pointing to one of Carbon's is* methods + * and should be automatically converted to a filter callback. + * + * @param callable $callable + * + * @return bool + */ + protected static function isCarbonPredicateMethod($callable) + { + return is_string($callable) && substr($callable, 0, 2) === 'is' && (method_exists('Carbon\Carbon', $callable) || Carbon::hasMacro($callable)); + } + + /** + * Return whether given variable is an ISO 8601 specification. + * + * Note: Check is very basic, as actual validation will be done later when parsing. + * We just want to ensure that variable is not any other type of a valid parameter. + * + * @param mixed $var + * + * @return bool + */ + protected static function isIso8601($var) + { + if (!is_string($var)) { + return false; + } + + // Match slash but not within a timezone name. + $part = '[a-z]+(?:[_-][a-z]+)*'; + + preg_match("#\b$part/$part\b|(/)#i", $var, $match); + + return isset($match[1]); + } + + /** + * Parse given ISO 8601 string into an array of arguments. + * + * @param string $iso + * + * @return array + */ + protected static function parseIso8601($iso) + { + $result = array(); + + $interval = null; + $start = null; + $end = null; + + foreach (explode('/', $iso) as $key => $part) { + if ($key === 0 && preg_match('/^R([0-9]*)$/', $part, $match)) { + $parsed = strlen($match[1]) ? (int) $match[1] : null; + } elseif ($interval === null && $parsed = CarbonInterval::make($part)) { + $interval = $part; + } elseif ($start === null && $parsed = Carbon::make($part)) { + $start = $part; + } elseif ($end === null && $parsed = Carbon::make(static::addMissingParts($start, $part))) { + $end = $part; + } else { + throw new InvalidArgumentException("Invalid ISO 8601 specification: $iso."); + } + + $result[] = $parsed; + } + + return $result; + } + + /** + * Add missing parts of the target date from the soure date. + * + * @param string $source + * @param string $target + * + * @return string + */ + protected static function addMissingParts($source, $target) + { + $pattern = '/'.preg_replace('/[0-9]+/', '[0-9]+', preg_quote($target, '/')).'$/'; + + $result = preg_replace($pattern, $target, $source, 1, $count); + + return $count ? $result : $target; + } + + /** + * Register a custom macro. + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Remove all macros. + */ + public static function resetMacros() + { + static::$macros = array(); + } + + /** + * Register macros from a mixin object. + * + * @param object $mixin + * + * @throws \ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + $reflection = new ReflectionClass($mixin); + + $methods = $reflection->getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Provide static proxy for instance aliases. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return call_user_func_array( + array(new static, $method), $parameters + ); + } + + /** + * CarbonPeriod constructor. + * + * @throws InvalidArgumentException + */ + public function __construct() + { + // Parse and assign arguments one by one. First argument may be an ISO 8601 spec, + // which will be first parsed into parts and then processed the same way. + $arguments = func_get_args(); + + if (count($arguments) && static::isIso8601($iso = $arguments[0])) { + array_splice($arguments, 0, 1, static::parseIso8601($iso)); + } + + foreach ($arguments as $argument) { + if ($this->dateInterval === null && $parsed = CarbonInterval::make($argument)) { + $this->setDateInterval($parsed); + } elseif ($this->startDate === null && $parsed = Carbon::make($argument)) { + $this->setStartDate($parsed); + } elseif ($this->endDate === null && $parsed = Carbon::make($argument)) { + $this->setEndDate($parsed); + } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) { + $this->setRecurrences($argument); + } elseif ($this->options === null && (is_int($argument) || $argument === null)) { + $this->setOptions($argument); + } else { + throw new InvalidArgumentException('Invalid constructor parameters.'); + } + } + + if ($this->startDate === null) { + $this->setStartDate(Carbon::now()); + } + + if ($this->dateInterval === null) { + $this->setDateInterval(CarbonInterval::day()); + + $this->isDefaultInterval = true; + } + + if ($this->options === null) { + $this->setOptions(0); + } + } + + /** + * Change the period date interval. + * + * @param DateInterval|string $interval + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setDateInterval($interval) + { + if (!$interval = CarbonInterval::make($interval)) { + throw new InvalidArgumentException('Invalid interval.'); + } + + if ($interval->spec() === 'PT0S') { + throw new InvalidArgumentException('Empty interval is not accepted.'); + } + + $this->dateInterval = $interval; + + $this->isDefaultInterval = false; + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Invert the period date interval. + * + * @return $this + */ + public function invertDateInterval() + { + $interval = $this->dateInterval->invert(); + + return $this->setDateInterval($interval); + } + + /** + * Set start and end date. + * + * @param DateTime|DateTimeInterface|string $start + * @param DateTime|DateTimeInterface|string|null $end + * + * @return $this + */ + public function setDates($start, $end) + { + $this->setStartDate($start); + $this->setEndDate($end); + + return $this; + } + + /** + * Change the period options. + * + * @param int|null $options + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setOptions($options) + { + if (!is_int($options) && !is_null($options)) { + throw new InvalidArgumentException('Invalid options.'); + } + + $this->options = $options ?: 0; + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Get the period options. + * + * @return int + */ + public function getOptions() + { + return $this->options; + } + + /** + * Toggle given options on or off. + * + * @param int $options + * @param bool|null $state + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function toggleOptions($options, $state = null) + { + if ($state === null) { + $state = ($this->options & $options) !== $options; + } + + return $this->setOptions($state ? + $this->options | $options : + $this->options & ~$options + ); + } + + /** + * Toggle EXCLUDE_START_DATE option. + * + * @param bool $state + * + * @return $this + */ + public function excludeStartDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_START_DATE, $state); + } + + /** + * Toggle EXCLUDE_END_DATE option. + * + * @param bool $state + * + * @return $this + */ + public function excludeEndDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_END_DATE, $state); + } + + /** + * Get the underlying date interval. + * + * @return CarbonInterval + */ + public function getDateInterval() + { + return $this->dateInterval->copy(); + } + + /** + * Get start date of the period. + * + * @return Carbon + */ + public function getStartDate() + { + return $this->startDate->copy(); + } + + /** + * Get end date of the period. + * + * @return Carbon|null + */ + public function getEndDate() + { + if ($this->endDate) { + return $this->endDate->copy(); + } + } + + /** + * Get number of recurrences. + * + * @return int|null + */ + public function getRecurrences() + { + return $this->recurrences; + } + + /** + * Returns true if the start date should be excluded. + * + * @return bool + */ + public function isStartExcluded() + { + return ($this->options & static::EXCLUDE_START_DATE) !== 0; + } + + /** + * Returns true if the end date should be excluded. + * + * @return bool + */ + public function isEndExcluded() + { + return ($this->options & static::EXCLUDE_END_DATE) !== 0; + } + + /** + * Add a filter to the stack. + * + * @param callable $callback + * @param string $name + * + * @return $this + */ + public function addFilter($callback, $name = null) + { + $tuple = $this->createFilterTuple(func_get_args()); + + $this->filters[] = $tuple; + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Prepend a filter to the stack. + * + * @param callable $callback + * @param string $name + * + * @return $this + */ + public function prependFilter($callback, $name = null) + { + $tuple = $this->createFilterTuple(func_get_args()); + + array_unshift($this->filters, $tuple); + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Create a filter tuple from raw parameters. + * + * Will create an automatic filter callback for one of Carbon's is* methods. + * + * @param array $parameters + * + * @return array + */ + protected function createFilterTuple(array $parameters) + { + $method = array_shift($parameters); + + if (!$this->isCarbonPredicateMethod($method)) { + return array($method, array_shift($parameters)); + } + + return array(function ($date) use ($method, $parameters) { + return call_user_func_array(array($date, $method), $parameters); + }, $method); + } + + /** + * Remove a filter by instance or name. + * + * @param callable|string $filter + * + * @return $this + */ + public function removeFilter($filter) + { + $key = is_callable($filter) ? 0 : 1; + + $this->filters = array_values(array_filter( + $this->filters, + function ($tuple) use ($key, $filter) { + return $tuple[$key] !== $filter; + } + )); + + $this->updateInternalState(); + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Return whether given instance or name is in the filter stack. + * + * @param callable|string $filter + * + * @return bool + */ + public function hasFilter($filter) + { + $key = is_callable($filter) ? 0 : 1; + + foreach ($this->filters as $tuple) { + if ($tuple[$key] === $filter) { + return true; + } + } + + return false; + } + + /** + * Get filters stack. + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Set filters stack. + * + * @param array $filters + * + * @return $this + */ + public function setFilters(array $filters) + { + $this->filters = $filters; + + $this->updateInternalState(); + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Reset filters stack. + * + * @return $this + */ + public function resetFilters() + { + $this->filters = array(); + + if ($this->endDate !== null) { + $this->filters[] = array(static::END_DATE_FILTER, null); + } + + if ($this->recurrences !== null) { + $this->filters[] = array(static::RECURRENCES_FILTER, null); + } + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Update properties after removing built-in filters. + * + * @return void + */ + protected function updateInternalState() + { + if (!$this->hasFilter(static::END_DATE_FILTER)) { + $this->endDate = null; + } + + if (!$this->hasFilter(static::RECURRENCES_FILTER)) { + $this->recurrences = null; + } + } + + /** + * Add a recurrences filter (set maximum number of recurrences). + * + * @param int|null $recurrences + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setRecurrences($recurrences) + { + if (!is_numeric($recurrences) && !is_null($recurrences) || $recurrences < 0) { + throw new InvalidArgumentException('Invalid number of recurrences.'); + } + + if ($recurrences === null) { + return $this->removeFilter(static::RECURRENCES_FILTER); + } + + $this->recurrences = (int) $recurrences; + + if (!$this->hasFilter(static::RECURRENCES_FILTER)) { + return $this->addFilter(static::RECURRENCES_FILTER); + } + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Recurrences filter callback (limits number of recurrences). + * + * @param \Carbon\Carbon $current + * @param int $key + * + * @return bool|string + */ + protected function filterRecurrences($current, $key) + { + if ($key < $this->recurrences) { + return true; + } + + return static::END_ITERATION; + } + + /** + * Change the period start date. + * + * @param DateTime|DateTimeInterface|string $date + * @param bool|null $inclusive + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setStartDate($date, $inclusive = null) + { + if (!$date = Carbon::make($date)) { + throw new InvalidArgumentException('Invalid start date.'); + } + + $this->startDate = $date; + + if ($inclusive !== null) { + $this->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive); + } + + return $this; + } + + /** + * Change the period end date. + * + * @param DateTime|DateTimeInterface|string|null $date + * @param bool|null $inclusive + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setEndDate($date, $inclusive = null) + { + if (!is_null($date) && !$date = Carbon::make($date)) { + throw new InvalidArgumentException('Invalid end date.'); + } + + if (!$date) { + return $this->removeFilter(static::END_DATE_FILTER); + } + + $this->endDate = $date; + + if ($inclusive !== null) { + $this->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive); + } + + if (!$this->hasFilter(static::END_DATE_FILTER)) { + return $this->addFilter(static::END_DATE_FILTER); + } + + $this->handleChangedParameters(); + + return $this; + } + + /** + * End date filter callback. + * + * @param \Carbon\Carbon $current + * + * @return bool|string + */ + protected function filterEndDate($current) + { + if (!$this->isEndExcluded() && $current == $this->endDate) { + return true; + } + + if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) { + return true; + } + + return static::END_ITERATION; + } + + /** + * End iteration filter callback. + * + * @return string + */ + protected function endIteration() + { + return static::END_ITERATION; + } + + /** + * Handle change of the parameters. + */ + protected function handleChangedParameters() + { + $this->validationResult = null; + } + + /** + * Validate current date and stop iteration when necessary. + * + * Returns true when current date is valid, false if it is not, or static::END_ITERATION + * when iteration should be stopped. + * + * @return bool|static::END_ITERATION + */ + protected function validateCurrentDate() + { + if ($this->current === null) { + $this->rewind(); + } + + // Check after the first rewind to avoid repeating the initial validation. + if ($this->validationResult !== null) { + return $this->validationResult; + } + + return $this->validationResult = $this->checkFilters(); + } + + /** + * Check whether current value and key pass all the filters. + * + * @return bool|string + */ + protected function checkFilters() + { + $current = $this->prepareForReturn($this->current); + + foreach ($this->filters as $tuple) { + $result = call_user_func( + $tuple[0], $current->copy(), $this->key, $this + ); + + if ($result === static::END_ITERATION) { + return static::END_ITERATION; + } + + if (!$result) { + return false; + } + } + + return true; + } + + /** + * Prepare given date to be returned to the external logic. + * + * @param Carbon $date + * + * @return Carbon + */ + protected function prepareForReturn(Carbon $date) + { + $date = $date->copy(); + + if ($this->timezone) { + $date->setTimezone($this->timezone); + } + + return $date; + } + + /** + * Check if the current position is valid. + * + * @return bool + */ + public function valid() + { + return $this->validateCurrentDate() === true; + } + + /** + * Return the current key. + * + * @return int|null + */ + public function key() + { + if ($this->valid()) { + return $this->key; + } + } + + /** + * Return the current date. + * + * @return Carbon|null + */ + public function current() + { + if ($this->valid()) { + return $this->prepareForReturn($this->current); + } + } + + /** + * Move forward to the next date. + * + * @throws \RuntimeException + * + * @return void + */ + public function next() + { + if ($this->current === null) { + $this->rewind(); + } + + if ($this->validationResult !== static::END_ITERATION) { + $this->key++; + + $this->incrementCurrentDateUntilValid(); + } + } + + /** + * Rewind to the start date. + * + * Iterating over a date in the UTC timezone avoids bug during backward DST change. + * + * @see https://bugs.php.net/bug.php?id=72255 + * @see https://bugs.php.net/bug.php?id=74274 + * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time + * + * @throws \RuntimeException + * + * @return void + */ + public function rewind() + { + $this->key = 0; + $this->current = $this->startDate->copy(); + $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null; + + if ($this->timezone) { + $this->current->setTimezone('UTC'); + } + + $this->validationResult = null; + + if ($this->isStartExcluded() || $this->validateCurrentDate() === false) { + $this->incrementCurrentDateUntilValid(); + } + } + + /** + * Skip iterations and returns iteration state (false if ended, true if still valid). + * + * @param int $count steps number to skip (1 by default) + * + * @return bool + */ + public function skip($count = 1) + { + for ($i = $count; $this->valid() && $i > 0; $i--) { + $this->next(); + } + + return $this->valid(); + } + + /** + * Keep incrementing the current date until a valid date is found or the iteration is ended. + * + * @throws \RuntimeException + * + * @return void + */ + protected function incrementCurrentDateUntilValid() + { + $attempts = 0; + + do { + $this->current->add($this->dateInterval); + + $this->validationResult = null; + + if (++$attempts > static::NEXT_MAX_ATTEMPTS) { + throw new RuntimeException('Could not find next valid date.'); + } + } while ($this->validateCurrentDate() === false); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function toIso8601String() + { + $parts = array(); + + if ($this->recurrences !== null) { + $parts[] = 'R'.$this->recurrences; + } + + $parts[] = $this->startDate->toIso8601String(); + + $parts[] = $this->dateInterval->spec(); + + if ($this->endDate !== null) { + $parts[] = $this->endDate->toIso8601String(); + } + + return implode('/', $parts); + } + + /** + * Convert the date period into a string. + * + * @return string + */ + public function toString() + { + $translator = Carbon::getTranslator(); + + $parts = array(); + + $format = !$this->startDate->isStartOfDay() || $this->endDate && !$this->endDate->isStartOfDay() + ? 'Y-m-d H:i:s' + : 'Y-m-d'; + + if ($this->recurrences !== null) { + $parts[] = $translator->transChoice('period_recurrences', $this->recurrences, array(':count' => $this->recurrences)); + } + + $parts[] = $translator->trans('period_interval', array(':interval' => $this->dateInterval->forHumans())); + + $parts[] = $translator->trans('period_start_date', array(':date' => $this->startDate->format($format))); + + if ($this->endDate !== null) { + $parts[] = $translator->trans('period_end_date', array(':date' => $this->endDate->format($format))); + } + + $result = implode(' ', $parts); + + return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function spec() + { + return $this->toIso8601String(); + } + + /** + * Convert the date period into an array without changing current iteration state. + * + * @return array + */ + public function toArray() + { + $state = array( + $this->key, + $this->current ? $this->current->copy() : null, + $this->validationResult, + ); + + $result = iterator_to_array($this); + + list( + $this->key, + $this->current, + $this->validationResult + ) = $state; + + return $result; + } + + /** + * Count dates in the date period. + * + * @return int + */ + public function count() + { + return count($this->toArray()); + } + + /** + * Return the first date in the date period. + * + * @return Carbon|null + */ + public function first() + { + if ($array = $this->toArray()) { + return $array[0]; + } + } + + /** + * Return the last date in the date period. + * + * @return Carbon|null + */ + public function last() + { + if ($array = $this->toArray()) { + return $array[count($array) - 1]; + } + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + $reflection = new ReflectionFunction($macro); + + $reflectionParameters = $reflection->getParameters(); + + $expectedCount = count($reflectionParameters); + $actualCount = count($parameters); + + if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') { + for ($i = $actualCount; $i < $expectedCount - 1; $i++) { + $parameters[] = $reflectionParameters[$i]->getDefaultValue(); + } + + $parameters[] = $this; + } + + if ($macro instanceof Closure && method_exists($macro, 'bindTo')) { + $macro = $macro->bindTo($this, get_class($this)); + } + + return call_user_func_array($macro, $parameters); + } + + /** + * Convert the date period into a string. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Add aliases for setters. + * + * CarbonPeriod::days(3)->hours(5)->invert() + * ->sinceNow()->until('2010-01-10') + * ->filter(...) + * ->count() + * + * Note: We use magic method to let static and instance aliases with the same names. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->callMacro($method, $parameters); + } + + $first = count($parameters) >= 1 ? $parameters[0] : null; + $second = count($parameters) >= 2 ? $parameters[1] : null; + + switch ($method) { + case 'start': + case 'since': + return $this->setStartDate($first, $second); + + case 'sinceNow': + return $this->setStartDate(new Carbon, $first); + + case 'end': + case 'until': + return $this->setEndDate($first, $second); + + case 'untilNow': + return $this->setEndDate(new Carbon, $first); + + case 'dates': + case 'between': + return $this->setDates($first, $second); + + case 'recurrences': + case 'times': + return $this->setRecurrences($first); + + case 'options': + return $this->setOptions($first); + + case 'toggle': + return $this->toggleOptions($first, $second); + + case 'filter': + case 'push': + return $this->addFilter($first, $second); + + case 'prepend': + return $this->prependFilter($first, $second); + + case 'filters': + return $this->setFilters($first ?: array()); + + case 'interval': + case 'each': + case 'every': + case 'step': + case 'stepBy': + return $this->setDateInterval($first); + + case 'invert': + return $this->invertDateInterval(); + + case 'years': + case 'year': + case 'months': + case 'month': + case 'weeks': + case 'week': + case 'days': + case 'dayz': + case 'day': + case 'hours': + case 'hour': + case 'minutes': + case 'minute': + case 'seconds': + case 'second': + return $this->setDateInterval(call_user_func( + // Override default P1D when instantiating via fluent setters. + array($this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method), + count($parameters) === 0 ? 1 : $first + )); + } + + throw new BadMethodCallException("Method $method does not exist."); + } +} diff --git a/core/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php b/core/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php new file mode 100644 index 0000000..1b0d473 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException; + +class InvalidDateException extends InvalidArgumentException +{ + /** + * The invalid field. + * + * @var string + */ + private $field; + + /** + * The invalid value. + * + * @var mixed + */ + private $value; + + /** + * Constructor. + * + * @param string $field + * @param mixed $value + * @param int $code + * @param \Exception|null $previous + */ + public function __construct($field, $value, $code = 0, Exception $previous = null) + { + $this->field = $field; + $this->value = $value; + parent::__construct($field.' : '.$value.' is not a valid value.', $code, $previous); + } + + /** + * Get the invalid field. + * + * @return string + */ + public function getField() + { + return $this->field; + } + + /** + * Get the invalid value. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/af.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/af.php new file mode 100644 index 0000000..5cf6a8d --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/af.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count jaar|:count jare', + 'y' => ':count jaar|:count jare', + 'month' => ':count maand|:count maande', + 'm' => ':count maand|:count maande', + 'week' => ':count week|:count weke', + 'w' => ':count week|:count weke', + 'day' => ':count dag|:count dae', + 'd' => ':count dag|:count dae', + 'hour' => ':count uur|:count ure', + 'h' => ':count uur|:count ure', + 'minute' => ':count minuut|:count minute', + 'min' => ':count minuut|:count minute', + 'second' => ':count sekond|:count sekondes', + 's' => ':count sekond|:count sekondes', + 'ago' => ':time terug', + 'from_now' => ':time van nou af', + 'after' => ':time na', + 'before' => ':time voor', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ar.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ar.php new file mode 100644 index 0000000..de8f6b8 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ar.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '{0}سنة|{1}سنة|{2}سنتين|[3,10]:count سنوات|[11,Inf]:count سنة', + 'y' => '{0}سنة|{1}سنة|{2}سنتين|[3,10]:count سنوات|[11,Inf]:count سنة', + 'month' => '{0}شهر|{1} شهر|{2}شهرين|[3,10]:count أشهر|[11,Inf]:count شهر', + 'm' => '{0}شهر|{1} شهر|{2}شهرين|[3,10]:count أشهر|[11,Inf]:count شهر', + 'week' => '{0}أسبوع|{1}أسبوع|{2}أسبوعين|[3,10]:count أسابيع|[11,Inf]:count أسبوع', + 'w' => '{0}أسبوع|{1}أسبوع|{2}أسبوعين|[3,10]:count أسابيع|[11,Inf]:count أسبوع', + 'day' => '{0}يوم|{1}يوم|{2}يومين|[3,10]:count أيام|[11,Inf] يوم', + 'd' => '{0}يوم|{1}يوم|{2}يومين|[3,10]:count أيام|[11,Inf] يوم', + 'hour' => '{0}ساعة|{1}ساعة|{2}ساعتين|[3,10]:count ساعات|[11,Inf]:count ساعة', + 'h' => '{0}ساعة|{1}ساعة|{2}ساعتين|[3,10]:count ساعات|[11,Inf]:count ساعة', + 'minute' => '{0}دقيقة|{1}دقيقة|{2}دقيقتين|[3,10]:count دقائق|[11,Inf]:count دقيقة', + 'min' => '{0}دقيقة|{1}دقيقة|{2}دقيقتين|[3,10]:count دقائق|[11,Inf]:count دقيقة', + 'second' => '{0}ثانية|{1}ثانية|{2}ثانيتين|[3,10]:count ثوان|[11,Inf]:count ثانية', + 's' => '{0}ثانية|{1}ثانية|{2}ثانيتين|[3,10]:count ثوان|[11,Inf]:count ثانية', + 'ago' => 'منذ :time', + 'from_now' => ':time من الآن', + 'after' => 'بعد :time', + 'before' => 'قبل :time', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php new file mode 100644 index 0000000..846ae02 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '[0,1] سَنَة|{2} سَنَتَيْن|[3,10]:count سَنَوَات|[11,Inf]:count سَنَة', + 'y' => '[0,1] سَنَة|{2} سَنَتَيْن|[3,10]:count سَنَوَات|[11,Inf]:count سَنَة', + 'month' => '[0,1] شَهْرَ|{2} شَهْرَيْن|[3,10]:count أَشْهُر|[11,Inf]:count شَهْرَ', + 'm' => '[0,1] شَهْرَ|{2} شَهْرَيْن|[3,10]:count أَشْهُر|[11,Inf]:count شَهْرَ', + 'week' => '[0,1] أُسْبُوع|{2} أُسْبُوعَيْن|[3,10]:count أَسَابِيع|[11,Inf]:count أُسْبُوع', + 'w' => '[0,1] أُسْبُوع|{2} أُسْبُوعَيْن|[3,10]:count أَسَابِيع|[11,Inf]:count أُسْبُوع', + 'day' => '[0,1] يَوْم|{2} يَوْمَيْن|[3,10]:count أَيَّام|[11,Inf] يَوْم', + 'd' => '[0,1] يَوْم|{2} يَوْمَيْن|[3,10]:count أَيَّام|[11,Inf] يَوْم', + 'hour' => '[0,1] سَاعَة|{2} سَاعَتَيْن|[3,10]:count سَاعَات|[11,Inf]:count سَاعَة', + 'h' => '[0,1] سَاعَة|{2} سَاعَتَيْن|[3,10]:count سَاعَات|[11,Inf]:count سَاعَة', + 'minute' => '[0,1] دَقِيقَة|{2} دَقِيقَتَيْن|[3,10]:count دَقَائِق|[11,Inf]:count دَقِيقَة', + 'min' => '[0,1] دَقِيقَة|{2} دَقِيقَتَيْن|[3,10]:count دَقَائِق|[11,Inf]:count دَقِيقَة', + 'second' => '[0,1] ثَانِيَة|{2} ثَانِيَتَيْن|[3,10]:count ثَوَان|[11,Inf]:count ثَانِيَة', + 's' => '[0,1] ثَانِيَة|{2} ثَانِيَتَيْن|[3,10]:count ثَوَان|[11,Inf]:count ثَانِيَة', + 'ago' => 'مُنْذُ :time', + 'from_now' => 'مِنَ الْآن :time', + 'after' => 'بَعْدَ :time', + 'before' => 'قَبْلَ :time', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/az.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/az.php new file mode 100644 index 0000000..25f5c4a --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/az.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count il', + 'y' => ':count il', + 'month' => ':count ay', + 'm' => ':count ay', + 'week' => ':count həftə', + 'w' => ':count həftə', + 'day' => ':count gün', + 'd' => ':count gün', + 'hour' => ':count saat', + 'h' => ':count saat', + 'minute' => ':count dəqiqə', + 'min' => ':count dəqiqə', + 'second' => ':count saniyə', + 's' => ':count saniyə', + 'ago' => ':time əvvəl', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time əvvəl', + 'diff_now' => 'indi', + 'diff_yesterday' => 'dünən', + 'diff_tomorrow' => 'sabah', + 'diff_before_yesterday' => 'srağagün', + 'diff_after_tomorrow' => 'birisi gün', + 'period_recurrences' => ':count dəfədən bir', + 'period_interval' => 'hər :interval', + 'period_start_date' => ':date tarixindən başlayaraq', + 'period_end_date' => ':date tarixinədək', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/bg.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/bg.php new file mode 100644 index 0000000..d9e510b --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/bg.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count година|:count години', + 'y' => ':count година|:count години', + 'month' => ':count месец|:count месеца', + 'm' => ':count месец|:count месеца', + 'week' => ':count седмица|:count седмици', + 'w' => ':count седмица|:count седмици', + 'day' => ':count ден|:count дни', + 'd' => ':count ден|:count дни', + 'hour' => ':count час|:count часа', + 'h' => ':count час|:count часа', + 'minute' => ':count минута|:count минути', + 'min' => ':count минута|:count минути', + 'second' => ':count секунда|:count секунди', + 's' => ':count секунда|:count секунди', + 'ago' => 'преди :time', + 'from_now' => ':time от сега', + 'after' => 'след :time', + 'before' => 'преди :time', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/bn.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/bn.php new file mode 100644 index 0000000..5817599 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/bn.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '১ বছর|:count বছর', + 'y' => '১ বছর|:count বছর', + 'month' => '১ মাস|:count মাস', + 'm' => '১ মাস|:count মাস', + 'week' => '১ সপ্তাহ|:count সপ্তাহ', + 'w' => '১ সপ্তাহ|:count সপ্তাহ', + 'day' => '১ দিন|:count দিন', + 'd' => '১ দিন|:count দিন', + 'hour' => '১ ঘন্টা|:count ঘন্টা', + 'h' => '১ ঘন্টা|:count ঘন্টা', + 'minute' => '১ মিনিট|:count মিনিট', + 'min' => '১ মিনিট|:count মিনিট', + 'second' => '১ সেকেন্ড|:count সেকেন্ড', + 's' => '১ সেকেন্ড|:count সেকেন্ড', + 'ago' => ':time পূর্বে', + 'from_now' => 'এখন থেকে :time', + 'after' => ':time পরে', + 'before' => ':time আগে', + 'diff_now' => 'এখন', + 'diff_yesterday' => 'গতকাল', + 'diff_tomorrow' => 'আগামীকাল', + 'period_recurrences' => ':count বার|:count বার', + 'period_interval' => 'প্রতি :interval', + 'period_start_date' => ':date থেকে', + 'period_end_date' => ':date পর্যন্ত', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php new file mode 100644 index 0000000..7a9b05a --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count godina|:count godine|:count godina', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count nedjelja|:count nedjelje|:count nedjelja', + 'w' => ':count nedjelja|:count nedjelje|:count nedjelja', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count dan|:count dana|:count dana', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count minut|:count minuta|:count minuta', + 'second' => ':count sekund|:count sekunda|:count sekundi', + 's' => ':count sekund|:count sekunda|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time ranije', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ca.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ca.php new file mode 100644 index 0000000..c854b5a --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ca.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count any|:count anys', + 'y' => ':count any|:count anys', + 'month' => ':count mes|:count mesos', + 'm' => ':count mes|:count mesos', + 'week' => ':count setmana|:count setmanes', + 'w' => ':count setmana|:count setmanes', + 'day' => ':count dia|:count dies', + 'd' => ':count dia|:count dies', + 'hour' => ':count hora|:count hores', + 'h' => ':count hora|:count hores', + 'minute' => ':count minut|:count minuts', + 'min' => ':count minut|:count minuts', + 'second' => ':count segon|:count segons', + 's' => ':count segon|:count segons', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquí :time', + 'after' => ':time després', + 'before' => ':time abans', + 'diff_now' => 'ara mateix', + 'diff_yesterday' => 'ahir', + 'diff_tomorrow' => 'demà', + 'diff_before_yesterday' => "abans d'ahir", + 'diff_after_tomorrow' => 'demà passat', + 'period_recurrences' => ':count cop|:count cops', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'fins a :date', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/cs.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/cs.php new file mode 100644 index 0000000..a447ce2 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/cs.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count rok|:count roky|:count let', + 'y' => ':count rok|:count roky|:count let', + 'month' => ':count měsíc|:count měsíce|:count měsíců', + 'm' => ':count měsíc|:count měsíce|:count měsíců', + 'week' => ':count týden|:count týdny|:count týdnů', + 'w' => ':count týden|:count týdny|:count týdnů', + 'day' => ':count den|:count dny|:count dní', + 'd' => ':count den|:count dny|:count dní', + 'hour' => ':count hodinu|:count hodiny|:count hodin', + 'h' => ':count hodinu|:count hodiny|:count hodin', + 'minute' => ':count minutu|:count minuty|:count minut', + 'min' => ':count minutu|:count minuty|:count minut', + 'second' => ':count sekundu|:count sekundy|:count sekund', + 's' => ':count sekundu|:count sekundy|:count sekund', + 'ago' => ':time nazpět', + 'from_now' => 'za :time', + 'after' => ':time později', + 'before' => ':time předtím', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/cy.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/cy.php new file mode 100644 index 0000000..c93750e --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/cy.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array( + 'year' => '1 flwyddyn|:count blynedd', + 'y' => ':countbl', + 'month' => '1 mis|:count fis', + 'm' => ':countmi', + 'week' => ':count wythnos', + 'w' => ':countw', + 'day' => ':count diwrnod', + 'd' => ':countd', + 'hour' => ':count awr', + 'h' => ':counth', + 'minute' => ':count munud', + 'min' => ':countm', + 'second' => ':count eiliad', + 's' => ':counts', + 'ago' => ':time yn ôl', + 'from_now' => ':time o hyn ymlaen', + 'after' => ':time ar ôl', + 'before' => ':time o\'r blaen', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/da.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/da.php new file mode 100644 index 0000000..86507b0 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/da.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count måned|:count måneder', + 'm' => ':count måned|:count måneder', + 'week' => ':count uge|:count uger', + 'w' => ':count uge|:count uger', + 'day' => ':count dag|:count dage', + 'd' => ':count dag|:count dage', + 'hour' => ':count time|:count timer', + 'h' => ':count time|:count timer', + 'minute' => ':count minut|:count minutter', + 'min' => ':count minut|:count minutter', + 'second' => ':count sekund|:count sekunder', + 's' => ':count sekund|:count sekunder', + 'ago' => ':time siden', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time før', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/de.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/de.php new file mode 100644 index 0000000..5ea2a03 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/de.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count Jahr|:count Jahre', + 'y' => ':countJ|:countJ', + 'month' => ':count Monat|:count Monate', + 'm' => ':countMon|:countMon', + 'week' => ':count Woche|:count Wochen', + 'w' => ':countWo|:countWo', + 'day' => ':count Tag|:count Tage', + 'd' => ':countTg|:countTg', + 'hour' => ':count Stunde|:count Stunden', + 'h' => ':countStd|:countStd', + 'minute' => ':count Minute|:count Minuten', + 'min' => ':countMin|:countMin', + 'second' => ':count Sekunde|:count Sekunden', + 's' => ':countSek|:countSek', + 'ago' => 'vor :time', + 'from_now' => 'in :time', + 'after' => ':time später', + 'before' => ':time zuvor', + + 'year_from_now' => ':count Jahr|:count Jahren', + 'month_from_now' => ':count Monat|:count Monaten', + 'week_from_now' => ':count Woche|:count Wochen', + 'day_from_now' => ':count Tag|:count Tagen', + 'year_ago' => ':count Jahr|:count Jahren', + 'month_ago' => ':count Monat|:count Monaten', + 'week_ago' => ':count Woche|:count Wochen', + 'day_ago' => ':count Tag|:count Tagen', + + 'diff_now' => 'Gerade eben', + 'diff_yesterday' => 'Gestern', + 'diff_tomorrow' => 'Heute', + 'diff_before_yesterday' => 'Vorgestern', + 'diff_after_tomorrow' => 'Übermorgen', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php new file mode 100644 index 0000000..e3c50b3 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '{0}އަހަރެއް|[1,Inf]:count އަހަރު', + 'y' => '{0}އަހަރެއް|[1,Inf]:count އަހަރު', + 'month' => '{0}މައްސަރެއް|[1,Inf]:count މަސް', + 'm' => '{0}މައްސަރެއް|[1,Inf]:count މަސް', + 'week' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ', + 'w' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ', + 'day' => '{0}ދުވަސް|[1,Inf]:count ދުވަސް', + 'd' => '{0}ދުވަސް|[1,Inf]:count ދުވަސް', + 'hour' => '{0}ގަޑިއިރެއް|[1,Inf]:count ގަޑި', + 'h' => '{0}ގަޑިއިރެއް|[1,Inf]:count ގަޑި', + 'minute' => '{0}މިނެޓެއް|[1,Inf]:count މިނެޓް', + 'min' => '{0}މިނެޓެއް|[1,Inf]:count މިނެޓް', + 'second' => '{0}ސިކުންތެއް|[1,Inf]:count ސިކުންތު', + 's' => '{0}ސިކުންތެއް|[1,Inf]:count ސިކުންތު', + 'ago' => ':time ކުރިން', + 'from_now' => ':time ފަހުން', + 'after' => ':time ފަހުން', + 'before' => ':time ކުރި', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/el.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/el.php new file mode 100644 index 0000000..16b3f44 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/el.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count χρόνος|:count χρόνια', + 'y' => ':count χρόνος|:count χρόνια', + 'month' => ':count μήνας|:count μήνες', + 'm' => ':count μήνας|:count μήνες', + 'week' => ':count εβδομάδα|:count εβδομάδες', + 'w' => ':count εβδομάδα|:count εβδομάδες', + 'day' => ':count μέρα|:count μέρες', + 'd' => ':count μέρα|:count μέρες', + 'hour' => ':count ώρα|:count ώρες', + 'h' => ':count ώρα|:count ώρες', + 'minute' => ':count λεπτό|:count λεπτά', + 'min' => ':count λεπτό|:count λεπτά', + 'second' => ':count δευτερόλεπτο|:count δευτερόλεπτα', + 's' => ':count δευτερόλεπτο|:count δευτερόλεπτα', + 'ago' => 'πριν από :time', + 'from_now' => 'σε :time από τώρα', + 'after' => ':time μετά', + 'before' => ':time πριν', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/en.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/en.php new file mode 100644 index 0000000..a15c131 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/en.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count year|:count years', + 'y' => ':countyr|:countyrs', + 'month' => ':count month|:count months', + 'm' => ':countmo|:countmos', + 'week' => ':count week|:count weeks', + 'w' => ':countw|:countw', + 'day' => ':count day|:count days', + 'd' => ':countd|:countd', + 'hour' => ':count hour|:count hours', + 'h' => ':counth|:counth', + 'minute' => ':count minute|:count minutes', + 'min' => ':countm|:countm', + 'second' => ':count second|:count seconds', + 's' => ':counts|:counts', + 'ago' => ':time ago', + 'from_now' => ':time from now', + 'after' => ':time after', + 'before' => ':time before', + 'diff_now' => 'just now', + 'diff_yesterday' => 'yesterday', + 'diff_tomorrow' => 'tomorrow', + 'diff_before_yesterday' => 'before yesterday', + 'diff_after_tomorrow' => 'after tomorrow', + 'period_recurrences' => 'once|:count times', + 'period_interval' => 'every :interval', + 'period_start_date' => 'from :date', + 'period_end_date' => 'to :date', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/eo.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/eo.php new file mode 100644 index 0000000..c5b90b3 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/eo.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count jaro|:count jaroj', + 'y' => ':count jaro|:count jaroj', + 'month' => ':count monato|:count monatoj', + 'm' => ':count monato|:count monatoj', + 'week' => ':count semajno|:count semajnoj', + 'w' => ':count semajno|:count semajnoj', + 'day' => ':count tago|:count tagoj', + 'd' => ':count tago|:count tagoj', + 'hour' => ':count horo|:count horoj', + 'h' => ':count horo|:count horoj', + 'minute' => ':count minuto|:count minutoj', + 'min' => ':count minuto|:count minutoj', + 'second' => ':count sekundo|:count sekundoj', + 's' => ':count sekundo|:count sekundoj', + 'ago' => 'antaŭ :time', + 'from_now' => 'je :time', + 'after' => ':time poste', + 'before' => ':time antaŭe', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/es.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/es.php new file mode 100644 index 0000000..567a280 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/es.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count año|:count años', + 'y' => ':count año|:count años', + 'month' => ':count mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count semana|:count semanas', + 'w' => ':count semana|:count semanas', + 'day' => ':count día|:count días', + 'd' => ':count día|:count días', + 'hour' => ':count hora|:count horas', + 'h' => ':count hora|:count horas', + 'minute' => ':count minuto|:count minutos', + 'min' => ':count minuto|:count minutos', + 'second' => ':count segundo|:count segundos', + 's' => ':count segundo|:count segundos', + 'ago' => 'hace :time', + 'from_now' => 'dentro de :time', + 'after' => ':time después', + 'before' => ':time antes', + 'diff_now' => 'ahora mismo', + 'diff_yesterday' => 'ayer', + 'diff_tomorrow' => 'mañana', + 'diff_before_yesterday' => 'antier', + 'diff_after_tomorrow' => 'pasado mañana', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/et.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/et.php new file mode 100644 index 0000000..2d9291e --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/et.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count aasta|:count aastat', + 'y' => ':count aasta|:count aastat', + 'month' => ':count kuu|:count kuud', + 'm' => ':count kuu|:count kuud', + 'week' => ':count nädal|:count nädalat', + 'w' => ':count nädal|:count nädalat', + 'day' => ':count päev|:count päeva', + 'd' => ':count päev|:count päeva', + 'hour' => ':count tund|:count tundi', + 'h' => ':count tund|:count tundi', + 'minute' => ':count minut|:count minutit', + 'min' => ':count minut|:count minutit', + 'second' => ':count sekund|:count sekundit', + 's' => ':count sekund|:count sekundit', + 'ago' => ':time tagasi', + 'from_now' => ':time pärast', + 'after' => ':time pärast', + 'before' => ':time enne', + 'year_from_now' => ':count aasta', + 'month_from_now' => ':count kuu', + 'week_from_now' => ':count nädala', + 'day_from_now' => ':count päeva', + 'hour_from_now' => ':count tunni', + 'minute_from_now' => ':count minuti', + 'second_from_now' => ':count sekundi', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/eu.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/eu.php new file mode 100644 index 0000000..1cb6b7c --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/eu.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => 'Urte 1|:count urte', + 'y' => 'Urte 1|:count urte', + 'month' => 'Hile 1|:count hile', + 'm' => 'Hile 1|:count hile', + 'week' => 'Aste 1|:count aste', + 'w' => 'Aste 1|:count aste', + 'day' => 'Egun 1|:count egun', + 'd' => 'Egun 1|:count egun', + 'hour' => 'Ordu 1|:count ordu', + 'h' => 'Ordu 1|:count ordu', + 'minute' => 'Minutu 1|:count minutu', + 'min' => 'Minutu 1|:count minutu', + 'second' => 'Segundu 1|:count segundu', + 's' => 'Segundu 1|:count segundu', + 'ago' => 'Orain dela :time', + 'from_now' => ':time barru', + 'after' => ':time geroago', + 'before' => ':time lehenago', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/fa.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/fa.php new file mode 100644 index 0000000..31bec88 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/fa.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count سال', + 'y' => ':count سال', + 'month' => ':count ماه', + 'm' => ':count ماه', + 'week' => ':count هفته', + 'w' => ':count هفته', + 'day' => ':count روز', + 'd' => ':count روز', + 'hour' => ':count ساعت', + 'h' => ':count ساعت', + 'minute' => ':count دقیقه', + 'min' => ':count دقیقه', + 'second' => ':count ثانیه', + 's' => ':count ثانیه', + 'ago' => ':time پیش', + 'from_now' => ':time بعد', + 'after' => ':time پس از', + 'before' => ':time پیش از', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/fi.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/fi.php new file mode 100644 index 0000000..4818804 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/fi.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count vuosi|:count vuotta', + 'y' => ':count vuosi|:count vuotta', + 'month' => ':count kuukausi|:count kuukautta', + 'm' => ':count kuukausi|:count kuukautta', + 'week' => ':count viikko|:count viikkoa', + 'w' => ':count viikko|:count viikkoa', + 'day' => ':count päivä|:count päivää', + 'd' => ':count päivä|:count päivää', + 'hour' => ':count tunti|:count tuntia', + 'h' => ':count tunti|:count tuntia', + 'minute' => ':count minuutti|:count minuuttia', + 'min' => ':count minuutti|:count minuuttia', + 'second' => ':count sekunti|:count sekuntia', + 's' => ':count sekunti|:count sekuntia', + 'ago' => ':time sitten', + 'from_now' => ':time tästä hetkestä', + 'after' => ':time sen jälkeen', + 'before' => ':time ennen', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/fo.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/fo.php new file mode 100644 index 0000000..d91104b --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/fo.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count ár|:count ár', + 'y' => ':count ár|:count ár', + 'month' => ':count mánaður|:count mánaðir', + 'm' => ':count mánaður|:count mánaðir', + 'week' => ':count vika|:count vikur', + 'w' => ':count vika|:count vikur', + 'day' => ':count dag|:count dagar', + 'd' => ':count dag|:count dagar', + 'hour' => ':count tími|:count tímar', + 'h' => ':count tími|:count tímar', + 'minute' => ':count minutt|:count minuttir', + 'min' => ':count minutt|:count minuttir', + 'second' => ':count sekund|:count sekundir', + 's' => ':count sekund|:count sekundir', + 'ago' => ':time síðan', + 'from_now' => 'um :time', + 'after' => ':time aftaná', + 'before' => ':time áðrenn', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/fr.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/fr.php new file mode 100644 index 0000000..0b20cdd --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/fr.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mois', + 'm' => ':count mois', + 'week' => ':count semaine|:count semaines', + 'w' => ':count sem.', + 'day' => ':count jour|:count jours', + 'd' => ':count j.', + 'hour' => ':count heure|:count heures', + 'h' => ':count h.', + 'minute' => ':count minute|:count minutes', + 'min' => ':count min.', + 'second' => ':count seconde|:count secondes', + 's' => ':count sec.', + 'ago' => 'il y a :time', + 'from_now' => 'dans :time', + 'after' => ':time après', + 'before' => ':time avant', + 'diff_now' => "à l'instant", + 'diff_yesterday' => 'hier', + 'diff_tomorrow' => 'demain', + 'diff_before_yesterday' => 'avant-hier', + 'diff_after_tomorrow' => 'après-demain', + 'period_recurrences' => ':count fois', + 'period_interval' => 'tous les :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'à :date', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/gl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/gl.php new file mode 100644 index 0000000..cd22a31 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/gl.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count ano|:count anos', + 'month' => ':count mes|:count meses', + 'week' => ':count semana|:count semanas', + 'day' => ':count día|:count días', + 'hour' => ':count hora|:count horas', + 'minute' => ':count minuto|:count minutos', + 'second' => ':count segundo|:count segundos', + 'ago' => 'fai :time', + 'from_now' => 'dentro de :time', + 'after' => ':time despois', + 'before' => ':time antes', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/gu.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/gu.php new file mode 100644 index 0000000..7759dfc --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/gu.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count વર્ષ|:count વર્ષો', + 'y' => ':countવર્ષ|:countવર્ષો', + 'month' => ':count મહિનો|:count મહિના', + 'm' => ':countમહિનો|:countમહિના', + 'week' => ':count અઠવાડિયું|:count અઠવાડિયા', + 'w' => ':countઅઠ.|:countઅઠ.', + 'day' => ':count દિવસ|:count દિવસો', + 'd' => ':countદિ.|:countદિ.', + 'hour' => ':count કલાક|:count કલાકો', + 'h' => ':countક.|:countક.', + 'minute' => ':count મિનિટ|:count મિનિટ', + 'min' => ':countમિ.|:countમિ.', + 'second' => ':count સેકેન્ડ|:count સેકેન્ડ', + 's' => ':countસે.|:countસે.', + 'ago' => ':time પહેલા', + 'from_now' => ':time અત્યારથી', + 'after' => ':time પછી', + 'before' => ':time પહેલા', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/he.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/he.php new file mode 100644 index 0000000..2d4f4f8 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/he.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => 'שנה|{2}שנתיים|:count שנים', + 'y' => 'שנה|{2}שנתיים|:count שנים', + 'month' => 'חודש|{2}חודשיים|:count חודשים', + 'm' => 'חודש|{2}חודשיים|:count חודשים', + 'week' => 'שבוע|{2}שבועיים|:count שבועות', + 'w' => 'שבוע|{2}שבועיים|:count שבועות', + 'day' => 'יום|{2}יומיים|:count ימים', + 'd' => 'יום|{2}יומיים|:count ימים', + 'hour' => 'שעה|{2}שעתיים|:count שעות', + 'h' => 'שעה|{2}שעתיים|:count שעות', + 'minute' => 'דקה|{2}דקותיים|:count דקות', + 'min' => 'דקה|{2}דקותיים|:count דקות', + 'second' => 'שניה|:count שניות', + 's' => 'שניה|:count שניות', + 'ago' => 'לפני :time', + 'from_now' => 'בעוד :time', + 'after' => 'אחרי :time', + 'before' => 'לפני :time', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/hi.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/hi.php new file mode 100644 index 0000000..6c670ee --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/hi.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '1 वर्ष|:count वर्षों', + 'y' => '1 वर्ष|:count वर्षों', + 'month' => '1 माह|:count महीने', + 'm' => '1 माह|:count महीने', + 'week' => '1 सप्ताह|:count सप्ताह', + 'w' => '1 सप्ताह|:count सप्ताह', + 'day' => '1 दिन|:count दिनों', + 'd' => '1 दिन|:count दिनों', + 'hour' => '1 घंटा|:count घंटे', + 'h' => '1 घंटा|:count घंटे', + 'minute' => '1 मिनट|:count मिनटों', + 'min' => '1 मिनट|:count मिनटों', + 'second' => '1 सेकंड|:count सेकंड', + 's' => '1 सेकंड|:count सेकंड', + 'ago' => ':time पूर्व', + 'from_now' => ':time से', + 'after' => ':time के बाद', + 'before' => ':time के पहले', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/hr.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/hr.php new file mode 100644 index 0000000..1a339de --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/hr.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count godinu|:count godine|:count godina', + 'y' => ':count godinu|:count godine|:count godina', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count tjedan|:count tjedna|:count tjedana', + 'w' => ':count tjedan|:count tjedna|:count tjedana', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count dan|:count dana|:count dana', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minutu|:count minute |:count minuta', + 'min' => ':count minutu|:count minute |:count minuta', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 's' => ':count sekundu|:count sekunde|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => 'za :time', + 'before' => 'prije :time', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/hu.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/hu.php new file mode 100644 index 0000000..45daf41 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/hu.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count év', + 'y' => ':count év', + 'month' => ':count hónap', + 'm' => ':count hónap', + 'week' => ':count hét', + 'w' => ':count hét', + 'day' => ':count nap', + 'd' => ':count nap', + 'hour' => ':count óra', + 'h' => ':count óra', + 'minute' => ':count perc', + 'min' => ':count perc', + 'second' => ':count másodperc', + 's' => ':count másodperc', + 'ago' => ':time', + 'from_now' => ':time múlva', + 'after' => ':time később', + 'before' => ':time korábban', + 'year_ago' => ':count éve', + 'month_ago' => ':count hónapja', + 'week_ago' => ':count hete', + 'day_ago' => ':count napja', + 'hour_ago' => ':count órája', + 'minute_ago' => ':count perce', + 'second_ago' => ':count másodperce', + 'year_after' => ':count évvel', + 'month_after' => ':count hónappal', + 'week_after' => ':count héttel', + 'day_after' => ':count nappal', + 'hour_after' => ':count órával', + 'minute_after' => ':count perccel', + 'second_after' => ':count másodperccel', + 'year_before' => ':count évvel', + 'month_before' => ':count hónappal', + 'week_before' => ':count héttel', + 'day_before' => ':count nappal', + 'hour_before' => ':count órával', + 'minute_before' => ':count perccel', + 'second_before' => ':count másodperccel', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/hy.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/hy.php new file mode 100644 index 0000000..d2665f2 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/hy.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count տարի', + 'y' => ':countտ', + 'month' => ':count ամիս', + 'm' => ':countամ', + 'week' => ':count շաբաթ', + 'w' => ':countշ', + 'day' => ':count օր', + 'd' => ':countօր', + 'hour' => ':count ժամ', + 'h' => ':countժ', + 'minute' => ':count րոպե', + 'min' => ':countր', + 'second' => ':count վարկյան', + 's' => ':countվրկ', + 'ago' => ':time առաջ', + 'from_now' => ':time ներկա պահից', + 'after' => ':time հետո', + 'before' => ':time առաջ', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/id.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/id.php new file mode 100644 index 0000000..7f7114f --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/id.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count tahun', + 'y' => ':count tahun', + 'month' => ':count bulan', + 'm' => ':count bulan', + 'week' => ':count minggu', + 'w' => ':count minggu', + 'day' => ':count hari', + 'd' => ':count hari', + 'hour' => ':count jam', + 'h' => ':count jam', + 'minute' => ':count menit', + 'min' => ':count menit', + 'second' => ':count detik', + 's' => ':count detik', + 'ago' => ':time yang lalu', + 'from_now' => ':time dari sekarang', + 'after' => ':time setelah', + 'before' => ':time sebelum', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/is.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/is.php new file mode 100644 index 0000000..94c76a7 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/is.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '1 ár|:count ár', + 'y' => '1 ár|:count ár', + 'month' => '1 mánuður|:count mánuðir', + 'm' => '1 mánuður|:count mánuðir', + 'week' => '1 vika|:count vikur', + 'w' => '1 vika|:count vikur', + 'day' => '1 dagur|:count dagar', + 'd' => '1 dagur|:count dagar', + 'hour' => '1 klukkutími|:count klukkutímar', + 'h' => '1 klukkutími|:count klukkutímar', + 'minute' => '1 mínúta|:count mínútur', + 'min' => '1 mínúta|:count mínútur', + 'second' => '1 sekúnda|:count sekúndur', + 's' => '1 sekúnda|:count sekúndur', + 'ago' => ':time síðan', + 'from_now' => ':time síðan', + 'after' => ':time eftir', + 'before' => ':time fyrir', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/it.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/it.php new file mode 100644 index 0000000..70bc6d7 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/it.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count anno|:count anni', + 'y' => ':count anno|:count anni', + 'month' => ':count mese|:count mesi', + 'm' => ':count mese|:count mesi', + 'week' => ':count settimana|:count settimane', + 'w' => ':count settimana|:count settimane', + 'day' => ':count giorno|:count giorni', + 'd' => ':count giorno|:count giorni', + 'hour' => ':count ora|:count ore', + 'h' => ':count ora|:count ore', + 'minute' => ':count minuto|:count minuti', + 'min' => ':count minuto|:count minuti', + 'second' => ':count secondo|:count secondi', + 's' => ':count secondo|:count secondi', + 'ago' => ':time fa', + 'from_now' => 'tra :time', + 'after' => ':time dopo', + 'before' => ':time prima', + 'diff_now' => 'proprio ora', + 'diff_yesterday' => 'ieri', + 'diff_tomorrow' => 'domani', + 'diff_before_yesterday' => "l'altro ieri", + 'diff_after_tomorrow' => 'dopodomani', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ja.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ja.php new file mode 100644 index 0000000..7119547 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ja.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count年', + 'y' => ':count年', + 'month' => ':countヶ月', + 'm' => ':countヶ月', + 'week' => ':count週間', + 'w' => ':count週間', + 'day' => ':count日', + 'd' => ':count日', + 'hour' => ':count時間', + 'h' => ':count時間', + 'minute' => ':count分', + 'min' => ':count分', + 'second' => ':count秒', + 's' => ':count秒', + 'ago' => ':time前', + 'from_now' => '今から:time', + 'after' => ':time後', + 'before' => ':time前', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ka.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ka.php new file mode 100644 index 0000000..a8dde7e --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ka.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count წლის', + 'y' => ':count წლის', + 'month' => ':count თვის', + 'm' => ':count თვის', + 'week' => ':count კვირის', + 'w' => ':count კვირის', + 'day' => ':count დღის', + 'd' => ':count დღის', + 'hour' => ':count საათის', + 'h' => ':count საათის', + 'minute' => ':count წუთის', + 'min' => ':count წუთის', + 'second' => ':count წამის', + 's' => ':count წამის', + 'ago' => ':time უკან', + 'from_now' => ':time შემდეგ', + 'after' => ':time შემდეგ', + 'before' => ':time უკან', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/kk.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/kk.php new file mode 100644 index 0000000..8d113af --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/kk.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array( + 'year' => ':count жыл', + 'y' => ':count жыл', + 'month' => ':count ай', + 'm' => ':count ай', + 'week' => ':count апта', + 'w' => ':count апта', + 'day' => ':count күн', + 'd' => ':count күн', + 'hour' => ':count сағат', + 'h' => ':count сағат', + 'minute' => ':count минут', + 'min' => ':count минут', + 'second' => ':count секунд', + 's' => ':count секунд', + 'ago' => ':time бұрын', + 'from_now' => ':time кейін', + 'after' => ':time кейін', + 'before' => ':time бұрын', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/km.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/km.php new file mode 100644 index 0000000..a104e06 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/km.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count ឆ្នាំ', + 'y' => ':count ឆ្នាំ', + 'month' => ':count ខែ', + 'm' => ':count ខែ', + 'week' => ':count សប្ដាហ៍', + 'w' => ':count សប្ដាហ៍', + 'day' => ':count ថ្ងៃ', + 'd' => ':count ថ្ងៃ', + 'hour' => ':count ម៉ោង', + 'h' => ':count ម៉ោង', + 'minute' => ':count នាទី', + 'min' => ':count នាទី', + 'second' => ':count វិនាទី', + 's' => ':count វិនាទី', + 'ago' => ':timeមុន', + 'from_now' => ':timeពី​ឥឡូវ', + 'after' => 'នៅ​ក្រោយ :time', + 'before' => 'នៅ​មុន :time', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ko.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ko.php new file mode 100644 index 0000000..0209164 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ko.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count 년', + 'y' => ':count 년', + 'month' => ':count 개월', + 'm' => ':count 개월', + 'week' => ':count 주일', + 'w' => ':count 주일', + 'day' => ':count 일', + 'd' => ':count 일', + 'hour' => ':count 시간', + 'h' => ':count 시간', + 'minute' => ':count 분', + 'min' => ':count 분', + 'second' => ':count 초', + 's' => ':count 초', + 'ago' => ':time 전', + 'from_now' => ':time 후', + 'after' => ':time 이후', + 'before' => ':time 이전', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/lt.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/lt.php new file mode 100644 index 0000000..3f2fd1e --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/lt.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count metus|:count metus|:count metų', + 'y' => ':count metus|:count metus|:count metų', + 'month' => ':count mėnesį|:count mėnesius|:count mėnesių', + 'm' => ':count mėnesį|:count mėnesius|:count mėnesių', + 'week' => ':count savaitę|:count savaites|:count savaičių', + 'w' => ':count savaitę|:count savaites|:count savaičių', + 'day' => ':count dieną|:count dienas|:count dienų', + 'd' => ':count dieną|:count dienas|:count dienų', + 'hour' => ':count valandą|:count valandas|:count valandų', + 'h' => ':count valandą|:count valandas|:count valandų', + 'minute' => ':count minutę|:count minutes|:count minučių', + 'min' => ':count minutę|:count minutes|:count minučių', + 'second' => ':count sekundę|:count sekundes|:count sekundžių', + 's' => ':count sekundę|:count sekundes|:count sekundžių', + 'second_from_now' => ':count sekundės|:count sekundžių|:count sekundžių', + 'minute_from_now' => ':count minutės|:count minučių|:count minučių', + 'hour_from_now' => ':count valandos|:count valandų|:count valandų', + 'day_from_now' => ':count dienos|:count dienų|:count dienų', + 'week_from_now' => ':count savaitės|:count savaičių|:count savaičių', + 'month_from_now' => ':count mėnesio|:count mėnesių|:count mėnesių', + 'year_from_now' => ':count metų', + 'ago' => 'prieš :time', + 'from_now' => 'už :time', + 'after' => 'po :time', + 'before' => ':time nuo dabar', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/lv.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/lv.php new file mode 100644 index 0000000..363193d --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/lv.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '0 gadiem|:count gada|:count gadiem', + 'y' => '0 gadiem|:count gada|:count gadiem', + 'month' => '0 mēnešiem|:count mēneša|:count mēnešiem', + 'm' => '0 mēnešiem|:count mēneša|:count mēnešiem', + 'week' => '0 nedēļām|:count nedēļas|:count nedēļām', + 'w' => '0 nedēļām|:count nedēļas|:count nedēļām', + 'day' => '0 dienām|:count dienas|:count dienām', + 'd' => '0 dienām|:count dienas|:count dienām', + 'hour' => '0 stundām|:count stundas|:count stundām', + 'h' => '0 stundām|:count stundas|:count stundām', + 'minute' => '0 minūtēm|:count minūtes|:count minūtēm', + 'min' => '0 minūtēm|:count minūtes|:count minūtēm', + 'second' => '0 sekundēm|:count sekundes|:count sekundēm', + 's' => '0 sekundēm|:count sekundes|:count sekundēm', + 'ago' => 'pirms :time', + 'from_now' => 'pēc :time', + 'after' => ':time vēlāk', + 'before' => ':time pirms', + + 'year_after' => '0 gadus|:count gadu|:count gadus', + 'month_after' => '0 mēnešus|:count mēnesi|:count mēnešus', + 'week_after' => '0 nedēļas|:count nedēļu|:count nedēļas', + 'day_after' => '0 dienas|:count dienu|:count dienas', + 'hour_after' => '0 stundas|:count stundu|:count stundas', + 'minute_after' => '0 minūtes|:count minūti|:count minūtes', + 'second_after' => '0 sekundes|:count sekundi|:count sekundes', + + 'year_before' => '0 gadus|:count gadu|:count gadus', + 'month_before' => '0 mēnešus|:count mēnesi|:count mēnešus', + 'week_before' => '0 nedēļas|:count nedēļu|:count nedēļas', + 'day_before' => '0 dienas|:count dienu|:count dienas', + 'hour_before' => '0 stundas|:count stundu|:count stundas', + 'minute_before' => '0 minūtes|:count minūti|:count minūtes', + 'second_before' => '0 sekundes|:count sekundi|:count sekundes', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/mk.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/mk.php new file mode 100644 index 0000000..c5ec12d --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/mk.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count година|:count години', + 'month' => ':count месец|:count месеци', + 'week' => ':count седмица|:count седмици', + 'day' => ':count ден|:count дена', + 'hour' => ':count час|:count часа', + 'minute' => ':count минута|:count минути', + 'second' => ':count секунда|:count секунди', + 'ago' => 'пред :time', + 'from_now' => ':time од сега', + 'after' => 'по :time', + 'before' => 'пред :time', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/mn.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/mn.php new file mode 100644 index 0000000..b26dce5 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/mn.php @@ -0,0 +1,62 @@ + + * + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @translator Batmandakh Erdenebileg + */ + +return array( + 'year' => ':count жил', + 'y' => ':count жил', + 'month' => ':count сар', + 'm' => ':count сар', + 'week' => ':count долоо хоног', + 'w' => ':count долоо хоног', + 'day' => ':count өдөр', + 'd' => ':count өдөр', + 'hour' => ':count цаг', + 'h' => ':countц', + 'minute' => ':count минут', + 'min' => ':countм', + 'second' => ':count секунд', + 's' => ':countс', + + 'ago' => ':timeн өмнө', + 'year_ago' => ':count жилий', + 'month_ago' => ':count сары', + 'day_ago' => ':count хоногий', + 'hour_ago' => ':count цагий', + 'minute_ago' => ':count минуты', + 'second_ago' => ':count секунды', + + 'from_now' => 'одоогоос :time', + 'year_from_now' => ':count жилийн дараа', + 'month_from_now' => ':count сарын дараа', + 'day_from_now' => ':count хоногийн дараа', + 'hour_from_now' => ':count цагийн дараа', + 'minute_from_now' => ':count минутын дараа', + 'second_from_now' => ':count секундын дараа', + + // Does it required to make translation for before, after as follows? hmm, I think we've made it with ago and from now keywords already. Anyway, I've included it just in case of undesired action... + 'after' => ':timeн дараа', + 'year_after' => ':count жилий', + 'month_after' => ':count сары', + 'day_after' => ':count хоногий', + 'hour_after' => ':count цагий', + 'minute_after' => ':count минуты', + 'second_after' => ':count секунды', + 'before' => ':timeн өмнө', + 'year_before' => ':count жилий', + 'month_before' => ':count сары', + 'day_before' => ':count хоногий', + 'hour_before' => ':count цагий', + 'minute_before' => ':count минуты', + 'second_before' => ':count секунды', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ms.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ms.php new file mode 100644 index 0000000..ef57422 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ms.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count tahun', + 'y' => ':count tahun', + 'month' => ':count bulan', + 'm' => ':count bulan', + 'week' => ':count minggu', + 'w' => ':count minggu', + 'day' => ':count hari', + 'd' => ':count hari', + 'hour' => ':count jam', + 'h' => ':count jam', + 'minute' => ':count minit', + 'min' => ':count minit', + 'second' => ':count saat', + 's' => ':count saat', + 'ago' => ':time yang lalu', + 'from_now' => ':time dari sekarang', + 'after' => ':time selepas', + 'before' => ':time sebelum', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/my.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/my.php new file mode 100644 index 0000000..e8e491e --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/my.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count နှစ်|:count နှစ်', + 'y' => ':count နှစ်|:count နှစ်', + 'month' => ':count လ|:count လ', + 'm' => ':count လ|:count လ', + 'week' => ':count ပတ်|:count ပတ်', + 'w' => ':count ပတ်|:count ပတ်', + 'day' => ':count ရက်|:count ရက်', + 'd' => ':count ရက်|:count ရက်', + 'hour' => ':count နာရီ|:count နာရီ', + 'h' => ':count နာရီ|:count နာရီ', + 'minute' => ':count မိနစ်|:count မိနစ်', + 'min' => ':count မိနစ်|:count မိနစ်', + 'second' => ':count စက္ကန့်|:count စက္ကန့်', + 's' => ':count စက္ကန့်|:count စက္ကန့်', + 'ago' => 'လွန်ခဲ့သော :time က', + 'from_now' => 'ယခုမှစ၍နောက် :time အကြာ', + 'after' => ':time ကြာပြီးနောက်', + 'before' => ':time မတိုင်ခင်', + 'diff_now' => 'အခုလေးတင်', + 'diff_yesterday' => 'မနေ့က', + 'diff_tomorrow' => 'မနက်ဖြန်', + 'diff_before_yesterday' => 'တမြန်နေ့က', + 'diff_after_tomorrow' => 'တဘက်ခါ', + 'period_recurrences' => ':count ကြိမ်', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ne.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ne.php new file mode 100644 index 0000000..0b528df --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ne.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count वर्ष', + 'y' => ':count वर्ष', + 'month' => ':count महिना', + 'm' => ':count महिना', + 'week' => ':count हप्ता', + 'w' => ':count हप्ता', + 'day' => ':count दिन', + 'd' => ':count दिन', + 'hour' => ':count घण्टा', + 'h' => ':count घण्टा', + 'minute' => ':count मिनेट', + 'min' => ':count मिनेट', + 'second' => ':count सेकेण्ड', + 's' => ':count सेकेण्ड', + 'ago' => ':time पहिले', + 'from_now' => ':time देखि', + 'after' => ':time पछि', + 'before' => ':time अघि', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/nl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/nl.php new file mode 100644 index 0000000..ec5a88e --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/nl.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count jaar', + 'y' => ':count jaar', + 'month' => ':count maand|:count maanden', + 'm' => ':count maand|:count maanden', + 'week' => ':count week|:count weken', + 'w' => ':count week|:count weken', + 'day' => ':count dag|:count dagen', + 'd' => ':count dag|:count dagen', + 'hour' => ':count uur', + 'h' => ':count uur', + 'minute' => ':count minuut|:count minuten', + 'min' => ':count minuut|:count minuten', + 'second' => ':count seconde|:count seconden', + 's' => ':count seconde|:count seconden', + 'ago' => ':time geleden', + 'from_now' => 'over :time', + 'after' => ':time later', + 'before' => ':time eerder', + 'diff_now' => 'nu', + 'diff_yesterday' => 'gisteren', + 'diff_tomorrow' => 'morgen', + 'diff_after_tomorrow' => 'overmorgen', + 'diff_before_yesterday' => 'eergisteren', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/no.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/no.php new file mode 100644 index 0000000..a6ece06 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/no.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count måned|:count måneder', + 'm' => ':count måned|:count måneder', + 'week' => ':count uke|:count uker', + 'w' => ':count uke|:count uker', + 'day' => ':count dag|:count dager', + 'd' => ':count dag|:count dager', + 'hour' => ':count time|:count timer', + 'h' => ':count time|:count timer', + 'minute' => ':count minutt|:count minutter', + 'min' => ':count minutt|:count minutter', + 'second' => ':count sekund|:count sekunder', + 's' => ':count sekund|:count sekunder', + 'ago' => ':time siden', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time før', + 'diff_now' => 'akkurat nå', + 'diff_yesterday' => 'i går', + 'diff_tomorrow' => 'i morgen', + 'diff_before_yesterday' => 'i forgårs', + 'diff_after_tomorrow' => 'i overmorgen', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/oc.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/oc.php new file mode 100644 index 0000000..e89e94c --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/oc.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +\Symfony\Component\Translation\PluralizationRules::set(function ($number) { + return $number == 1 ? 0 : 1; +}, 'oc'); + +return array( + 'year' => ':count an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count setmana|:count setmanas', + 'w' => ':count setmana|:count setmanas', + 'day' => ':count jorn|:count jorns', + 'd' => ':count jorn|:count jorns', + 'hour' => ':count ora|:count oras', + 'h' => ':count ora|:count oras', + 'minute' => ':count minuta|:count minutas', + 'min' => ':count minuta|:count minutas', + 'second' => ':count segonda|:count segondas', + 's' => ':count segonda|:count segondas', + 'ago' => 'fa :time', + 'from_now' => 'dins :time', + 'after' => ':time aprèp', + 'before' => ':time abans', + 'diff_now' => 'ara meteis', + 'diff_yesterday' => 'ièr', + 'diff_tomorrow' => 'deman', + 'diff_before_yesterday' => 'ièr delà', + 'diff_after_tomorrow' => 'deman passat', + 'period_recurrences' => ':count còp|:count còps', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'fins a :date', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/pl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/pl.php new file mode 100644 index 0000000..2308af2 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/pl.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count rok|:count lata|:count lat', + 'y' => ':countr|:countl', + 'month' => ':count miesiąc|:count miesiące|:count miesięcy', + 'm' => ':countmies', + 'week' => ':count tydzień|:count tygodnie|:count tygodni', + 'w' => ':counttyg', + 'day' => ':count dzień|:count dni|:count dni', + 'd' => ':countd', + 'hour' => ':count godzina|:count godziny|:count godzin', + 'h' => ':countg', + 'minute' => ':count minuta|:count minuty|:count minut', + 'min' => ':countm', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 's' => ':counts', + 'ago' => ':time temu', + 'from_now' => ':time od teraz', + 'after' => ':time po', + 'before' => ':time przed', + 'diff_now' => 'przed chwilą', + 'diff_yesterday' => 'wczoraj', + 'diff_tomorrow' => 'jutro', + 'diff_before_yesterday' => 'przedwczoraj', + 'diff_after_tomorrow' => 'pojutrze', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ps.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ps.php new file mode 100644 index 0000000..15c3296 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ps.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count کال|:count کاله', + 'y' => ':countکال|:countکاله', + 'month' => ':count مياشت|:count مياشتي', + 'm' => ':countمياشت|:countمياشتي', + 'week' => ':count اونۍ|:count اونۍ', + 'w' => ':countاونۍ|:countاونۍ', + 'day' => ':count ورځ|:count ورځي', + 'd' => ':countورځ|:countورځي', + 'hour' => ':count ساعت|:count ساعته', + 'h' => ':countساعت|:countساعته', + 'minute' => ':count دقيقه|:count دقيقې', + 'min' => ':countدقيقه|:countدقيقې', + 'second' => ':count ثانيه|:count ثانيې', + 's' => ':countثانيه|:countثانيې', + 'ago' => ':time دمخه', + 'from_now' => ':time له اوس څخه', + 'after' => ':time وروسته', + 'before' => ':time دمخه', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/pt.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/pt.php new file mode 100644 index 0000000..392b121 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/pt.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count ano|:count anos', + 'y' => ':count ano|:count anos', + 'month' => ':count mês|:count meses', + 'm' => ':count mês|:count meses', + 'week' => ':count semana|:count semanas', + 'w' => ':count semana|:count semanas', + 'day' => ':count dia|:count dias', + 'd' => ':count dia|:count dias', + 'hour' => ':count hora|:count horas', + 'h' => ':count hora|:count horas', + 'minute' => ':count minuto|:count minutos', + 'min' => ':count minuto|:count minutos', + 'second' => ':count segundo|:count segundos', + 's' => ':count segundo|:count segundos', + 'ago' => ':time atrás', + 'from_now' => 'em :time', + 'after' => ':time depois', + 'before' => ':time antes', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php new file mode 100644 index 0000000..1f84eac --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count ano|:count anos', + 'y' => ':counta|:counta', + 'month' => ':count mês|:count meses', + 'm' => ':countm|:countm', + 'week' => ':count semana|:count semanas', + 'w' => ':countsem|:countsem', + 'day' => ':count dia|:count dias', + 'd' => ':countd|:countd', + 'hour' => ':count hora|:count horas', + 'h' => ':counth|:counth', + 'minute' => ':count minuto|:count minutos', + 'min' => ':countmin|:countmin', + 'second' => ':count segundo|:count segundos', + 's' => ':counts|:counts', + 'ago' => 'há :time', + 'from_now' => 'em :time', + 'after' => 'após :time', + 'before' => ':time atrás', + 'diff_now' => 'agora', + 'diff_yesterday' => 'ontem', + 'diff_tomorrow' => 'amanhã', + 'diff_before_yesterday' => 'anteontem', + 'diff_after_tomorrow' => 'depois de amanhã', + 'period_recurrences' => 'uma|:count vez', + 'period_interval' => 'toda :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'até :date', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ro.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ro.php new file mode 100644 index 0000000..cc16724 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ro.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => 'un an|:count ani|:count ani', + 'y' => 'un an|:count ani|:count ani', + 'month' => 'o lună|:count luni|:count luni', + 'm' => 'o lună|:count luni|:count luni', + 'week' => 'o săptămână|:count săptămâni|:count săptămâni', + 'w' => 'o săptămână|:count săptămâni|:count săptămâni', + 'day' => 'o zi|:count zile|:count zile', + 'd' => 'o zi|:count zile|:count zile', + 'hour' => 'o oră|:count ore|:count ore', + 'h' => 'o oră|:count ore|:count ore', + 'minute' => 'un minut|:count minute|:count minute', + 'min' => 'un minut|:count minute|:count minute', + 'second' => 'o secundă|:count secunde|:count secunde', + 's' => 'o secundă|:count secunde|:count secunde', + 'ago' => 'acum :time', + 'from_now' => ':time de acum', + 'after' => 'peste :time', + 'before' => 'acum :time', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ru.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ru.php new file mode 100644 index 0000000..6a83fb1 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ru.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count год|:count года|:count лет', + 'y' => ':count г|:count г|:count л', + 'month' => ':count месяц|:count месяца|:count месяцев', + 'm' => ':count м|:count м|:count м', + 'week' => ':count неделю|:count недели|:count недель', + 'w' => ':count н|:count н|:count н', + 'day' => ':count день|:count дня|:count дней', + 'd' => ':count д|:count д|:count д', + 'hour' => ':count час|:count часа|:count часов', + 'h' => ':count ч|:count ч|:count ч', + 'minute' => ':count минуту|:count минуты|:count минут', + 'min' => ':count мин|:count мин|:count мин', + 'second' => ':count секунду|:count секунды|:count секунд', + 's' => ':count с|:count с|:count с', + 'ago' => ':time назад', + 'from_now' => 'через :time', + 'after' => ':time после', + 'before' => ':time до', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sh.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sh.php new file mode 100644 index 0000000..57f287a --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sh.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +\Symfony\Component\Translation\PluralizationRules::set(function ($number) { + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); +}, 'sh'); + +return array( + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count godina|:count godine|:count godina', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count mesec|:count meseca|:count meseci', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count nedelja|:count nedelje|:count nedelja', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count dan|:count dana|:count dana', + 'hour' => ':count čas|:count časa|:count časova', + 'h' => ':count čas|:count časa|:count časova', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count minut|:count minuta|:count minuta', + 'second' => ':count sekund|:count sekunda|:count sekundi', + 's' => ':count sekund|:count sekunda|:count sekundi', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time raniјe', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sk.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sk.php new file mode 100644 index 0000000..6101344 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sk.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => 'rok|:count roky|:count rokov', + 'y' => 'rok|:count roky|:count rokov', + 'month' => 'mesiac|:count mesiace|:count mesiacov', + 'm' => 'mesiac|:count mesiace|:count mesiacov', + 'week' => 'týždeň|:count týždne|:count týždňov', + 'w' => 'týždeň|:count týždne|:count týždňov', + 'day' => 'deň|:count dni|:count dní', + 'd' => 'deň|:count dni|:count dní', + 'hour' => 'hodinu|:count hodiny|:count hodín', + 'h' => 'hodinu|:count hodiny|:count hodín', + 'minute' => 'minútu|:count minúty|:count minút', + 'min' => 'minútu|:count minúty|:count minút', + 'second' => 'sekundu|:count sekundy|:count sekúnd', + 's' => 'sekundu|:count sekundy|:count sekúnd', + 'ago' => 'pred :time', + 'from_now' => 'za :time', + 'after' => 'o :time neskôr', + 'before' => ':time predtým', + 'year_ago' => 'rokom|:count rokmi|:count rokmi', + 'month_ago' => 'mesiacom|:count mesiacmi|:count mesiacmi', + 'week_ago' => 'týždňom|:count týždňami|:count týždňami', + 'day_ago' => 'dňom|:count dňami|:count dňami', + 'hour_ago' => 'hodinou|:count hodinami|:count hodinami', + 'minute_ago' => 'minútou|:count minútami|:count minútami', + 'second_ago' => 'sekundou|:count sekundami|:count sekundami', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sl.php new file mode 100644 index 0000000..06686d1 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sl.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count leto|:count leti|:count leta|:count let', + 'y' => ':count leto|:count leti|:count leta|:count let', + 'month' => ':count mesec|:count meseca|:count mesece|:count mesecev', + 'm' => ':count mesec|:count meseca|:count mesece|:count mesecev', + 'week' => ':count teden|:count tedna|:count tedne|:count tednov', + 'w' => ':count teden|:count tedna|:count tedne|:count tednov', + 'day' => ':count dan|:count dni|:count dni|:count dni', + 'd' => ':count dan|:count dni|:count dni|:count dni', + 'hour' => ':count uro|:count uri|:count ure|:count ur', + 'h' => ':count uro|:count uri|:count ure|:count ur', + 'minute' => ':count minuto|:count minuti|:count minute|:count minut', + 'min' => ':count minuto|:count minuti|:count minute|:count minut', + 'second' => ':count sekundo|:count sekundi|:count sekunde|:count sekund', + 's' => ':count sekundo|:count sekundi|:count sekunde|:count sekund', + 'year_ago' => ':count letom|:count leti|:count leti|:count leti', + 'month_ago' => ':count mesecem|:count meseci|:count meseci|:count meseci', + 'week_ago' => ':count tednom|:count tednoma|:count tedni|:count tedni', + 'day_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi', + 'hour_ago' => ':count uro|:count urama|:count urami|:count urami', + 'minute_ago' => ':count minuto|:count minutama|:count minutami|:count minutami', + 'second_ago' => ':count sekundo|:count sekundama|:count sekundami|:count sekundami', + 'ago' => 'pred :time', + 'from_now' => 'čez :time', + 'after' => 'čez :time', + 'before' => 'pred :time', + 'diff_now' => 'ravnokar', + 'diff_yesterday' => 'včeraj', + 'diff_tomorrow' => 'jutri', + 'diff_before_yesterday' => 'predvčerajšnjim', + 'diff_after_tomorrow' => 'pojutrišnjem', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sq.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sq.php new file mode 100644 index 0000000..6e138a0 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sq.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count vit|:count vjet', + 'y' => ':count vit|:count vjet', + 'month' => ':count muaj|:count muaj', + 'm' => ':count muaj|:count muaj', + 'week' => ':count javë|:count javë', + 'w' => ':count javë|:count javë', + 'day' => ':count ditë|:count ditë', + 'd' => ':count ditë|:count ditë', + 'hour' => ':count orë|:count orë', + 'h' => ':count orë|:count orë', + 'minute' => ':count minutë|:count minuta', + 'min' => ':count minutë|:count minuta', + 'second' => ':count sekondë|:count sekonda', + 's' => ':count sekondë|:count sekonda', + 'ago' => ':time më parë', + 'from_now' => ':time nga tani', + 'after' => ':time pas', + 'before' => ':time para', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr.php new file mode 100644 index 0000000..5a10642 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count godina|:count godine|:count godina', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count mesec|:count meseca|:count meseci', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count nedelja|:count nedelje|:count nedelja', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count dan|:count dana|:count dana', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minut|:count minuta |:count minuta', + 'min' => ':count minut|:count minuta |:count minuta', + 'second' => ':count sekund|:count sekunde|:count sekunde', + 's' => ':count sekund|:count sekunde|:count sekunde', + 'ago' => 'pre :time', + 'from_now' => ':time od sada', + 'after' => 'nakon :time', + 'before' => 'pre :time', + + 'year_from_now' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina', + 'year_ago' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina', + + 'week_from_now' => '{1} :count nedelju|{2,3,4} :count nedelje|[5,Inf[ :count nedelja', + 'week_ago' => '{1} :count nedelju|{2,3,4} :count nedelje|[5,Inf[ :count nedelja', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php new file mode 100644 index 0000000..2db83ed --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[ :count година', + 'y' => ':count г.', + 'month' => '{1} :count месец|{2,3,4}:count месеца|[5,Inf[ :count месеци', + 'm' => ':count м.', + 'week' => '{1} :count недеља|{2,3,4}:count недеље|[5,Inf[ :count недеља', + 'w' => ':count нед.', + 'day' => '{1,21,31} :count дан|[2,Inf[ :count дана', + 'd' => ':count д.', + 'hour' => '{1,21} :count сат|{2,3,4,22,23,24}:count сата|[5,Inf[ :count сати', + 'h' => ':count ч.', + 'minute' => '{1,21,31,41,51} :count минут|[2,Inf[ :count минута', + 'min' => ':count мин.', + 'second' => '{1,21,31,41,51} :count секунд|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count секунде|[5,Inf[:count секунди', + 's' => ':count сек.', + 'ago' => 'пре :time', + 'from_now' => 'за :time', + 'after' => ':time након', + 'before' => ':time пре', + + 'year_from_now' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година', + 'year_ago' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година', + + 'week_from_now' => '{1} :count недељу|{2,3,4} :count недеље|[5,Inf[ :count недеља', + 'week_ago' => '{1} :count недељу|{2,3,4} :count недеље|[5,Inf[ :count недеља', + + 'diff_now' => 'управо сада', + 'diff_yesterday' => 'јуче', + 'diff_tomorrow' => 'сутра', + 'diff_before_yesterday' => 'прекјуче', + 'diff_after_tomorrow' => 'прекосутра', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php new file mode 100644 index 0000000..18214c4 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[ :count година', + 'y' => ':count г.', + 'month' => '{1} :count мјесец|{2,3,4}:count мјесеца|[5,Inf[ :count мјесеци', + 'm' => ':count мј.', + 'week' => '{1} :count недјеља|{2,3,4}:count недјеље|[5,Inf[ :count недјеља', + 'w' => ':count нед.', + 'day' => '{1,21,31} :count дан|[2,Inf[ :count дана', + 'd' => ':count д.', + 'hour' => '{1,21} :count сат|{2,3,4,22,23,24}:count сата|[5,Inf[ :count сати', + 'h' => ':count ч.', + 'minute' => '{1,21,31,41,51} :count минут|[2,Inf[ :count минута', + 'min' => ':count мин.', + 'second' => '{1,21,31,41,51} :count секунд|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count секунде|[5,Inf[:count секунди', + 's' => ':count сек.', + 'ago' => 'прије :time', + 'from_now' => 'за :time', + 'after' => ':time након', + 'before' => ':time прије', + + 'year_from_now' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година', + 'year_ago' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година', + + 'week_from_now' => '{1} :count недјељу|{2,3,4} :count недјеље|[5,Inf[ :count недјеља', + 'week_ago' => '{1} :count недјељу|{2,3,4} :count недјеље|[5,Inf[ :count недјеља', + + 'diff_now' => 'управо сада', + 'diff_yesterday' => 'јуче', + 'diff_tomorrow' => 'сутра', + 'diff_before_yesterday' => 'прекјуче', + 'diff_after_tomorrow' => 'прекосјутра', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php new file mode 100644 index 0000000..2d2e288 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count godine|[0,Inf[ :count godina', + 'y' => ':count g.', + 'month' => '{1} :count mjesec|{2,3,4}:count mjeseca|[5,Inf[ :count mjeseci', + 'm' => ':count mj.', + 'week' => '{1} :count nedjelja|{2,3,4}:count nedjelje|[5,Inf[ :count nedjelja', + 'w' => ':count ned.', + 'day' => '{1,21,31} :count dan|[2,Inf[ :count dana', + 'd' => ':count d.', + 'hour' => '{1,21} :count sat|{2,3,4,22,23,24}:count sata|[5,Inf[ :count sati', + 'h' => ':count č.', + 'minute' => '{1,21,31,41,51} :count minut|[2,Inf[ :count minuta', + 'min' => ':count min.', + 'second' => '{1,21,31,41,51} :count sekund|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count sekunde|[5,Inf[:count sekundi', + 's' => ':count sek.', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time nakon', + 'before' => ':time prije', + + 'year_from_now' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina', + 'year_ago' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina', + + 'week_from_now' => '{1} :count nedjelju|{2,3,4} :count nedjelje|[5,Inf[ :count nedjelja', + 'week_ago' => '{1} :count nedjelju|{2,3,4} :count nedjelje|[5,Inf[ :count nedjelja', + + 'diff_now' => 'upravo sada', + 'diff_yesterday' => 'juče', + 'diff_tomorrow' => 'sutra', + 'diff_before_yesterday' => 'prekjuče', + 'diff_after_tomorrow' => 'preksutra', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php new file mode 100644 index 0000000..7ebf6f0 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr_Latn_ME.php'; diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sv.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sv.php new file mode 100644 index 0000000..89a03b4 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sv.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count månad|:count månader', + 'm' => ':count månad|:count månader', + 'week' => ':count vecka|:count veckor', + 'w' => ':count vecka|:count veckor', + 'day' => ':count dag|:count dagar', + 'd' => ':count dag|:count dagar', + 'hour' => ':count timme|:count timmar', + 'h' => ':count timme|:count timmar', + 'minute' => ':count minut|:count minuter', + 'min' => ':count minut|:count minuter', + 'second' => ':count sekund|:count sekunder', + 's' => ':count sekund|:count sekunder', + 'ago' => ':time sedan', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time före', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sw.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sw.php new file mode 100644 index 0000000..52f0342 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sw.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => 'mwaka 1|miaka :count', + 'y' => 'mwaka 1|miaka :count', + 'month' => 'mwezi 1|miezi :count', + 'm' => 'mwezi 1|miezi :count', + 'week' => 'wiki 1|wiki :count', + 'w' => 'wiki 1|wiki :count', + 'day' => 'siku 1|siku :count', + 'd' => 'siku 1|siku :count', + 'hour' => 'saa 1|masaa :count', + 'h' => 'saa 1|masaa :count', + 'minute' => 'dakika 1|dakika :count', + 'min' => 'dakika 1|dakika :count', + 'second' => 'sekunde 1|sekunde :count', + 's' => 'sekunde 1|sekunde :count', + 'ago' => ':time ziliyopita', + 'from_now' => ':time kwanzia sasa', + 'after' => ':time baada', + 'before' => ':time kabla', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/th.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/th.php new file mode 100644 index 0000000..88bb4ac --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/th.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count ปี', + 'y' => ':count ปี', + 'month' => ':count เดือน', + 'm' => ':count เดือน', + 'week' => ':count สัปดาห์', + 'w' => ':count สัปดาห์', + 'day' => ':count วัน', + 'd' => ':count วัน', + 'hour' => ':count ชั่วโมง', + 'h' => ':count ชั่วโมง', + 'minute' => ':count นาที', + 'min' => ':count นาที', + 'second' => ':count วินาที', + 's' => ':count วินาที', + 'ago' => ':timeที่แล้ว', + 'from_now' => ':timeต่อจากนี้', + 'after' => ':timeหลังจากนี้', + 'before' => ':timeก่อน', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/tr.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/tr.php new file mode 100644 index 0000000..6a9dfed --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/tr.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count yıl', + 'y' => ':count yıl', + 'month' => ':count ay', + 'm' => ':count ay', + 'week' => ':count hafta', + 'w' => ':count hafta', + 'day' => ':count gün', + 'd' => ':count gün', + 'hour' => ':count saat', + 'h' => ':count saat', + 'minute' => ':count dakika', + 'min' => ':count dakika', + 'second' => ':count saniye', + 's' => ':count saniye', + 'ago' => ':time önce', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time önce', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/uk.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/uk.php new file mode 100644 index 0000000..8d08eaa --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/uk.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count рік|:count роки|:count років', + 'y' => ':count рік|:count роки|:count років', + 'month' => ':count місяць|:count місяці|:count місяців', + 'm' => ':count місяць|:count місяці|:count місяців', + 'week' => ':count тиждень|:count тижні|:count тижнів', + 'w' => ':count тиждень|:count тижні|:count тижнів', + 'day' => ':count день|:count дні|:count днів', + 'd' => ':count день|:count дні|:count днів', + 'hour' => ':count година|:count години|:count годин', + 'h' => ':count година|:count години|:count годин', + 'minute' => ':count хвилину|:count хвилини|:count хвилин', + 'min' => ':count хвилину|:count хвилини|:count хвилин', + 'second' => ':count секунду|:count секунди|:count секунд', + 's' => ':count секунду|:count секунди|:count секунд', + 'ago' => ':time тому', + 'from_now' => 'через :time', + 'after' => ':time після', + 'before' => ':time до', + 'diff_now' => 'щойно', + 'diff_yesterday' => 'вчора', + 'diff_tomorrow' => 'завтра', + 'diff_before_yesterday' => 'позавчора', + 'diff_after_tomorrow' => 'післязавтра', + 'period_recurrences' => 'один раз|:count рази|:count разів', + 'period_interval' => 'кожні :interval', + 'period_start_date' => 'з :date', + 'period_end_date' => 'до :date', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ur.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ur.php new file mode 100644 index 0000000..3c5f7ed --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ur.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count سال', + 'month' => ':count ماه', + 'week' => ':count ہفتے', + 'day' => ':count روز', + 'hour' => ':count گھنٹے', + 'minute' => ':count منٹ', + 'second' => ':count سیکنڈ', + 'ago' => ':time پہلے', + 'from_now' => ':time بعد', + 'after' => ':time بعد', + 'before' => ':time پہلے', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/uz.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/uz.php new file mode 100644 index 0000000..1cb6f71 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/uz.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count yil', + 'y' => ':count yil', + 'month' => ':count oy', + 'm' => ':count oy', + 'week' => ':count hafta', + 'w' => ':count hafta', + 'day' => ':count kun', + 'd' => ':count kun', + 'hour' => ':count soat', + 'h' => ':count soat', + 'minute' => ':count daqiqa', + 'min' => ':count daq', + 'second' => ':count soniya', + 's' => ':count s', + 'ago' => ':time avval', + 'from_now' => ':time dan keyin', + 'after' => ':time keyin', + 'before' => ':time oldin', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/vi.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/vi.php new file mode 100644 index 0000000..3f9838d --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/vi.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count năm', + 'y' => ':count năm', + 'month' => ':count tháng', + 'm' => ':count tháng', + 'week' => ':count tuần', + 'w' => ':count tuần', + 'day' => ':count ngày', + 'd' => ':count ngày', + 'hour' => ':count giờ', + 'h' => ':count giờ', + 'minute' => ':count phút', + 'min' => ':count phút', + 'second' => ':count giây', + 's' => ':count giây', + 'ago' => ':time trước', + 'from_now' => ':time từ bây giờ', + 'after' => ':time sau', + 'before' => ':time trước', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/zh.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/zh.php new file mode 100644 index 0000000..9e1f6ca --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/zh.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count年', + 'y' => ':count年', + 'month' => ':count个月', + 'm' => ':count个月', + 'week' => ':count周', + 'w' => ':count周', + 'day' => ':count天', + 'd' => ':count天', + 'hour' => ':count小时', + 'h' => ':count小时', + 'minute' => ':count分钟', + 'min' => ':count分钟', + 'second' => ':count秒', + 's' => ':count秒', + 'ago' => ':time前', + 'from_now' => '距现在:time', + 'after' => ':time后', + 'before' => ':time前', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php new file mode 100644 index 0000000..c848723 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'year' => ':count年', + 'y' => ':count年', + 'month' => ':count月', + 'm' => ':count月', + 'week' => ':count週', + 'w' => ':count週', + 'day' => ':count天', + 'd' => ':count天', + 'hour' => ':count小時', + 'h' => ':count小時', + 'minute' => ':count分鐘', + 'min' => ':count分鐘', + 'second' => ':count秒', + 's' => ':count秒', + 'ago' => ':time前', + 'from_now' => '距現在:time', + 'after' => ':time後', + 'before' => ':time前', +); diff --git a/core/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php b/core/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php new file mode 100644 index 0000000..4d83b0c --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php @@ -0,0 +1,37 @@ +app['events']; + if ($events instanceof EventDispatcher || $events instanceof Dispatcher) { + $events->listen(class_exists('Illuminate\Foundation\Events\LocaleUpdated') ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed', function () use ($service) { + $service->updateLocale(); + }); + $service->updateLocale(); + } + } + + public function updateLocale() + { + $translator = $this->app['translator']; + if ($translator instanceof Translator || $translator instanceof IlluminateTranslator) { + Carbon::setLocale($translator->getLocale()); + } + } + + public function register() + { + // Needed for Laravel < 5.3 compatibility + } +} diff --git a/core/vendor/nesbot/carbon/src/Carbon/Translator.php b/core/vendor/nesbot/carbon/src/Carbon/Translator.php new file mode 100644 index 0000000..12115b0 --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Translator.php @@ -0,0 +1,143 @@ +addLoader('array', new Translation\Loader\ArrayLoader()); + parent::__construct($locale, $formatter, $cacheDir, $debug); + } + + /** + * Reset messages of a locale (all locale if no locale passed). + * Remove custom messages and reload initial messages from matching + * file in Lang directory. + * + * @param string|null $locale + * + * @return bool + */ + public function resetMessages($locale = null) + { + if ($locale === null) { + static::$messages = array(); + + return true; + } + + if (file_exists($filename = __DIR__.'/Lang/'.$locale.'.php')) { + static::$messages[$locale] = require $filename; + $this->addResource('array', static::$messages[$locale], $locale); + + return true; + } + + return false; + } + + /** + * Init messages language from matching file in Lang directory. + * + * @param string $locale + * + * @return bool + */ + protected function loadMessagesFromFile($locale) + { + if (isset(static::$messages[$locale])) { + return true; + } + + return $this->resetMessages($locale); + } + + /** + * Set messages of a locale and take file first if present. + * + * @param string $locale + * @param array $messages + * + * @return $this + */ + public function setMessages($locale, $messages) + { + $this->loadMessagesFromFile($locale); + $this->addResource('array', $messages, $locale); + static::$messages[$locale] = array_merge( + isset(static::$messages[$locale]) ? static::$messages[$locale] : array(), + $messages + ); + + return $this; + } + + /** + * Get messages of a locale, if none given, return all the + * languages. + * + * @param string|null $locale + * + * @return array + */ + public function getMessages($locale = null) + { + return $locale === null ? static::$messages : static::$messages[$locale]; + } + + /** + * Set the current translator locale and indicate if the source locale file exists + * + * @param string $locale locale ex. en + * + * @return bool + */ + public function setLocale($locale) + { + $locale = preg_replace_callback('/[-_]([a-z]{2,})/', function ($matches) { + // _2-letters is a region, _3+-letters is a variant + return '_'.call_user_func(strlen($matches[1]) > 2 ? 'ucfirst' : 'strtoupper', $matches[1]); + }, strtolower($locale)); + + if ($this->loadMessagesFromFile($locale)) { + parent::setLocale($locale); + + return true; + } + + return false; + } +} diff --git a/core/vendor/nesbot/carbon/src/Carbon/Upgrade.php b/core/vendor/nesbot/carbon/src/Carbon/Upgrade.php new file mode 100644 index 0000000..26449ff --- /dev/null +++ b/core/vendor/nesbot/carbon/src/Carbon/Upgrade.php @@ -0,0 +1,150 @@ + '5.8.0', + 'laravel/cashier' => '9.0.1', + 'illuminate/support' => '5.8.0', + 'laravel/dusk' => '5.0.0', + ); + + protected static $otherLibraries = array( + 'spatie/laravel-analytics' => '3.6.4', + 'jenssegers/date' => '3.5.0', + ); + + /** + * @param \UpdateHelper\UpdateHelper $helper + */ + public function check(UpdateHelper $helper) + { + $helper->write(array( + 'Carbon 1 is deprecated, see how to migrate to Carbon 2.', + 'https://carbon.nesbot.com/docs/#api-carbon-2', + )); + + if (static::SUGGEST_ON_UPDATE || static::ASK_ON_UPDATE || $helper->getIo()->isVerbose()) { + $laravelUpdate = array(); + + foreach (static::$laravelLibraries as $name => $version) { + if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) { + $laravelUpdate[$name] = $version; + } + } + + if (count($laravelUpdate)) { + $output = array( + ' Please consider upgrading your Laravel dependencies to be compatible with Carbon 2:', + ); + + foreach ($laravelUpdate as $name => $version) { + $output[] = " - $name at least to version $version"; + } + + $output[] = ''; + $output[] = " If you can't update Laravel, check https://carbon.nesbot.com/ to see how to"; + $output[] = ' install Carbon 2 using alias version and our adapter kylekatarnls/laravel-carbon-2'; + $output[] = ''; + + $helper->write($output); + } + + foreach (static::$otherLibraries as $name => $version) { + if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) { + $helper->write(" Please consider upgrading $name at least to $version to be compatible with Carbon 2.\n"); + } + } + + if (static::ASK_ON_UPDATE) { + static::askForUpgrade($helper); + + return; + } + } + + $path = implode(DIRECTORY_SEPARATOR, array('.', 'vendor', 'bin', 'upgrade-carbon')); + + if (!file_exists($path)) { + $path = realpath(__DIR__.'/../../bin/upgrade-carbon'); + } + + $helper->write( + ' You can run '.escapeshellarg($path). + ' to get help in updating carbon and other frameworks and libraries that depend on it.' + ); + } + + private static function getUpgradeQuestion($upgrades) + { + $message = "Do you want us to try the following upgrade:\n"; + + foreach ($upgrades as $name => $version) { + $message .= " - $name: $version\n"; + } + + return $message.'[Y/N] '; + } + + public static function askForUpgrade(UpdateHelper $helper, $upgradeIfNotInteractive = false) + { + $upgrades = array( + 'nesbot/carbon' => '^2.0.0', + ); + + foreach (array(static::$laravelLibraries, static::$otherLibraries) as $libraries) { + foreach ($libraries as $name => $version) { + if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) { + $upgrades[$name] = "^$version"; + } + } + } + + $shouldUpgrade = $helper->isInteractive() + ? $helper->getIo()->askConfirmation(static::getUpgradeQuestion($upgrades)) + : $upgradeIfNotInteractive; + + if ($shouldUpgrade) { + $helper->setDependencyVersions($upgrades)->update(); + } + } + + public static function upgrade(ScriptEvent $event = null) + { + if (!$event) { + $composer = new Composer(); + $baseDir = __DIR__.'/../..'; + + if (file_exists("$baseDir/autoload.php")) { + $baseDir .= '/..'; + } + + $composer->setConfig(new Config(true, $baseDir)); + $event = new ScriptEvent( + 'upgrade-carbon', + $composer, + new ConsoleIO(new StringInput(''), new ConsoleOutput(), new HelperSet(array( + new QuestionHelper(), + ))) + ); + } + + static::askForUpgrade(new UpdateHelper($event), true); + } +} diff --git a/core/vendor/nesbot/carbon/src/JsonSerializable.php b/core/vendor/nesbot/carbon/src/JsonSerializable.php new file mode 100644 index 0000000..d34060b --- /dev/null +++ b/core/vendor/nesbot/carbon/src/JsonSerializable.php @@ -0,0 +1,18 @@ +json_encode, + * which is a value of any type other than a resource. + * + * @since 5.4.0 + */ + public function jsonSerialize(); + } +} diff --git a/core/vendor/nikic/php-parser/.gitignore b/core/vendor/nikic/php-parser/.gitignore new file mode 100644 index 0000000..8c7db2a --- /dev/null +++ b/core/vendor/nikic/php-parser/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +grammar/kmyacc.exe +grammar/y.output diff --git a/core/vendor/nikic/php-parser/.travis.yml b/core/vendor/nikic/php-parser/.travis.yml new file mode 100644 index 0000000..4716915 --- /dev/null +++ b/core/vendor/nikic/php-parser/.travis.yml @@ -0,0 +1,32 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - nightly + - hhvm + +install: + - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then composer require satooshi/php-coveralls '~1.0'; fi + - composer install --prefer-dist + +matrix: + allow_failures: + - php: nightly + fast_finish: true + +script: + - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then vendor/bin/phpunit --coverage-clover build/logs/clover.xml; else vendor/bin/phpunit; fi + - if [ $TRAVIS_PHP_VERSION = '7.0' ]; then test_old/run-php-src.sh; fi + +after_success: + if [ $TRAVIS_PHP_VERSION = '5.6' ]; then php vendor/bin/coveralls; fi + diff --git a/core/vendor/nikic/php-parser/CHANGELOG.md b/core/vendor/nikic/php-parser/CHANGELOG.md new file mode 100644 index 0000000..0ae6b7e --- /dev/null +++ b/core/vendor/nikic/php-parser/CHANGELOG.md @@ -0,0 +1,151 @@ +Version 2.1.2-dev +----------------- + +Nothing yet. + +Version 2.1.1 (2016-09-16) +-------------------------- + +### Changed + +* The pretty printer will now escape all control characters in the range `\x00-\x1F` inside double + quoted strings. If no special escape sequence is available, an octal escape will be used. +* The quality of the error recovery has been improved. In particular unterminated expressions should + be handled more gracefully. +* The PHP 7 parser will now generate a parse error for `$var =& new Obj` assignments. +* Comments on free-standing code blocks will no be retained as comments on the first statement in + the code block. + +Version 2.1.0 (2016-04-19) +-------------------------- + +### Fixed + +* Properly support `B""` strings (with uppercase `B`) in a number of places. +* Fixed reformatting of indented parts in a certain non-standard comment style. + +### Added + +* Added `dumpComments` option to node dumper, to enable dumping of comments associated with nodes. +* Added `Stmt\Nop` node, that is used to collect comments located at the end of a block or at the + end of a file (without a following node with which they could otherwise be associated). +* Added `kind` attribute to `Expr\Exit` to distinguish between `exit` and `die`. +* Added `kind` attribute to `Scalar\LNumber` to distinguish between decimal, binary, octal and + hexadecimal numbers. +* Added `kind` attribtue to `Expr\Array` to distinguish between `array()` and `[]`. +* Added `kind` attribute to `Scalar\String` and `Scalar\Encapsed` to distinguish between + single-quoted, double-quoted, heredoc and nowdoc string. +* Added `docLabel` attribute to `Scalar\String` and `Scalar\Encapsed`, if it is a heredoc or + nowdoc string. +* Added start file offset information to `Comment` nodes. +* Added `setReturnType()` method to function and method builders. +* Added `-h` and `--help` options to `php-parse` script. + +### Changed + +* Invalid octal literals now throw a parse error in PHP 7 mode. +* The pretty printer takes all the new attributes mentioned in the previous section into account. +* The protected `AbstractPrettyPrinter::pComments()` method no longer returns a trailing newline. +* The bundled autoloader supports library files being stored in a different directory than + `PhpParser` for easier downstream distribution. + +### Deprecated + +* The `Comment::setLine()` and `Comment::setText()` methods have been deprecated. Construct new + objects instead. + +### Removed + +* The internal (but public) method `Scalar\LNumber::parse()` has been removed. A non-internal + `LNumber::fromString()` method has been added instead. + +Version 2.0.1 (2016-02-28) +-------------------------- + +### Fixed + +* `declare() {}` and `declare();` are not semantically equivalent and will now result in different + ASTs. The format case will have an empty `stmts` array, while the latter will set `stmts` to + `null`. +* Magic constants are now supported as semi-reserved keywords. +* A shebang line like `#!/usr/bin/env php` is now allowed at the start of a namespaced file. + Previously this generated an exception. +* The `prettyPrintFile()` method will not strip a trailing `?>` from the raw data that follows a + `__halt_compiler()` statement. +* The `prettyPrintFile()` method will not strip an opening `slice()` which takes a subslice of a name. + +### Changed + +* `PhpParser\Parser` is now an interface, implemented by `Parser\Php5`, `Parser\Php7` and + `Parser\Multiple`. The `Multiple` parser will try multiple parsers, until one succeeds. +* Token constants are now defined on `PhpParser\Parser\Tokens` rather than `PhpParser\Parser`. +* The `Name->set()`, `Name->append()`, `Name->prepend()` and `Name->setFirst()` methods are + deprecated in favor of `Name::concat()` and `Name->slice()`. +* The `NodeTraverser` no longer clones nodes by default. The old behavior can be restored by + passing `true` to the constructor. +* The constructor for `Scalar` nodes no longer has a default value. E.g. `new LNumber()` should now + be written as `new LNumber(0)`. + +--- + +**This changelog only includes changes from the 2.0 series. For older changes see the +[1.x series changelog](https://github.com/nikic/PHP-Parser/blob/1.x/CHANGELOG.md) and the +[0.9 series changelog](https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md).** \ No newline at end of file diff --git a/core/vendor/nikic/php-parser/LICENSE b/core/vendor/nikic/php-parser/LICENSE new file mode 100644 index 0000000..443210b --- /dev/null +++ b/core/vendor/nikic/php-parser/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2011 by Nikita Popov. + +Some 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. + + * The names of the contributors may not 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/core/vendor/nikic/php-parser/README.md b/core/vendor/nikic/php-parser/README.md new file mode 100644 index 0000000..28c565c --- /dev/null +++ b/core/vendor/nikic/php-parser/README.md @@ -0,0 +1,96 @@ +PHP Parser +========== + +[![Build Status](https://travis-ci.org/nikic/PHP-Parser.svg?branch=master)](https://travis-ci.org/nikic/PHP-Parser) [![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master) + +This is a PHP 5.2 to PHP 7.0 parser written in PHP. Its purpose is to simplify static code analysis and +manipulation. + +[**Documentation for version 2.x**][doc_master] (stable; for running on PHP >= 5.4; for parsing PHP 5.2 to PHP 7.0). + +[Documentation for version 1.x][doc_1_x] (unsupported; for running on PHP >= 5.3; for parsing PHP 5.2 to PHP 5.6). + +In a Nutshell +------------- + +The parser turns PHP source code into an abstract syntax tree. For example, if you pass the following code into the +parser: + +```php + Expr_AssignOp_BitwiseAnd +Expr_AssignBitwiseOr => Expr_AssignOp_BitwiseOr +Expr_AssignBitwiseXor => Expr_AssignOp_BitwiseXor +Expr_AssignConcat => Expr_AssignOp_Concat +Expr_AssignDiv => Expr_AssignOp_Div +Expr_AssignMinus => Expr_AssignOp_Minus +Expr_AssignMod => Expr_AssignOp_Mod +Expr_AssignMul => Expr_AssignOp_Mul +Expr_AssignPlus => Expr_AssignOp_Plus +Expr_AssignShiftLeft => Expr_AssignOp_ShiftLeft +Expr_AssignShiftRight => Expr_AssignOp_ShiftRight + +Expr_BitwiseAnd => Expr_BinaryOp_BitwiseAnd +Expr_BitwiseOr => Expr_BinaryOp_BitwiseOr +Expr_BitwiseXor => Expr_BinaryOp_BitwiseXor +Expr_BooleanAnd => Expr_BinaryOp_BooleanAnd +Expr_BooleanOr => Expr_BinaryOp_BooleanOr +Expr_Concat => Expr_BinaryOp_Concat +Expr_Div => Expr_BinaryOp_Div +Expr_Equal => Expr_BinaryOp_Equal +Expr_Greater => Expr_BinaryOp_Greater +Expr_GreaterOrEqual => Expr_BinaryOp_GreaterOrEqual +Expr_Identical => Expr_BinaryOp_Identical +Expr_LogicalAnd => Expr_BinaryOp_LogicalAnd +Expr_LogicalOr => Expr_BinaryOp_LogicalOr +Expr_LogicalXor => Expr_BinaryOp_LogicalXor +Expr_Minus => Expr_BinaryOp_Minus +Expr_Mod => Expr_BinaryOp_Mod +Expr_Mul => Expr_BinaryOp_Mul +Expr_NotEqual => Expr_BinaryOp_NotEqual +Expr_NotIdentical => Expr_BinaryOp_NotIdentical +Expr_Plus => Expr_BinaryOp_Plus +Expr_ShiftLeft => Expr_BinaryOp_ShiftLeft +Expr_ShiftRight => Expr_BinaryOp_ShiftRight +Expr_Smaller => Expr_BinaryOp_Smaller +Expr_SmallerOrEqual => Expr_BinaryOp_SmallerOrEqual + +Scalar_ClassConst => Scalar_MagicConst_Class +Scalar_DirConst => Scalar_MagicConst_Dir +Scalar_FileConst => Scalar_MagicConst_File +Scalar_FuncConst => Scalar_MagicConst_Function +Scalar_LineConst => Scalar_MagicConst_Line +Scalar_MethodConst => Scalar_MagicConst_Method +Scalar_NSConst => Scalar_MagicConst_Namespace +Scalar_TraitConst => Scalar_MagicConst_Trait +``` + +These changes may affect custom pretty printers and code comparing the return value of `Node::getType()` to specific +strings. + +### Miscellaneous + + * The classes `Template` and `TemplateLoader` have been removed. You should use some other [code generation][code_gen] + project built on top of PHP-Parser instead. + + * The `PrettyPrinterAbstract::pStmts()` method now emits a leading newline if the statement list is not empty. + Custom pretty printers should remove the explicit newline before `pStmts()` calls. + + Old: + + ```php + public function pStmt_Trait(PHPParser_Node_Stmt_Trait $node) { + return 'trait ' . $node->name + . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; + } + ``` + + New: + + ```php + public function pStmt_Trait(Stmt\Trait_ $node) { + return 'trait ' . $node->name + . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}'; + } + ``` + + [code_gen]: https://github.com/nikic/PHP-Parser/wiki/Projects-using-the-PHP-Parser#code-generation \ No newline at end of file diff --git a/core/vendor/nikic/php-parser/UPGRADE-2.0.md b/core/vendor/nikic/php-parser/UPGRADE-2.0.md new file mode 100644 index 0000000..1c61de5 --- /dev/null +++ b/core/vendor/nikic/php-parser/UPGRADE-2.0.md @@ -0,0 +1,74 @@ +Upgrading from PHP-Parser 1.x to 2.0 +==================================== + +### PHP version requirements + +PHP-Parser now requires PHP 5.4 or newer to run. It is however still possible to *parse* PHP 5.2 and +PHP 5.3 source code, while running on a newer version. + +### Creating a parser instance + +Parser instances should now be created through the `ParserFactory`. Old direct instantiation code +will not work, because the parser class was renamed. + +Old: + +```php +use PhpParser\Parser, PhpParser\Lexer; +$parser = new Parser(new Lexer\Emulative); +``` + +New: + +```php +use PhpParser\ParserFactory; +$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); +``` + +The first argument to `ParserFactory` determines how different PHP versions are handled. The +possible values are: + + * `ParserFactory::PREFER_PHP7`: Try to parse code as PHP 7. If this fails, try to parse it as PHP 5. + * `ParserFactory::PREFER_PHP5`: Try to parse code as PHP 5. If this fails, try to parse it as PHP 7. + * `ParserFactory::ONLY_PHP7`: Parse code as PHP 7. + * `ParserFactory::ONLY_PHP5`: Parse code as PHP 5. + +For most practical purposes the difference between `PREFER_PHP7` and `PREFER_PHP5` is mainly whether +a scalar type hint like `string` will be stored as `'string'` (PHP 7) or as `new Name('string')` +(PHP 5). + +To use a custom lexer, pass it as the second argument to the `create()` method: + +```php +use PhpParser\ParserFactory; +$lexer = new MyLexer; +$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer); +``` + +### Rename of the `PhpParser\Parser` class + +`PhpParser\Parser` is now an interface, which is implemented by `Parser\Php5`, `Parser\Php7` and +`Parser\Multiple`. Parser tokens are now defined in `Parser\Tokens`. If you use the `ParserFactory` +described above to create your parser instance, these changes should have no further impact on you. + +### Removal of legacy aliases + +All legacy aliases for classes have been removed. This includes the old non-namespaced `PHPParser_` +classes, as well as the classes that had to be renamed for PHP 7 support. + +### Deprecations + +The `set()`, `setFirst()`, `append()` and `prepend()` methods of the `Node\Name` class have been +deprecated. Instead `Name::concat()` and `Name->slice()` should be used. + +### Miscellaneous + +* The `NodeTraverser` no longer clones nodes by default. If you want to restore the old behavior, + pass `true` to the constructor. +* The legacy node format has been removed. If you use custom nodes, they are now expected to + implement a `getSubNodeNames()` method. +* The default value for `Scalar` node constructors was removed. This means that something like + `new LNumber()` should be replaced by `new LNumber(0)`. +* String parts of encapsed strings are now represented using `Scalar\EncapsStringPart` nodes, while + previously raw strings were used. This affects the `parts` child of `Scalar\Encaps` and + `Expr\ShellExec`. \ No newline at end of file diff --git a/core/vendor/nikic/php-parser/bin/php-parse b/core/vendor/nikic/php-parser/bin/php-parse new file mode 100644 index 0000000..974eb1a --- /dev/null +++ b/core/vendor/nikic/php-parser/bin/php-parse @@ -0,0 +1,173 @@ +#!/usr/bin/env php + array( + 'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments' +))); +$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer); +$dumper = new PhpParser\NodeDumper(['dumpComments' => true]); +$prettyPrinter = new PhpParser\PrettyPrinter\Standard; +$serializer = new PhpParser\Serializer\XML; + +$traverser = new PhpParser\NodeTraverser(); +$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + +foreach ($files as $file) { + if (strpos($file, ' Code $code\n"; + } else { + if (!file_exists($file)) { + die("File $file does not exist.\n"); + } + + $code = file_get_contents($file); + echo "====> File $file:\n"; + } + + try { + $stmts = $parser->parse($code); + } catch (PhpParser\Error $e) { + if ($attributes['with-column-info'] && $e->hasColumnInfo()) { + $startLine = $e->getStartLine(); + $endLine = $e->getEndLine(); + $startColumn = $e->getStartColumn($code); + $endColumn = $e->getEndColumn($code); + $message .= $e->getRawMessage() . " from $startLine:$startColumn to $endLine:$endColumn"; + } else { + $message = $e->getMessage(); + } + + die($message . "\n"); + } + + foreach ($operations as $operation) { + if ('dump' === $operation) { + echo "==> Node dump:\n"; + echo $dumper->dump($stmts), "\n"; + } elseif ('pretty-print' === $operation) { + echo "==> Pretty print:\n"; + echo $prettyPrinter->prettyPrintFile($stmts), "\n"; + } elseif ('serialize-xml' === $operation) { + echo "==> Serialized XML:\n"; + echo $serializer->serialize($stmts), "\n"; + } elseif ('var-dump' === $operation) { + echo "==> var_dump():\n"; + var_dump($stmts); + } elseif ('resolve-names' === $operation) { + echo "==> Resolved names.\n"; + $stmts = $traverser->traverse($stmts); + } + } +} + +function showHelp($error = '') { + if ($error) { + echo $error . "\n\n"; + } + die(<< false, + ); + + array_shift($args); + $parseOptions = true; + foreach ($args as $arg) { + if (!$parseOptions) { + $files[] = $arg; + continue; + } + + switch ($arg) { + case '--dump': + case '-d': + $operations[] = 'dump'; + break; + case '--pretty-print': + case '-p': + $operations[] = 'pretty-print'; + break; + case '--serialize-xml': + $operations[] = 'serialize-xml'; + break; + case '--var-dump': + $operations[] = 'var-dump'; + break; + case '--resolve-names': + case '-N'; + $operations[] = 'resolve-names'; + break; + case '--with-column-info': + case '-c'; + $attributes['with-column-info'] = true; + break; + case '--help': + case '-h'; + showHelp(); + break; + case '--': + $parseOptions = false; + break; + default: + if ($arg[0] === '-') { + showHelp("Invalid operation $arg."); + } else { + $files[] = $arg; + } + } + } + + return array($operations, $files, $attributes); +} diff --git a/core/vendor/nikic/php-parser/composer.json b/core/vendor/nikic/php-parser/composer.json new file mode 100644 index 0000000..080d76a --- /dev/null +++ b/core/vendor/nikic/php-parser/composer.json @@ -0,0 +1,30 @@ +{ + "name": "nikic/php-parser", + "description": "A PHP parser written in PHP", + "keywords": ["php", "parser"], + "type": "library", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov" + } + ], + "require": { + "php": ">=5.4", + "ext-tokenizer": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "bin": ["bin/php-parse"], + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + } +} diff --git a/core/vendor/nikic/php-parser/doc/0_Introduction.markdown b/core/vendor/nikic/php-parser/doc/0_Introduction.markdown new file mode 100644 index 0000000..fc2c038 --- /dev/null +++ b/core/vendor/nikic/php-parser/doc/0_Introduction.markdown @@ -0,0 +1,80 @@ +Introduction +============ + +This project is a PHP 5.2 to PHP 7.0 parser **written in PHP itself**. + +What is this for? +----------------- + +A parser is useful for [static analysis][0], manipulation of code and basically any other +application dealing with code programmatically. A parser constructs an [Abstract Syntax Tree][1] +(AST) of the code and thus allows dealing with it in an abstract and robust way. + +There are other ways of processing source code. One that PHP supports natively is using the +token stream generated by [`token_get_all`][2]. The token stream is much more low level than +the AST and thus has different applications: It allows to also analyze the exact formatting of +a file. On the other hand the token stream is much harder to deal with for more complex analysis. +For example an AST abstracts away the fact that in PHP variables can be written as `$foo`, but also +as `$$bar`, `${'foobar'}` or even `${!${''}=barfoo()}`. You don't have to worry about recognizing +all the different syntaxes from a stream of tokens. + +Another question is: Why would I want to have a PHP parser *written in PHP*? Well, PHP might not be +a language especially suited for fast parsing, but processing the AST is much easier in PHP than it +would be in other, faster languages like C. Furthermore the people most probably wanting to do +programmatic PHP code analysis are incidentally PHP developers, not C developers. + +What can it parse? +------------------ + +The parser supports parsing PHP 5.2-5.6 and PHP 7. + +As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP +version it runs on), additionally a wrapper for emulating new tokens from 5.5, 5.6 and 7.0 is +provided. This allows to parse PHP 7.0 source code running on PHP 5.4, for example. This emulation +is somewhat hacky and not perfect, but it should work well on any sane code. + +What output does it produce? +---------------------------- + +The parser produces an [Abstract Syntax Tree][1] (AST) also known as a node tree. How this looks like +can best be seen in an example. The program `create(ParserFactory::PREFER_PHP7); +``` + +The factory accepts a kind argument, that determines how different PHP versions are treated: + +Kind | Behavior +-----|--------- +`ParserFactory::PREFER_PHP7` | Try to parse code as PHP 7. If this fails, try to parse it as PHP 5. +`ParserFactory::PREFER_PHP5` | Try to parse code as PHP 5. If this fails, try to parse it as PHP 7. +`ParserFactory::ONLY_PHP7` | Parse code as PHP 7. +`ParserFactory::ONLY_PHP5` | Parse code as PHP 5. + +Unless you have strong reason to use something else, `PREFER_PHP7` is a reasonable default. + +The `create()` method optionally accepts a `Lexer` instance as the second argument. Some use cases +that require customized lexers are discussed in the [lexer documentation](component/Lexer.markdown). + +Subsequently you can pass PHP code (including the opening `create(ParserFactory::PREFER_PHP7); + +try { + $stmts = $parser->parse($code); + // $stmts is an array of statement nodes +} catch (Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +A parser instance can be reused to parse multiple files. + +Node tree +--------- + +If you use the above code with `$code = "subNodeName`. The `Stmt\Echo_` node has only one subnode `exprs`. So in order to access it +in the above example you would write `$stmts[0]->exprs`. If you wanted to access the name of the function +call, you would write `$stmts[0]->exprs[1]->name`. + +All nodes also define a `getType()` method that returns the node type. The type is the class name +without the `PhpParser\Node\` prefix and `\` replaced with `_`. It also does not contain a trailing +`_` for reserved-keyword class names. + +It is possible to associate custom metadata with a node using the `setAttribute()` method. This data +can then be retrieved using `hasAttribute()`, `getAttribute()` and `getAttributes()`. + +By default the lexer adds the `startLine`, `endLine` and `comments` attributes. `comments` is an array +of `PhpParser\Comment[\Doc]` instances. + +The start line can also be accessed using `getLine()`/`setLine()` (instead of `getAttribute('startLine')`). +The last doc comment from the `comments` attribute can be obtained using `getDocComment()`. + +Pretty printer +-------------- + +The pretty printer component compiles the AST back to PHP code. As the parser does not retain formatting +information the formatting is done using a specified scheme. Currently there is only one scheme available, +namely `PhpParser\PrettyPrinter\Standard`. + +```php +use PhpParser\Error; +use PhpParser\ParserFactory; +use PhpParser\PrettyPrinter; + +$code = "create(ParserFactory::PREFER_PHP7); +$prettyPrinter = new PrettyPrinter\Standard; + +try { + // parse + $stmts = $parser->parse($code); + + // change + $stmts[0] // the echo statement + ->exprs // sub expressions + [0] // the first of them (the string node) + ->value // it's value, i.e. 'Hi ' + = 'Hello '; // change to 'Hello ' + + // pretty print + $code = $prettyPrinter->prettyPrint($stmts); + + echo $code; +} catch (Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +The above code will output: + + parse()`, then changed and then +again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`. + +The `prettyPrint()` method pretty prints a statements array. It is also possible to pretty print only a +single expression using `prettyPrintExpr()`. + +The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `create(ParserFactory::PREFER_PHP7); +$traverser = new NodeTraverser; +$prettyPrinter = new PrettyPrinter\Standard; + +// add your visitor +$traverser->addVisitor(new MyNodeVisitor); + +try { + $code = file_get_contents($fileName); + + // parse + $stmts = $parser->parse($code); + + // traverse + $stmts = $traverser->traverse($stmts); + + // pretty print + $code = $prettyPrinter->prettyPrintFile($stmts); + + echo $code; +} catch (PhpParser\Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +The corresponding node visitor might look like this: + +```php +use PhpParser\Node; +use PhpParser\NodeVisitorAbstract; + +class MyNodeVisitor extends NodeVisitorAbstract +{ + public function leaveNode(Node $node) { + if ($node instanceof Node\Scalar\String_) { + $node->value = 'foo'; + } + } +} +``` + +The above node visitor would change all string literals in the program to `'foo'`. + +All visitors must implement the `PhpParser\NodeVisitor` interface, which defines the following four +methods: + +```php +public function beforeTraverse(array $nodes); +public function enterNode(\PhpParser\Node $node); +public function leaveNode(\PhpParser\Node $node); +public function afterTraverse(array $nodes); +``` + +The `beforeTraverse()` method is called once before the traversal begins and is passed the nodes the +traverser was called with. This method can be used for resetting values before traversation or +preparing the tree for traversal. + +The `afterTraverse()` method is similar to the `beforeTraverse()` method, with the only difference that +it is called once after the traversal. + +The `enterNode()` and `leaveNode()` methods are called on every node, the former when it is entered, +i.e. before its subnodes are traversed, the latter when it is left. + +All four methods can either return the changed node or not return at all (i.e. `null`) in which +case the current node is not changed. + +The `enterNode()` method can additionally return the value `NodeTraverser::DONT_TRAVERSE_CHILDREN`, +which instructs the traverser to skip all children of the current node. + +The `leaveNode()` method can additionally return the value `NodeTraverser::REMOVE_NODE`, in which +case the current node will be removed from the parent array. Furthermore it is possible to return +an array of nodes, which will be merged into the parent array at the offset of the current node. +I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will +be `array(A, X, Y, Z, C)`. + +Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract` +class, which will define empty default implementations for all the above methods. + +The NameResolver node visitor +----------------------------- + +One visitor is already bundled with the package: `PhpParser\NodeVisitor\NameResolver`. This visitor +helps you work with namespaced code by trying to resolve most names to fully qualified ones. + +For example, consider the following code: + + use A as B; + new B\C(); + +In order to know that `B\C` really is `A\C` you would need to track aliases and namespaces yourself. +The `NameResolver` takes care of that and resolves names as far as possible. + +After running it most names will be fully qualified. The only names that will stay unqualified are +unqualified function and constant names. These are resolved at runtime and thus the visitor can't +know which function they are referring to. In most cases this is a non-issue as the global functions +are meant. + +Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations +that contains the namespaced name instead of only the shortname that is available via `name`. + +Example: Converting namespaced code to pseudo namespaces +-------------------------------------------------------- + +A small example to understand the concept: We want to convert namespaced code to pseudo namespaces +so it works on 5.2, i.e. names like `A\\B` should be converted to `A_B`. Note that such conversions +are fairly complicated if you take PHP's dynamic features into account, so our conversion will +assume that no dynamic features are used. + +We start off with the following base code: + +```php +use PhpParser\ParserFactory; +use PhpParser\PrettyPrinter; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; + +$inDir = '/some/path'; +$outDir = '/some/other/path'; + +$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); +$traverser = new NodeTraverser; +$prettyPrinter = new PrettyPrinter\Standard; + +$traverser->addVisitor(new NameResolver); // we will need resolved names +$traverser->addVisitor(new NamespaceConverter); // our own node visitor + +// iterate over all .php files in the directory +$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($inDir)); +$files = new \RegexIterator($files, '/\.php$/'); + +foreach ($files as $file) { + try { + // read the file that should be converted + $code = file_get_contents($file); + + // parse + $stmts = $parser->parse($code); + + // traverse + $stmts = $traverser->traverse($stmts); + + // pretty print + $code = $prettyPrinter->prettyPrintFile($stmts); + + // write the converted file to the target directory + file_put_contents( + substr_replace($file->getPathname(), $outDir, 0, strlen($inDir)), + $code + ); + } catch (PhpParser\Error $e) { + echo 'Parse Error: ', $e->getMessage(); + } +} +``` + +Now lets start with the main code, the `NodeVisitor\NamespaceConverter`. One thing it needs to do +is convert `A\\B` style names to `A_B` style ones. + +```php +use PhpParser\Node; + +class NamespaceConverter extends \PhpParser\NodeVisitorAbstract +{ + public function leaveNode(Node $node) { + if ($node instanceof Node\Name) { + return new Node\Name($node->toString('_')); + } + } +} +``` + +The above code profits from the fact that the `NameResolver` already resolved all names as far as +possible, so we don't need to do that. We only need to create a string with the name parts separated +by underscores instead of backslashes. This is what `$node->toString('_')` does. (If you want to +create a name with backslashes either write `$node->toString()` or `(string) $node`.) Then we create +a new name from the string and return it. Returning a new node replaces the old node. + +Another thing we need to do is change the class/function/const declarations. Currently they contain +only the shortname (i.e. the last part of the name), but they need to contain the complete name including +the namespace prefix: + +```php +use PhpParser\Node; +use PhpParser\Node\Stmt; + +class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract +{ + public function leaveNode(Node $node) { + if ($node instanceof Node\Name) { + return new Node\Name($node->toString('_')); + } elseif ($node instanceof Stmt\Class_ + || $node instanceof Stmt\Interface_ + || $node instanceof Stmt\Function_) { + $node->name = $node->namespacedName->toString('_'); + } elseif ($node instanceof Stmt\Const_) { + foreach ($node->consts as $const) { + $const->name = $const->namespacedName->toString('_'); + } + } + } +} +``` + +There is not much more to it than converting the namespaced name to string with `_` as separator. + +The last thing we need to do is remove the `namespace` and `use` statements: + +```php +use PhpParser\Node; +use PhpParser\Node\Stmt; + +class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract +{ + public function leaveNode(Node $node) { + if ($node instanceof Node\Name) { + return new Node\Name($node->toString('_')); + } elseif ($node instanceof Stmt\Class_ + || $node instanceof Stmt\Interface_ + || $node instanceof Stmt\Function_) { + $node->name = $node->namespacedName->toString('_'); + } elseif ($node instanceof Stmt\Const_) { + foreach ($node->consts as $const) { + $const->name = $const->namespacedName->toString('_'); + } + } elseif ($node instanceof Stmt\Namespace_) { + // returning an array merges is into the parent array + return $node->stmts; + } elseif ($node instanceof Stmt\Use_) { + // returning false removed the node altogether + return false; + } + } +} +``` + +That's all. diff --git a/core/vendor/nikic/php-parser/doc/3_Other_node_tree_representations.markdown b/core/vendor/nikic/php-parser/doc/3_Other_node_tree_representations.markdown new file mode 100644 index 0000000..691640e --- /dev/null +++ b/core/vendor/nikic/php-parser/doc/3_Other_node_tree_representations.markdown @@ -0,0 +1,202 @@ +Other node tree representations +=============================== + +It is possible to convert the AST into several textual representations, which serve different uses. + +Simple serialization +-------------------- + +It is possible to serialize the node tree using `serialize()` and also unserialize it using +`unserialize()`. The output is not human readable and not easily processable from anything +but PHP, but it is compact and generates fast. The main application thus is in caching. + +Human readable dumping +---------------------- + +Furthermore it is possible to dump nodes into a human readable format using the `dump` method of +`PhpParser\NodeDumper`. This can be used for debugging. + +```php +$code = <<<'CODE' +create(PhpParser\ParserFactory::PREFER_PHP7); +$nodeDumper = new PhpParser\NodeDumper; + +try { + $stmts = $parser->parse($code); + + echo $nodeDumper->dump($stmts), "\n"; +} catch (PhpParser\Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +The above script will have an output looking roughly like this: + +``` +array( + 0: Stmt_Function( + byRef: false + params: array( + 0: Param( + name: msg + default: null + type: null + byRef: false + ) + ) + stmts: array( + 0: Stmt_Echo( + exprs: array( + 0: Expr_Variable( + name: msg + ) + 1: Scalar_String( + value: + + ) + ) + ) + ) + name: printLine + ) + 1: Expr_FuncCall( + name: Name( + parts: array( + 0: printLine + ) + ) + args: array( + 0: Arg( + value: Scalar_String( + value: Hello World!!! + ) + byRef: false + ) + ) + ) +) +``` + +Serialization to XML +-------------------- + +It is also possible to serialize the node tree to XML using `PhpParser\Serializer\XML->serialize()` +and to unserialize it using `PhpParser\Unserializer\XML->unserialize()`. This is useful for +interfacing with other languages and applications or for doing transformation using XSLT. + +```php +create(PhpParser\ParserFactory::PREFER_PHP7); +$serializer = new PhpParser\Serializer\XML; + +try { + $stmts = $parser->parse($code); + + echo $serializer->serialize($stmts); +} catch (PhpParser\Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +Produces: + +```xml + + + + + + + + + + + + msg + + + + + + + + + + + + + + + + + + + + + msg + + + + + + + + + + + + + + + printLine + + + + + + + + printLine + + + + + + + + + + + Hello World!!! + + + + + + + + + + + + +``` \ No newline at end of file diff --git a/core/vendor/nikic/php-parser/doc/4_Code_generation.markdown b/core/vendor/nikic/php-parser/doc/4_Code_generation.markdown new file mode 100644 index 0000000..2c0aa0e --- /dev/null +++ b/core/vendor/nikic/php-parser/doc/4_Code_generation.markdown @@ -0,0 +1,84 @@ +Code generation +=============== + +It is also possible to generate code using the parser, by first creating an Abstract Syntax Tree and then using the +pretty printer to convert it to PHP code. To simplify code generation, the project comes with builders which allow +creating node trees using a fluid interface, instead of instantiating all nodes manually. Builders are available for +the following syntactic elements: + + * namespaces and use statements + * classes, interfaces and traits + * methods, functions and parameters + * properties + +Here is an example: + +```php +use PhpParser\BuilderFactory; +use PhpParser\PrettyPrinter; +use PhpParser\Node; + +$factory = new BuilderFactory; +$node = $factory->namespace('Name\Space') + ->addStmt($factory->use('Some\Other\Thingy')->as('SomeOtherClass')) + ->addStmt($factory->class('SomeClass') + ->extend('SomeOtherClass') + ->implement('A\Few', '\Interfaces') + ->makeAbstract() // ->makeFinal() + + ->addStmt($factory->method('someMethod') + ->makePublic() + ->makeAbstract() // ->makeFinal() + ->setReturnType('bool') + ->addParam($factory->param('someParam')->setTypeHint('SomeClass')) + ->setDocComment('/** + * This method does something. + * + * @param SomeClass And takes a parameter + */') + ) + + ->addStmt($factory->method('anotherMethod') + ->makeProtected() // ->makePublic() [default], ->makePrivate() + ->addParam($factory->param('someParam')->setDefault('test')) + // it is possible to add manually created nodes + ->addStmt(new Node\Expr\Print_(new Node\Expr\Variable('someParam'))) + ) + + // properties will be correctly reordered above the methods + ->addStmt($factory->property('someProperty')->makeProtected()) + ->addStmt($factory->property('anotherProperty')->makePrivate()->setDefault(array(1, 2, 3))) + ) + + ->getNode() +; + +$stmts = array($node); +$prettyPrinter = new PrettyPrinter\Standard(); +echo $prettyPrinter->prettyPrintFile($stmts); +``` + +This will produce the following output with the standard pretty printer: + +```php + array('comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'), +)); +$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer); + +try { + $stmts = $parser->parse($code); + // ... +} catch (PhpParser\Error $e) { + // ... +} +``` + +Before using column information its availability needs to be checked with `$e->hasColumnInfo()`, as the precise +location of an error cannot always be determined. The methods for retrieving column information also have to be passed +the source code of the parsed file. An example for printing an error: + +```php +if ($e->hasColumnInfo()) { + echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code) + . ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code); +} else { + echo $e->getMessage(); +} +``` + +Both line numbers and column numbers are 1-based. EOF errors will be located at the position one past the end of the +file. + +Error recovery +-------------- + +> **EXPERIMENTAL** + +By default the parser will throw an exception upon encountering the first error during parsing. An alternative mode is +also supported, in which the parser will remember the error, but try to continue parsing the rest of the source code. + +To enable this mode the `throwOnError` parser option needs to be disabled. Any errors that occurred during parsing can +then be retrieved using `$parser->getErrors()`. The `$parser->parse()` method will either return a partial syntax tree +or `null` if recovery fails. + +A usage example: + +```php +$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, null, array( + 'throwOnError' => false, +)); + +$stmts = $parser->parse($code); +$errors = $parser->getErrors(); + +foreach ($errors as $error) { + // $error is an ordinary PhpParser\Error +} + +if (null !== $stmts) { + // $stmts is a best-effort partial AST +} +``` + +The error recovery implementation is experimental -- it currently won't be able to recover from many types of errors. diff --git a/core/vendor/nikic/php-parser/doc/component/Lexer.markdown b/core/vendor/nikic/php-parser/doc/component/Lexer.markdown new file mode 100644 index 0000000..422dd37 --- /dev/null +++ b/core/vendor/nikic/php-parser/doc/component/Lexer.markdown @@ -0,0 +1,149 @@ +Lexer component documentation +============================= + +The lexer is responsible for providing tokens to the parser. The project comes with two lexers: `PhpParser\Lexer` and +`PhpParser\Lexer\Emulative`. The latter is an extension of the former, which adds the ability to emulate tokens of +newer PHP versions and thus allows parsing of new code on older versions. + +This documentation discusses options available for the default lexers and explains how lexers can be extended. + +Lexer options +------------- + +The two default lexers accept an `$options` array in the constructor. Currently only the `'usedAttributes'` option is +supported, which allows you to specify which attributes will be added to the AST nodes. The attributes can then be +accessed using `$node->getAttribute()`, `$node->setAttribute()`, `$node->hasAttribute()` and `$node->getAttributes()` +methods. A sample options array: + +```php +$lexer = new PhpParser\Lexer(array( + 'usedAttributes' => array( + 'comments', 'startLine', 'endLine' + ) +)); +``` + +The attributes used in this example match the default behavior of the lexer. The following attributes are supported: + + * `comments`: Array of `PhpParser\Comment` or `PhpParser\Comment\Doc` instances, representing all comments that occurred + between the previous non-discarded token and the current one. Use of this attribute is required for the + `$node->getDocComment()` method to work. The attribute is also needed if you wish the pretty printer to retain + comments present in the original code. + * `startLine`: Line in which the node starts. This attribute is required for the `$node->getLine()` to work. It is also + required if syntax errors should contain line number information. + * `endLine`: Line in which the node ends. + * `startTokenPos`: Offset into the token array of the first token in the node. + * `endTokenPos`: Offset into the token array of the last token in the node. + * `startFilePos`: Offset into the code string of the first character that is part of the node. + * `endFilePos`: Offset into the code string of the last character that is part of the node. + +### Using token positions + +The token offset information is useful if you wish to examine the exact formatting used for a node. For example the AST +does not distinguish whether a property was declared using `public` or using `var`, but you can retrieve this +information based on the token position: + +```php +function isDeclaredUsingVar(array $tokens, PhpParser\Node\Stmt\Property $prop) { + $i = $prop->getAttribute('startTokenPos'); + return $tokens[$i][0] === T_VAR; +} +``` + +In order to make use of this function, you will have to provide the tokens from the lexer to your node visitor using +code similar to the following: + +```php +class MyNodeVisitor extends PhpParser\NodeVisitorAbstract { + private $tokens; + public function setTokens(array $tokens) { + $this->tokens = $tokens; + } + + public function leaveNode(PhpParser\Node $node) { + if ($node instanceof PhpParser\Node\Stmt\Property) { + var_dump(isDeclaredUsingVar($this->tokens, $node)); + } + } +} + +$lexer = new PhpParser\Lexer(array( + 'usedAttributes' => array( + 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos' + ) +)); +$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer); + +$visitor = new MyNodeVisitor(); +$traverser = new PhpParser\NodeTraverser(); +$traverser->addVisitor($visitor); + +try { + $stmts = $parser->parse($code); + $visitor->setTokens($lexer->getTokens()); + $stmts = $traverser->traverse($stmts); +} catch (PhpParser\Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +The same approach can also be used to perform specific modifications in the code, without changing the formatting in +other places (which is the case when using the pretty printer). + +Lexer extension +--------------- + +A lexer has to define the following public interface: + + void startLexing(string $code); + array getTokens(); + string handleHaltCompiler(); + int getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null); + +The `startLexing()` method is invoked with the source code that is to be lexed (including the opening tag) whenever the +`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens. + +The `getTokens()` method returns the current token array, in the usual `token_get_all()` format. This method is not +used by the parser (which uses `getNextToken()`), but is useful in combination with the token position attributes. + +The `handleHaltCompiler()` method is called whenever a `T_HALT_COMPILER` token is encountered. It has to return the +remaining string after the construct (not including `();`). + +The `getNextToken()` method returns the ID of the next token (as defined by the `Parser::T_*` constants). If no more +tokens are available it must return `0`, which is the ID of the `EOF` token. Furthermore the string content of the +token should be written into the by-reference `$value` parameter (which will then be available as `$n` in the parser). + +### Attribute handling + +The other two by-ref variables `$startAttributes` and `$endAttributes` define which attributes will eventually be +assigned to the generated nodes: The parser will take the `$startAttributes` from the first token which is part of the +node and the `$endAttributes` from the last token that is part of the node. + +E.g. if the tokens `T_FUNCTION T_STRING ... '{' ... '}'` constitute a node, then the `$startAttributes` from the +`T_FUNCTION` token will be taken and the `$endAttributes` from the `'}'` token. + +An application of custom attributes is storing the original formatting of literals: The parser does not retain +information about the formatting of integers (like decimal vs. hexadecimal) or strings (like used quote type or used +escape sequences). This can be remedied by storing the original value in an attribute: + +```php +use PhpParser\Lexer; +use PhpParser\Parser\Tokens; + +class KeepOriginalValueLexer extends Lexer // or Lexer\Emulative +{ + public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) { + $tokenId = parent::getNextToken($value, $startAttributes, $endAttributes); + + if ($tokenId == Tokens::T_CONSTANT_ENCAPSED_STRING // non-interpolated string + || $tokenId == Tokens::T_LNUMBER // integer + || $tokenId == Tokens::T_DNUMBER // floating point number + ) { + // could also use $startAttributes, doesn't really matter here + $endAttributes['originalValue'] = $value; + } + + return $tokenId; + } +} +``` diff --git a/core/vendor/nikic/php-parser/grammar/README.md b/core/vendor/nikic/php-parser/grammar/README.md new file mode 100644 index 0000000..93206a8 --- /dev/null +++ b/core/vendor/nikic/php-parser/grammar/README.md @@ -0,0 +1,29 @@ +What do all those files mean? +============================= + + * `php5.y`: PHP 5 grammar written in a pseudo language + * `php7.y`: PHP 7 grammar written in a pseudo language + * `tokens.y`: Tokens definition shared between PHP 5 and PHP 7 grammars + * `parser.template`: A `kmyacc` parser prototype file for PHP + * `tokens.template`: A `kmyacc` prototype file for the `Tokens` class + * `analyze.php`: Analyzes the grammer and outputs some info about it + * `rebuildParser.php`: Preprocesses the grammar and builds the parser using `kmyacc` + +.phpy pseudo language +===================== + +The `.y` file is a normal grammer in `kmyacc` (`yacc`) style, with some transformations +applied to it: + + * Nodes are created using the syntax `Name[..., ...]`. This is transformed into + `new Name(..., ..., attributes())` + * Some function-like constructs are resolved (see `rebuildParser.php` for a list) + +Building the parser +=================== + +In order to rebuild the parser, you need [moriyoshi's fork of kmyacc](https://github.com/moriyoshi/kmyacc-forked). +After you compiled/installed it, run the `rebuildParser.php` script. + +By default only the `Parser.php` is built. If you want to additionally emit debug symbols and create `y.output`, run the +script with `--debug`. If you want to retain the preprocessed grammar pass `--keep-tmp-grammar`. diff --git a/core/vendor/nikic/php-parser/grammar/analyze.php b/core/vendor/nikic/php-parser/grammar/analyze.php new file mode 100644 index 0000000..cd30b3c --- /dev/null +++ b/core/vendor/nikic/php-parser/grammar/analyze.php @@ -0,0 +1,96 @@ +\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\') + (?"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+") + (?(?&singleQuotedString)|(?&doubleQuotedString)) + (?/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/) + (?\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+}) +)'; + +const RULE_BLOCK = '(?[a-z_]++):(?[^\'"/{};]*+(?:(?:(?&string)|(?&comment)|(?&code)|/|})[^\'"/{};]*+)*+);'; + +$usedTerminals = array_flip(array( + 'T_VARIABLE', 'T_STRING', 'T_INLINE_HTML', 'T_ENCAPSED_AND_WHITESPACE', + 'T_LNUMBER', 'T_DNUMBER', 'T_CONSTANT_ENCAPSED_STRING', 'T_STRING_VARNAME', 'T_NUM_STRING' +)); +$unusedNonterminals = array_flip(array( + 'case_separator', 'optional_comma' +)); + +function regex($regex) { + return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~'; +} + +function magicSplit($regex, $string) { + $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string); + + foreach ($pieces as &$piece) { + $piece = trim($piece); + } + + return array_filter($pieces); +} + +echo '
    ';
    +
    +////////////////////
    +////////////////////
    +////////////////////
    +
    +list($defs, $ruleBlocks) = magicSplit('%%', file_get_contents(GRAMMAR_FILE));
    +
    +if ('' !== trim(preg_replace(regex(RULE_BLOCK), '', $ruleBlocks))) {
    +    die('Not all rule blocks were properly recognized!');
    +}
    +
    +preg_match_all(regex(RULE_BLOCK), $ruleBlocks, $ruleBlocksMatches, PREG_SET_ORDER);
    +foreach ($ruleBlocksMatches as $match) {
    +    $ruleBlockName = $match['name'];
    +    $rules = magicSplit('\|', $match['rules']);
    +
    +    foreach ($rules as &$rule) {
    +        $parts = magicSplit('\s+', $rule);
    +        $usedParts = array();
    +
    +        foreach ($parts as $part) {
    +            if ('{' === $part[0]) {
    +                preg_match_all('~\$([0-9]+)~', $part, $backReferencesMatches, PREG_SET_ORDER);
    +                foreach ($backReferencesMatches as $match) {
    +                    $usedParts[$match[1]] = true;
    +                }
    +            }
    +        }
    +
    +        $i = 1;
    +        foreach ($parts as &$part) {
    +            if ('/' === $part[0]) {
    +                continue;
    +            }
    +
    +            if (isset($usedParts[$i])) {
    +                if ('\'' === $part[0] || '{' === $part[0]
    +                    || (ctype_upper($part[0]) && !isset($usedTerminals[$part]))
    +                    || (ctype_lower($part[0]) && isset($unusedNonterminals[$part]))
    +                ) {
    +                    $part = '' . $part . '';
    +                } else {
    +                    $part = '' . $part . '';
    +                }
    +            } elseif ((ctype_upper($part[0]) && isset($usedTerminals[$part]))
    +                      || (ctype_lower($part[0]) && !isset($unusedNonterminals[$part]))
    +
    +            ) {
    +                $part = '' . $part . '';
    +            }
    +
    +            ++$i;
    +        }
    +
    +        $rule = implode(' ', $parts);
    +    }
    +
    +    echo $ruleBlockName, ':', "\n", '      ', implode("\n" . '    | ', $rules), "\n", ';', "\n\n";
    +}
    diff --git a/core/vendor/nikic/php-parser/grammar/parser.template b/core/vendor/nikic/php-parser/grammar/parser.template
    new file mode 100644
    index 0000000..fff893f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/grammar/parser.template
    @@ -0,0 +1,103 @@
    +semValue
    +#semval($,%t) $this->semValue
    +#semval(%n) $this->stackPos-(%l-%n)
    +#semval(%n,%t) $this->stackPos-(%l-%n)
    +
    +namespace PhpParser\Parser;
    +
    +use PhpParser\Error;
    +use PhpParser\Node;
    +use PhpParser\Node\Expr;
    +use PhpParser\Node\Name;
    +use PhpParser\Node\Scalar;
    +use PhpParser\Node\Stmt;
    +#include;
    +
    +/* This is an automatically GENERATED file, which should not be manually edited.
    + * Instead edit one of the following:
    + *  * the grammar files grammar/php5.y or grammar/php7.y
    + *  * the skeleton file grammar/parser.template
    + *  * the preprocessing script grammar/rebuildParsers.php
    + */
    +class #(-p) extends \PhpParser\ParserAbstract
    +{
    +    protected $tokenToSymbolMapSize = #(YYMAXLEX);
    +    protected $actionTableSize = #(YYLAST);
    +    protected $gotoTableSize = #(YYGLAST);
    +
    +    protected $invalidSymbol = #(YYBADCH);
    +    protected $errorSymbol = #(YYINTERRTOK);
    +    protected $defaultAction = #(YYDEFAULT);
    +    protected $unexpectedTokenRule = #(YYUNEXPECTED);
    +
    +    protected $YY2TBLSTATE  = #(YY2TBLSTATE);
    +    protected $YYNLSTATES   = #(YYNLSTATES);
    +
    +    protected $symbolToName = array(
    +        #listvar terminals
    +    );
    +
    +    protected $tokenToSymbol = array(
    +        #listvar yytranslate
    +    );
    +
    +    protected $action = array(
    +        #listvar yyaction
    +    );
    +
    +    protected $actionCheck = array(
    +        #listvar yycheck
    +    );
    +
    +    protected $actionBase = array(
    +        #listvar yybase
    +    );
    +
    +    protected $actionDefault = array(
    +        #listvar yydefault
    +    );
    +
    +    protected $goto = array(
    +        #listvar yygoto
    +    );
    +
    +    protected $gotoCheck = array(
    +        #listvar yygcheck
    +    );
    +
    +    protected $gotoBase = array(
    +        #listvar yygbase
    +    );
    +
    +    protected $gotoDefault = array(
    +        #listvar yygdefault
    +    );
    +
    +    protected $ruleToNonTerminal = array(
    +        #listvar yylhs
    +    );
    +
    +    protected $ruleToLength = array(
    +        #listvar yylen
    +    );
    +#if -t
    +
    +    protected $productions = array(
    +        #production-strings;
    +    );
    +#endif
    +#reduce
    +
    +    protected function reduceRule%n() {
    +        %b
    +    }
    +#noact
    +
    +    protected function reduceRule%n() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +#endreduce
    +}
    +#tailcode;
    diff --git a/core/vendor/nikic/php-parser/grammar/php5.y b/core/vendor/nikic/php-parser/grammar/php5.y
    new file mode 100644
    index 0000000..d40d093
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/grammar/php5.y
    @@ -0,0 +1,980 @@
    +%pure_parser
    +%expect 6
    +
    +%tokens
    +
    +%%
    +
    +start:
    +    top_statement_list                                      { $$ = $this->handleNamespaces($1); }
    +;
    +
    +top_statement_list_ex:
    +      top_statement_list_ex top_statement                   { pushNormalizing($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +top_statement_list:
    +      top_statement_list_ex
    +          { makeNop($nop, $this->lookaheadStartAttributes);
    +            if ($nop !== null) { $1[] = $nop; } $$ = $1; }
    +;
    +
    +reserved_non_modifiers:
    +      T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
    +    | T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
    +    | T_ENDWHILE | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH
    +    | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
    +    | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
    +    | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
    +    | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER
    +;
    +
    +semi_reserved:
    +      reserved_non_modifiers
    +    | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
    +;
    +
    +identifier:
    +      T_STRING                                              { $$ = $1; }
    +    | semi_reserved                                         { $$ = $1; }
    +;
    +
    +namespace_name_parts:
    +      T_STRING                                              { init($1); }
    +    | namespace_name_parts T_NS_SEPARATOR T_STRING          { push($1, $3); }
    +;
    +
    +namespace_name:
    +      namespace_name_parts                                  { $$ = Name[$1]; }
    +;
    +
    +top_statement:
    +      statement                                             { $$ = $1; }
    +    | function_declaration_statement                        { $$ = $1; }
    +    | class_declaration_statement                           { $$ = $1; }
    +    | T_HALT_COMPILER
    +          { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
    +    | T_NAMESPACE namespace_name ';'                        { $$ = Stmt\Namespace_[$2, null]; }
    +    | T_NAMESPACE namespace_name '{' top_statement_list '}' { $$ = Stmt\Namespace_[$2, $4]; }
    +    | T_NAMESPACE '{' top_statement_list '}'                { $$ = Stmt\Namespace_[null,     $3]; }
    +    | T_USE use_declarations ';'                            { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
    +    | T_USE use_type use_declarations ';'                   { $$ = Stmt\Use_[$3, $2]; }
    +    | group_use_declaration ';'                             { $$ = $1; }
    +    | T_CONST constant_declaration_list ';'                 { $$ = Stmt\Const_[$2]; }
    +;
    +
    +use_type:
    +      T_FUNCTION                                            { $$ = Stmt\Use_::TYPE_FUNCTION; }
    +    | T_CONST                                               { $$ = Stmt\Use_::TYPE_CONSTANT; }
    +;
    +
    +/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
    +group_use_declaration:
    +      T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
    +          { $$ = Stmt\GroupUse[Name[$3], $6, $2]; }
    +    | T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
    +          { $$ = Stmt\GroupUse[Name[$4], $7, $2]; }
    +    | T_USE namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
    +          { $$ = Stmt\GroupUse[Name[$2], $5, Stmt\Use_::TYPE_UNKNOWN]; }
    +    | T_USE T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
    +          { $$ = Stmt\GroupUse[Name[$3], $6, Stmt\Use_::TYPE_UNKNOWN]; }
    +;
    +
    +unprefixed_use_declarations:
    +      unprefixed_use_declarations ',' unprefixed_use_declaration
    +          { push($1, $3); }
    +    | unprefixed_use_declaration                            { init($1); }
    +;
    +
    +use_declarations:
    +      use_declarations ',' use_declaration                  { push($1, $3); }
    +    | use_declaration                                       { init($1); }
    +;
    +
    +inline_use_declarations:
    +      inline_use_declarations ',' inline_use_declaration    { push($1, $3); }
    +    | inline_use_declaration                                { init($1); }
    +;
    +
    +unprefixed_use_declaration:
    +      namespace_name                                        { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; }
    +    | namespace_name T_AS T_STRING                          { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; }
    +;
    +
    +use_declaration:
    +      unprefixed_use_declaration                            { $$ = $1; }
    +    | T_NS_SEPARATOR unprefixed_use_declaration             { $$ = $2; }
    +;
    +
    +inline_use_declaration:
    +      unprefixed_use_declaration                            { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; }
    +    | use_type unprefixed_use_declaration                   { $$ = $2; $$->type = $1; }
    +;
    +
    +constant_declaration_list:
    +      constant_declaration_list ',' constant_declaration    { push($1, $3); }
    +    | constant_declaration                                  { init($1); }
    +;
    +
    +constant_declaration:
    +    T_STRING '=' static_scalar                              { $$ = Node\Const_[$1, $3]; }
    +;
    +
    +class_const_list:
    +      class_const_list ',' class_const                      { push($1, $3); }
    +    | class_const                                           { init($1); }
    +;
    +
    +class_const:
    +    identifier '=' static_scalar                            { $$ = Node\Const_[$1, $3]; }
    +;
    +
    +inner_statement_list_ex:
    +      inner_statement_list_ex inner_statement               { pushNormalizing($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +inner_statement_list:
    +      inner_statement_list_ex
    +          { makeNop($nop, $this->lookaheadStartAttributes);
    +            if ($nop !== null) { $1[] = $nop; } $$ = $1; }
    +;
    +
    +inner_statement:
    +      statement                                             { $$ = $1; }
    +    | function_declaration_statement                        { $$ = $1; }
    +    | class_declaration_statement                           { $$ = $1; }
    +    | T_HALT_COMPILER
    +          { throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
    +;
    +
    +non_empty_statement:
    +      '{' inner_statement_list '}'                          { $$ = $2; prependLeadingComments($$); }
    +    | T_IF parentheses_expr statement elseif_list else_single
    +          { $$ = Stmt\If_[$2, ['stmts' => toArray($3), 'elseifs' => $4, 'else' => $5]]; }
    +    | T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
    +          { $$ = Stmt\If_[$2, ['stmts' => $4, 'elseifs' => $5, 'else' => $6]]; }
    +    | T_WHILE parentheses_expr while_statement              { $$ = Stmt\While_[$2, $3]; }
    +    | T_DO statement T_WHILE parentheses_expr ';'           { $$ = Stmt\Do_   [$4, toArray($2)]; }
    +    | T_FOR '(' for_expr ';'  for_expr ';' for_expr ')' for_statement
    +          { $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
    +    | T_SWITCH parentheses_expr switch_case_list            { $$ = Stmt\Switch_[$2, $3]; }
    +    | T_BREAK ';'                                           { $$ = Stmt\Break_[null]; }
    +    | T_BREAK expr ';'                                      { $$ = Stmt\Break_[$2]; }
    +    | T_CONTINUE ';'                                        { $$ = Stmt\Continue_[null]; }
    +    | T_CONTINUE expr ';'                                   { $$ = Stmt\Continue_[$2]; }
    +    | T_RETURN ';'                                          { $$ = Stmt\Return_[null]; }
    +    | T_RETURN expr ';'                                     { $$ = Stmt\Return_[$2]; }
    +    | yield_expr ';'                                        { $$ = $1; }
    +    | T_GLOBAL global_var_list ';'                          { $$ = Stmt\Global_[$2]; }
    +    | T_STATIC static_var_list ';'                          { $$ = Stmt\Static_[$2]; }
    +    | T_ECHO expr_list ';'                                  { $$ = Stmt\Echo_[$2]; }
    +    | T_INLINE_HTML                                         { $$ = Stmt\InlineHTML[$1]; }
    +    | expr ';'                                              { $$ = $1; }
    +    | T_UNSET '(' variables_list ')' ';'                    { $$ = Stmt\Unset_[$3]; }
    +    | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
    +          { $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
    +    | T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
    +          { $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
    +    | T_DECLARE '(' declare_list ')' declare_statement      { $$ = Stmt\Declare_[$3, $5]; }
    +    | T_TRY '{' inner_statement_list '}' catches optional_finally
    +          { $$ = Stmt\TryCatch[$3, $5, $6]; }
    +    | T_THROW expr ';'                                      { $$ = Stmt\Throw_[$2]; }
    +    | T_GOTO T_STRING ';'                                   { $$ = Stmt\Goto_[$2]; }
    +    | T_STRING ':'                                          { $$ = Stmt\Label[$1]; }
    +    | expr error                                            { $$ = $1; }
    +    | error                                                 { $$ = array(); /* means: no statement */ }
    +;
    +
    +statement:
    +      non_empty_statement                                   { $$ = $1; }
    +    | ';'
    +          { makeNop($$, $this->startAttributeStack[#1]);
    +            if ($$ === null) $$ = array(); /* means: no statement */ }
    +;
    +
    +catches:
    +      /* empty */                                           { init(); }
    +    | catches catch                                         { push($1, $2); }
    +;
    +
    +catch:
    +    T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
    +        { $$ = Stmt\Catch_[$3, parseVar($4), $7]; }
    +;
    +
    +optional_finally:
    +      /* empty */                                           { $$ = null; }
    +    | T_FINALLY '{' inner_statement_list '}'                { $$ = $3; }
    +;
    +
    +variables_list:
    +      variable                                              { init($1); }
    +    | variables_list ',' variable                           { push($1, $3); }
    +;
    +
    +optional_ref:
    +      /* empty */                                           { $$ = false; }
    +    | '&'                                                   { $$ = true; }
    +;
    +
    +optional_ellipsis:
    +      /* empty */                                           { $$ = false; }
    +    | T_ELLIPSIS                                            { $$ = true; }
    +;
    +
    +function_declaration_statement:
    +    T_FUNCTION optional_ref T_STRING '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
    +        { $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
    +;
    +
    +class_declaration_statement:
    +      class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
    +          { $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; }
    +    | T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
    +          { $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; }
    +    | T_TRAIT T_STRING '{' class_statement_list '}'
    +          { $$ = Stmt\Trait_[$2, $4]; }
    +;
    +
    +class_entry_type:
    +      T_CLASS                                               { $$ = 0; }
    +    | T_ABSTRACT T_CLASS                                    { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
    +    | T_FINAL T_CLASS                                       { $$ = Stmt\Class_::MODIFIER_FINAL; }
    +;
    +
    +extends_from:
    +      /* empty */                                           { $$ = null; }
    +    | T_EXTENDS name                                        { $$ = $2; }
    +;
    +
    +interface_extends_list:
    +      /* empty */                                           { $$ = array(); }
    +    | T_EXTENDS name_list                                   { $$ = $2; }
    +;
    +
    +implements_list:
    +      /* empty */                                           { $$ = array(); }
    +    | T_IMPLEMENTS name_list                                { $$ = $2; }
    +;
    +
    +name_list:
    +      name                                                  { init($1); }
    +    | name_list ',' name                                    { push($1, $3); }
    +;
    +
    +for_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDFOR ';'                 { $$ = $2; }
    +;
    +
    +foreach_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDFOREACH ';'             { $$ = $2; }
    +;
    +
    +declare_statement:
    +      non_empty_statement                                   { $$ = toArray($1); }
    +    | ';'                                                   { $$ = null; }
    +    | ':' inner_statement_list T_ENDDECLARE ';'             { $$ = $2; }
    +;
    +
    +declare_list:
    +      declare_list_element                                  { init($1); }
    +    | declare_list ',' declare_list_element                 { push($1, $3); }
    +;
    +
    +declare_list_element:
    +      T_STRING '=' static_scalar                            { $$ = Stmt\DeclareDeclare[$1, $3]; }
    +;
    +
    +switch_case_list:
    +      '{' case_list '}'                                     { $$ = $2; }
    +    | '{' ';' case_list '}'                                 { $$ = $3; }
    +    | ':' case_list T_ENDSWITCH ';'                         { $$ = $2; }
    +    | ':' ';' case_list T_ENDSWITCH ';'                     { $$ = $3; }
    +;
    +
    +case_list:
    +      /* empty */                                           { init(); }
    +    | case_list case                                        { push($1, $2); }
    +;
    +
    +case:
    +      T_CASE expr case_separator inner_statement_list       { $$ = Stmt\Case_[$2, $4]; }
    +    | T_DEFAULT case_separator inner_statement_list         { $$ = Stmt\Case_[null, $3]; }
    +;
    +
    +case_separator:
    +      ':'
    +    | ';'
    +;
    +
    +while_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDWHILE ';'               { $$ = $2; }
    +;
    +
    +elseif_list:
    +      /* empty */                                           { init(); }
    +    | elseif_list elseif                                    { push($1, $2); }
    +;
    +
    +elseif:
    +      T_ELSEIF parentheses_expr statement                   { $$ = Stmt\ElseIf_[$2, toArray($3)]; }
    +;
    +
    +new_elseif_list:
    +      /* empty */                                           { init(); }
    +    | new_elseif_list new_elseif                            { push($1, $2); }
    +;
    +
    +new_elseif:
    +     T_ELSEIF parentheses_expr ':' inner_statement_list     { $$ = Stmt\ElseIf_[$2, $4]; }
    +;
    +
    +else_single:
    +      /* empty */                                           { $$ = null; }
    +    | T_ELSE statement                                      { $$ = Stmt\Else_[toArray($2)]; }
    +;
    +
    +new_else_single:
    +      /* empty */                                           { $$ = null; }
    +    | T_ELSE ':' inner_statement_list                       { $$ = Stmt\Else_[$3]; }
    +;
    +
    +foreach_variable:
    +      variable                                              { $$ = array($1, false); }
    +    | '&' variable                                          { $$ = array($2, true); }
    +    | list_expr                                             { $$ = array($1, false); }
    +;
    +
    +parameter_list:
    +      non_empty_parameter_list                              { $$ = $1; }
    +    | /* empty */                                           { $$ = array(); }
    +;
    +
    +non_empty_parameter_list:
    +      parameter                                             { init($1); }
    +    | non_empty_parameter_list ',' parameter                { push($1, $3); }
    +;
    +
    +parameter:
    +      optional_param_type optional_ref optional_ellipsis T_VARIABLE
    +          { $$ = Node\Param[parseVar($4), null, $1, $2, $3]; }
    +    | optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' static_scalar
    +          { $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; }
    +;
    +
    +type:
    +      name                                                  { $$ = $1; }
    +    | T_ARRAY                                               { $$ = 'array'; }
    +    | T_CALLABLE                                            { $$ = 'callable'; }
    +;
    +
    +optional_param_type:
    +      /* empty */                                           { $$ = null; }
    +    | type                                                  { $$ = $1; }
    +;
    +
    +optional_return_type:
    +      /* empty */                                           { $$ = null; }
    +    | ':' type                                              { $$ = $2; }
    +;
    +
    +argument_list:
    +      '(' ')'                                               { $$ = array(); }
    +    | '(' non_empty_argument_list ')'                       { $$ = $2; }
    +    | '(' yield_expr ')'                                    { $$ = array(Node\Arg[$2, false, false]); }
    +;
    +
    +non_empty_argument_list:
    +      argument                                              { init($1); }
    +    | non_empty_argument_list ',' argument                  { push($1, $3); }
    +;
    +
    +argument:
    +      expr                                                  { $$ = Node\Arg[$1, false, false]; }
    +    | '&' variable                                          { $$ = Node\Arg[$2, true, false]; }
    +    | T_ELLIPSIS expr                                       { $$ = Node\Arg[$2, false, true]; }
    +;
    +
    +global_var_list:
    +      global_var_list ',' global_var                        { push($1, $3); }
    +    | global_var                                            { init($1); }
    +;
    +
    +global_var:
    +      T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    +    | '$' variable                                          { $$ = Expr\Variable[$2]; }
    +    | '$' '{' expr '}'                                      { $$ = Expr\Variable[$3]; }
    +;
    +
    +static_var_list:
    +      static_var_list ',' static_var                        { push($1, $3); }
    +    | static_var                                            { init($1); }
    +;
    +
    +static_var:
    +      T_VARIABLE                                            { $$ = Stmt\StaticVar[parseVar($1), null]; }
    +    | T_VARIABLE '=' static_scalar                          { $$ = Stmt\StaticVar[parseVar($1), $3]; }
    +;
    +
    +class_statement_list:
    +      class_statement_list class_statement                  { push($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +class_statement:
    +      variable_modifiers property_declaration_list ';'      { $$ = Stmt\Property[$1, $2]; }
    +    | T_CONST class_const_list ';'                          { $$ = Stmt\ClassConst[$2]; }
    +    | method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
    +          { $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]]; }
    +    | T_USE name_list trait_adaptations                     { $$ = Stmt\TraitUse[$2, $3]; }
    +;
    +
    +trait_adaptations:
    +      ';'                                                   { $$ = array(); }
    +    | '{' trait_adaptation_list '}'                         { $$ = $2; }
    +;
    +
    +trait_adaptation_list:
    +      /* empty */                                           { init(); }
    +    | trait_adaptation_list trait_adaptation                { push($1, $2); }
    +;
    +
    +trait_adaptation:
    +      trait_method_reference_fully_qualified T_INSTEADOF name_list ';'
    +          { $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
    +    | trait_method_reference T_AS member_modifier identifier ';'
    +          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
    +    | trait_method_reference T_AS member_modifier ';'
    +          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
    +    | trait_method_reference T_AS T_STRING ';'
    +          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
    +    | trait_method_reference T_AS reserved_non_modifiers ';'
    +          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
    +;
    +
    +trait_method_reference_fully_qualified:
    +      name T_PAAMAYIM_NEKUDOTAYIM identifier                { $$ = array($1, $3); }
    +;
    +trait_method_reference:
    +      trait_method_reference_fully_qualified                { $$ = $1; }
    +    | identifier                                            { $$ = array(null, $1); }
    +;
    +
    +method_body:
    +      ';' /* abstract method */                             { $$ = null; }
    +    | '{' inner_statement_list '}'                          { $$ = $2; }
    +;
    +
    +variable_modifiers:
    +      non_empty_member_modifiers                            { $$ = $1; }
    +    | T_VAR                                                 { $$ = 0; }
    +;
    +
    +method_modifiers:
    +      /* empty */                                           { $$ = 0; }
    +    | non_empty_member_modifiers                            { $$ = $1; }
    +;
    +
    +non_empty_member_modifiers:
    +      member_modifier                                       { $$ = $1; }
    +    | non_empty_member_modifiers member_modifier            { Stmt\Class_::verifyModifier($1, $2); $$ = $1 | $2; }
    +;
    +
    +member_modifier:
    +      T_PUBLIC                                              { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
    +    | T_PROTECTED                                           { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
    +    | T_PRIVATE                                             { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
    +    | T_STATIC                                              { $$ = Stmt\Class_::MODIFIER_STATIC; }
    +    | T_ABSTRACT                                            { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
    +    | T_FINAL                                               { $$ = Stmt\Class_::MODIFIER_FINAL; }
    +;
    +
    +property_declaration_list:
    +      property_declaration                                  { init($1); }
    +    | property_declaration_list ',' property_declaration    { push($1, $3); }
    +;
    +
    +property_declaration:
    +      T_VARIABLE                                            { $$ = Stmt\PropertyProperty[parseVar($1), null]; }
    +    | T_VARIABLE '=' static_scalar                          { $$ = Stmt\PropertyProperty[parseVar($1), $3]; }
    +;
    +
    +expr_list:
    +      expr_list ',' expr                                    { push($1, $3); }
    +    | expr                                                  { init($1); }
    +;
    +
    +for_expr:
    +      /* empty */                                           { $$ = array(); }
    +    | expr_list                                             { $$ = $1; }
    +;
    +
    +expr:
    +      variable                                              { $$ = $1; }
    +    | list_expr '=' expr                                    { $$ = Expr\Assign[$1, $3]; }
    +    | variable '=' expr                                     { $$ = Expr\Assign[$1, $3]; }
    +    | variable '=' '&' variable                             { $$ = Expr\AssignRef[$1, $4]; }
    +    | variable '=' '&' new_expr                             { $$ = Expr\AssignRef[$1, $4]; }
    +    | new_expr                                              { $$ = $1; }
    +    | T_CLONE expr                                          { $$ = Expr\Clone_[$2]; }
    +    | variable T_PLUS_EQUAL expr                            { $$ = Expr\AssignOp\Plus      [$1, $3]; }
    +    | variable T_MINUS_EQUAL expr                           { $$ = Expr\AssignOp\Minus     [$1, $3]; }
    +    | variable T_MUL_EQUAL expr                             { $$ = Expr\AssignOp\Mul       [$1, $3]; }
    +    | variable T_DIV_EQUAL expr                             { $$ = Expr\AssignOp\Div       [$1, $3]; }
    +    | variable T_CONCAT_EQUAL expr                          { $$ = Expr\AssignOp\Concat    [$1, $3]; }
    +    | variable T_MOD_EQUAL expr                             { $$ = Expr\AssignOp\Mod       [$1, $3]; }
    +    | variable T_AND_EQUAL expr                             { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; }
    +    | variable T_OR_EQUAL expr                              { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; }
    +    | variable T_XOR_EQUAL expr                             { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; }
    +    | variable T_SL_EQUAL expr                              { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
    +    | variable T_SR_EQUAL expr                              { $$ = Expr\AssignOp\ShiftRight[$1, $3]; }
    +    | variable T_POW_EQUAL expr                             { $$ = Expr\AssignOp\Pow       [$1, $3]; }
    +    | variable T_INC                                        { $$ = Expr\PostInc[$1]; }
    +    | T_INC variable                                        { $$ = Expr\PreInc [$2]; }
    +    | variable T_DEC                                        { $$ = Expr\PostDec[$1]; }
    +    | T_DEC variable                                        { $$ = Expr\PreDec [$2]; }
    +    | expr T_BOOLEAN_OR expr                                { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
    +    | expr T_BOOLEAN_AND expr                               { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
    +    | expr T_LOGICAL_OR expr                                { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
    +    | expr T_LOGICAL_AND expr                               { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
    +    | expr T_LOGICAL_XOR expr                               { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
    +    | expr '|' expr                                         { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
    +    | expr '&' expr                                         { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
    +    | expr '^' expr                                         { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
    +    | expr '.' expr                                         { $$ = Expr\BinaryOp\Concat    [$1, $3]; }
    +    | expr '+' expr                                         { $$ = Expr\BinaryOp\Plus      [$1, $3]; }
    +    | expr '-' expr                                         { $$ = Expr\BinaryOp\Minus     [$1, $3]; }
    +    | expr '*' expr                                         { $$ = Expr\BinaryOp\Mul       [$1, $3]; }
    +    | expr '/' expr                                         { $$ = Expr\BinaryOp\Div       [$1, $3]; }
    +    | expr '%' expr                                         { $$ = Expr\BinaryOp\Mod       [$1, $3]; }
    +    | expr T_SL expr                                        { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
    +    | expr T_SR expr                                        { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
    +    | expr T_POW expr                                       { $$ = Expr\BinaryOp\Pow       [$1, $3]; }
    +    | '+' expr %prec T_INC                                  { $$ = Expr\UnaryPlus [$2]; }
    +    | '-' expr %prec T_INC                                  { $$ = Expr\UnaryMinus[$2]; }
    +    | '!' expr                                              { $$ = Expr\BooleanNot[$2]; }
    +    | '~' expr                                              { $$ = Expr\BitwiseNot[$2]; }
    +    | expr T_IS_IDENTICAL expr                              { $$ = Expr\BinaryOp\Identical     [$1, $3]; }
    +    | expr T_IS_NOT_IDENTICAL expr                          { $$ = Expr\BinaryOp\NotIdentical  [$1, $3]; }
    +    | expr T_IS_EQUAL expr                                  { $$ = Expr\BinaryOp\Equal         [$1, $3]; }
    +    | expr T_IS_NOT_EQUAL expr                              { $$ = Expr\BinaryOp\NotEqual      [$1, $3]; }
    +    | expr T_SPACESHIP expr                                 { $$ = Expr\BinaryOp\Spaceship     [$1, $3]; }
    +    | expr '<' expr                                         { $$ = Expr\BinaryOp\Smaller       [$1, $3]; }
    +    | expr T_IS_SMALLER_OR_EQUAL expr                       { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
    +    | expr '>' expr                                         { $$ = Expr\BinaryOp\Greater       [$1, $3]; }
    +    | expr T_IS_GREATER_OR_EQUAL expr                       { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
    +    | expr T_INSTANCEOF class_name_reference                { $$ = Expr\Instanceof_[$1, $3]; }
    +    | parentheses_expr                                      { $$ = $1; }
    +    /* we need a separate '(' new_expr ')' rule to avoid problems caused by a s/r conflict */
    +    | '(' new_expr ')'                                      { $$ = $2; }
    +    | expr '?' expr ':' expr                                { $$ = Expr\Ternary[$1, $3,   $5]; }
    +    | expr '?' ':' expr                                     { $$ = Expr\Ternary[$1, null, $4]; }
    +    | expr T_COALESCE expr                                  { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }
    +    | T_ISSET '(' variables_list ')'                        { $$ = Expr\Isset_[$3]; }
    +    | T_EMPTY '(' expr ')'                                  { $$ = Expr\Empty_[$3]; }
    +    | T_INCLUDE expr                                        { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; }
    +    | T_INCLUDE_ONCE expr                                   { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; }
    +    | T_EVAL parentheses_expr                               { $$ = Expr\Eval_[$2]; }
    +    | T_REQUIRE expr                                        { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
    +    | T_REQUIRE_ONCE expr                                   { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
    +    | T_INT_CAST expr                                       { $$ = Expr\Cast\Int_    [$2]; }
    +    | T_DOUBLE_CAST expr                                    { $$ = Expr\Cast\Double  [$2]; }
    +    | T_STRING_CAST expr                                    { $$ = Expr\Cast\String_ [$2]; }
    +    | T_ARRAY_CAST expr                                     { $$ = Expr\Cast\Array_  [$2]; }
    +    | T_OBJECT_CAST expr                                    { $$ = Expr\Cast\Object_ [$2]; }
    +    | T_BOOL_CAST expr                                      { $$ = Expr\Cast\Bool_   [$2]; }
    +    | T_UNSET_CAST expr                                     { $$ = Expr\Cast\Unset_  [$2]; }
    +    | T_EXIT exit_expr
    +          { $attrs = attributes();
    +            $attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
    +            $$ = new Expr\Exit_($2, $attrs); }
    +    | '@' expr                                              { $$ = Expr\ErrorSuppress[$2]; }
    +    | scalar                                                { $$ = $1; }
    +    | array_expr                                            { $$ = $1; }
    +    | scalar_dereference                                    { $$ = $1; }
    +    | '`' backticks_expr '`'                                { $$ = Expr\ShellExec[$2]; }
    +    | T_PRINT expr                                          { $$ = Expr\Print_[$2]; }
    +    | T_YIELD                                               { $$ = Expr\Yield_[null, null]; }
    +    | T_YIELD_FROM expr                                     { $$ = Expr\YieldFrom[$2]; }
    +    | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
    +      '{' inner_statement_list '}'
    +          { $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $9]]; }
    +    | T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
    +      '{' inner_statement_list '}'
    +          { $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $10]]; }
    +;
    +
    +parentheses_expr:
    +      '(' expr ')'                                          { $$ = $2; }
    +    | '(' yield_expr ')'                                    { $$ = $2; }
    +;
    +
    +yield_expr:
    +      T_YIELD expr                                          { $$ = Expr\Yield_[$2, null]; }
    +    | T_YIELD expr T_DOUBLE_ARROW expr                      { $$ = Expr\Yield_[$4, $2]; }
    +;
    +
    +array_expr:
    +      T_ARRAY '(' array_pair_list ')'
    +          { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
    +            $$ = new Expr\Array_($3, $attrs); }
    +    | '[' array_pair_list ']'
    +          { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT;
    +            $$ = new Expr\Array_($2, $attrs); }
    +;
    +
    +scalar_dereference:
    +      array_expr '[' dim_offset ']'                         { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
    +          { $attrs = attributes(); $attrs['kind'] = strKind($1);
    +            $$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
    +    | constant '[' dim_offset ']'                           { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | scalar_dereference '[' dim_offset ']'                 { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    /* alternative array syntax missing intentionally */
    +;
    +
    +anonymous_class:
    +      T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
    +          { $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2); }
    +
    +new_expr:
    +      T_NEW class_name_reference ctor_arguments             { $$ = Expr\New_[$2, $3]; }
    +    | T_NEW anonymous_class
    +          { list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
    +;
    +
    +lexical_vars:
    +      /* empty */                                           { $$ = array(); }
    +    | T_USE '(' lexical_var_list ')'                        { $$ = $3; }
    +;
    +
    +lexical_var_list:
    +      lexical_var                                           { init($1); }
    +    | lexical_var_list ',' lexical_var                      { push($1, $3); }
    +;
    +
    +lexical_var:
    +      optional_ref T_VARIABLE                               { $$ = Expr\ClosureUse[parseVar($2), $1]; }
    +;
    +
    +function_call:
    +      name argument_list                                    { $$ = Expr\FuncCall[$1, $2]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier argument_list
    +          { $$ = Expr\StaticCall[$1, $3, $4]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
    +          { $$ = Expr\StaticCall[$1, $4, $6]; }
    +    | static_property argument_list {
    +            if ($1 instanceof Node\Expr\StaticPropertyFetch) {
    +                $$ = Expr\StaticCall[$1->class, Expr\Variable[$1->name], $2];
    +            } elseif ($1 instanceof Node\Expr\ArrayDimFetch) {
    +                $tmp = $1;
    +                while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
    +                    $tmp = $tmp->var;
    +                }
    +
    +                $$ = Expr\StaticCall[$tmp->var->class, $1, $2];
    +                $tmp->var = Expr\Variable[$tmp->var->name];
    +            } else {
    +                throw new \Exception;
    +            }
    +          }
    +    | variable_without_objects argument_list
    +          { $$ = Expr\FuncCall[$1, $2]; }
    +    | function_call '[' dim_offset ']'                      { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +      /* alternative array syntax missing intentionally */
    +;
    +
    +class_name:
    +      T_STATIC                                              { $$ = Name[$1]; }
    +    | name                                                  { $$ = $1; }
    +;
    +
    +name:
    +      namespace_name_parts                                  { $$ = Name[$1]; }
    +    | T_NS_SEPARATOR namespace_name_parts                   { $$ = Name\FullyQualified[$2]; }
    +    | T_NAMESPACE T_NS_SEPARATOR namespace_name_parts       { $$ = Name\Relative[$3]; }
    +;
    +
    +class_name_reference:
    +      class_name                                            { $$ = $1; }
    +    | dynamic_class_name_reference                          { $$ = $1; }
    +;
    +
    +dynamic_class_name_reference:
    +      object_access_for_dcnr                                { $$ = $1; }
    +    | base_variable                                         { $$ = $1; }
    +;
    +
    +class_name_or_var:
    +      class_name                                            { $$ = $1; }
    +    | reference_variable                                    { $$ = $1; }
    +;
    +
    +object_access_for_dcnr:
    +      base_variable T_OBJECT_OPERATOR object_property
    +          { $$ = Expr\PropertyFetch[$1, $3]; }
    +    | object_access_for_dcnr T_OBJECT_OPERATOR object_property
    +          { $$ = Expr\PropertyFetch[$1, $3]; }
    +    | object_access_for_dcnr '[' dim_offset ']'             { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | object_access_for_dcnr '{' expr '}'                   { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +;
    +
    +exit_expr:
    +      /* empty */                                           { $$ = null; }
    +    | '(' ')'                                               { $$ = null; }
    +    | parentheses_expr                                      { $$ = $1; }
    +;
    +
    +backticks_expr:
    +      /* empty */                                           { $$ = array(); }
    +    | T_ENCAPSED_AND_WHITESPACE
    +          { $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`', false)]); }
    +    | encaps_list                                           { parseEncapsed($1, '`', false); $$ = $1; }
    +;
    +
    +ctor_arguments:
    +      /* empty */                                           { $$ = array(); }
    +    | argument_list                                         { $$ = $1; }
    +;
    +
    +common_scalar:
    +      T_LNUMBER                                             { $$ = Scalar\LNumber::fromString($1, attributes(), true); }
    +    | T_DNUMBER                                             { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
    +    | T_CONSTANT_ENCAPSED_STRING
    +          { $attrs = attributes(); $attrs['kind'] = strKind($1);
    +            $$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
    +    | T_LINE                                                { $$ = Scalar\MagicConst\Line[]; }
    +    | T_FILE                                                { $$ = Scalar\MagicConst\File[]; }
    +    | T_DIR                                                 { $$ = Scalar\MagicConst\Dir[]; }
    +    | T_CLASS_C                                             { $$ = Scalar\MagicConst\Class_[]; }
    +    | T_TRAIT_C                                             { $$ = Scalar\MagicConst\Trait_[]; }
    +    | T_METHOD_C                                            { $$ = Scalar\MagicConst\Method[]; }
    +    | T_FUNC_C                                              { $$ = Scalar\MagicConst\Function_[]; }
    +    | T_NS_C                                                { $$ = Scalar\MagicConst\Namespace_[]; }
    +    | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
    +          { $attrs = attributes(); setDocStringAttrs($attrs, $1);
    +            $$ = new Scalar\String_(Scalar\String_::parseDocString($1, $2, false), $attrs); }
    +    | T_START_HEREDOC T_END_HEREDOC
    +          { $attrs = attributes(); setDocStringAttrs($attrs, $1);
    +            $$ = new Scalar\String_('', $attrs); }
    +;
    +
    +static_scalar:
    +      common_scalar                                         { $$ = $1; }
    +    | class_name T_PAAMAYIM_NEKUDOTAYIM identifier          { $$ = Expr\ClassConstFetch[$1, $3]; }
    +    | name                                                  { $$ = Expr\ConstFetch[$1]; }
    +    | T_ARRAY '(' static_array_pair_list ')'                { $$ = Expr\Array_[$3]; }
    +    | '[' static_array_pair_list ']'                        { $$ = Expr\Array_[$2]; }
    +    | static_operation                                      { $$ = $1; }
    +;
    +
    +static_operation:
    +      static_scalar T_BOOLEAN_OR static_scalar              { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
    +    | static_scalar T_BOOLEAN_AND static_scalar             { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
    +    | static_scalar T_LOGICAL_OR static_scalar              { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
    +    | static_scalar T_LOGICAL_AND static_scalar             { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
    +    | static_scalar T_LOGICAL_XOR static_scalar             { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
    +    | static_scalar '|' static_scalar                       { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
    +    | static_scalar '&' static_scalar                       { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
    +    | static_scalar '^' static_scalar                       { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
    +    | static_scalar '.' static_scalar                       { $$ = Expr\BinaryOp\Concat    [$1, $3]; }
    +    | static_scalar '+' static_scalar                       { $$ = Expr\BinaryOp\Plus      [$1, $3]; }
    +    | static_scalar '-' static_scalar                       { $$ = Expr\BinaryOp\Minus     [$1, $3]; }
    +    | static_scalar '*' static_scalar                       { $$ = Expr\BinaryOp\Mul       [$1, $3]; }
    +    | static_scalar '/' static_scalar                       { $$ = Expr\BinaryOp\Div       [$1, $3]; }
    +    | static_scalar '%' static_scalar                       { $$ = Expr\BinaryOp\Mod       [$1, $3]; }
    +    | static_scalar T_SL static_scalar                      { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
    +    | static_scalar T_SR static_scalar                      { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
    +    | static_scalar T_POW static_scalar                     { $$ = Expr\BinaryOp\Pow       [$1, $3]; }
    +    | '+' static_scalar %prec T_INC                         { $$ = Expr\UnaryPlus [$2]; }
    +    | '-' static_scalar %prec T_INC                         { $$ = Expr\UnaryMinus[$2]; }
    +    | '!' static_scalar                                     { $$ = Expr\BooleanNot[$2]; }
    +    | '~' static_scalar                                     { $$ = Expr\BitwiseNot[$2]; }
    +    | static_scalar T_IS_IDENTICAL static_scalar            { $$ = Expr\BinaryOp\Identical     [$1, $3]; }
    +    | static_scalar T_IS_NOT_IDENTICAL static_scalar        { $$ = Expr\BinaryOp\NotIdentical  [$1, $3]; }
    +    | static_scalar T_IS_EQUAL static_scalar                { $$ = Expr\BinaryOp\Equal         [$1, $3]; }
    +    | static_scalar T_IS_NOT_EQUAL static_scalar            { $$ = Expr\BinaryOp\NotEqual      [$1, $3]; }
    +    | static_scalar '<' static_scalar                       { $$ = Expr\BinaryOp\Smaller       [$1, $3]; }
    +    | static_scalar T_IS_SMALLER_OR_EQUAL static_scalar     { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
    +    | static_scalar '>' static_scalar                       { $$ = Expr\BinaryOp\Greater       [$1, $3]; }
    +    | static_scalar T_IS_GREATER_OR_EQUAL static_scalar     { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
    +    | static_scalar '?' static_scalar ':' static_scalar     { $$ = Expr\Ternary[$1, $3,   $5]; }
    +    | static_scalar '?' ':' static_scalar                   { $$ = Expr\Ternary[$1, null, $4]; }
    +    | static_scalar '[' static_scalar ']'                   { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | '(' static_scalar ')'                                 { $$ = $2; }
    +;
    +
    +constant:
    +      name                                                  { $$ = Expr\ConstFetch[$1]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier
    +          { $$ = Expr\ClassConstFetch[$1, $3]; }
    +;
    +
    +scalar:
    +      common_scalar                                         { $$ = $1; }
    +    | constant                                              { $$ = $1; }
    +    | '"' encaps_list '"'
    +          { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
    +            parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
    +    | T_START_HEREDOC encaps_list T_END_HEREDOC
    +          { $attrs = attributes(); setDocStringAttrs($attrs, $1);
    +            parseEncapsedDoc($2, true); $$ = new Scalar\Encapsed($2, $attrs); }
    +;
    +
    +static_array_pair_list:
    +      /* empty */                                           { $$ = array(); }
    +    | non_empty_static_array_pair_list optional_comma       { $$ = $1; }
    +;
    +
    +optional_comma:
    +      /* empty */
    +    | ','
    +;
    +
    +non_empty_static_array_pair_list:
    +      non_empty_static_array_pair_list ',' static_array_pair { push($1, $3); }
    +    | static_array_pair                                      { init($1); }
    +;
    +
    +static_array_pair:
    +      static_scalar T_DOUBLE_ARROW static_scalar            { $$ = Expr\ArrayItem[$3, $1,   false]; }
    +    | static_scalar                                         { $$ = Expr\ArrayItem[$1, null, false]; }
    +;
    +
    +variable:
    +      object_access                                         { $$ = $1; }
    +    | base_variable                                         { $$ = $1; }
    +    | function_call                                         { $$ = $1; }
    +    | new_expr_array_deref                                  { $$ = $1; }
    +;
    +
    +new_expr_array_deref:
    +      '(' new_expr ')' '[' dim_offset ']'                   { $$ = Expr\ArrayDimFetch[$2, $5]; }
    +    | new_expr_array_deref '[' dim_offset ']'               { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +      /* alternative array syntax missing intentionally */
    +;
    +
    +object_access:
    +      variable_or_new_expr T_OBJECT_OPERATOR object_property
    +          { $$ = Expr\PropertyFetch[$1, $3]; }
    +    | variable_or_new_expr T_OBJECT_OPERATOR object_property argument_list
    +          { $$ = Expr\MethodCall[$1, $3, $4]; }
    +    | object_access argument_list                           { $$ = Expr\FuncCall[$1, $2]; }
    +    | object_access '[' dim_offset ']'                      { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | object_access '{' expr '}'                            { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +;
    +
    +variable_or_new_expr:
    +      variable                                              { $$ = $1; }
    +    | '(' new_expr ')'                                      { $$ = $2; }
    +;
    +
    +variable_without_objects:
    +      reference_variable                                    { $$ = $1; }
    +    | '$' variable_without_objects                          { $$ = Expr\Variable[$2]; }
    +;
    +
    +base_variable:
    +      variable_without_objects                              { $$ = $1; }
    +    | static_property                                       { $$ = $1; }
    +;
    +
    +static_property:
    +      class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' reference_variable
    +          { $$ = Expr\StaticPropertyFetch[$1, $4]; }
    +    | static_property_with_arrays                           { $$ = $1; }
    +;
    +
    +static_property_with_arrays:
    +      class_name_or_var T_PAAMAYIM_NEKUDOTAYIM T_VARIABLE
    +          { $$ = Expr\StaticPropertyFetch[$1, parseVar($3)]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' '{' expr '}'
    +          { $$ = Expr\StaticPropertyFetch[$1, $5]; }
    +    | static_property_with_arrays '[' dim_offset ']'        { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | static_property_with_arrays '{' expr '}'              { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +;
    +
    +reference_variable:
    +      reference_variable '[' dim_offset ']'                 { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | reference_variable '{' expr '}'                       { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    +    | '$' '{' expr '}'                                      { $$ = Expr\Variable[$3]; }
    +;
    +
    +dim_offset:
    +      /* empty */                                           { $$ = null; }
    +    | expr                                                  { $$ = $1; }
    +;
    +
    +object_property:
    +      T_STRING                                              { $$ = $1; }
    +    | '{' expr '}'                                          { $$ = $2; }
    +    | variable_without_objects                              { $$ = $1; }
    +;
    +
    +list_expr:
    +      T_LIST '(' list_expr_elements ')'                     { $$ = Expr\List_[$3]; }
    +;
    +
    +list_expr_elements:
    +      list_expr_elements ',' list_expr_element              { push($1, $3); }
    +    | list_expr_element                                     { init($1); }
    +;
    +
    +list_expr_element:
    +      variable                                              { $$ = $1; }
    +    | list_expr                                             { $$ = $1; }
    +    | /* empty */                                           { $$ = null; }
    +;
    +
    +array_pair_list:
    +      /* empty */                                           { $$ = array(); }
    +    | non_empty_array_pair_list optional_comma              { $$ = $1; }
    +;
    +
    +non_empty_array_pair_list:
    +      non_empty_array_pair_list ',' array_pair              { push($1, $3); }
    +    | array_pair                                            { init($1); }
    +;
    +
    +array_pair:
    +      expr T_DOUBLE_ARROW expr                              { $$ = Expr\ArrayItem[$3, $1,   false]; }
    +    | expr                                                  { $$ = Expr\ArrayItem[$1, null, false]; }
    +    | expr T_DOUBLE_ARROW '&' variable                      { $$ = Expr\ArrayItem[$4, $1,   true]; }
    +    | '&' variable                                          { $$ = Expr\ArrayItem[$2, null, true]; }
    +;
    +
    +encaps_list:
    +      encaps_list encaps_var                                { push($1, $2); }
    +    | encaps_list encaps_string_part                        { push($1, $2); }
    +    | encaps_var                                            { init($1); }
    +    | encaps_string_part encaps_var                         { init($1, $2); }
    +;
    +
    +encaps_string_part:
    +      T_ENCAPSED_AND_WHITESPACE                             { $$ = Scalar\EncapsedStringPart[$1]; }
    +;
    +
    +encaps_var:
    +      T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    +    | T_VARIABLE '[' encaps_var_offset ']'                  { $$ = Expr\ArrayDimFetch[Expr\Variable[parseVar($1)], $3]; }
    +    | T_VARIABLE T_OBJECT_OPERATOR T_STRING                 { $$ = Expr\PropertyFetch[Expr\Variable[parseVar($1)], $3]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES expr '}'                   { $$ = Expr\Variable[$2]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}'       { $$ = Expr\Variable[$2]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
    +          { $$ = Expr\ArrayDimFetch[Expr\Variable[$2], $4]; }
    +    | T_CURLY_OPEN variable '}'                             { $$ = $2; }
    +;
    +
    +encaps_var_offset:
    +      T_STRING                                              { $$ = Scalar\String_[$1]; }
    +    | T_NUM_STRING                                          { $$ = Scalar\String_[$1]; }
    +    | T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    +;
    +
    +%%
    diff --git a/core/vendor/nikic/php-parser/grammar/php7.y b/core/vendor/nikic/php-parser/grammar/php7.y
    new file mode 100644
    index 0000000..cf5f841
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/grammar/php7.y
    @@ -0,0 +1,846 @@
    +%pure_parser
    +%expect 2
    +
    +%tokens
    +
    +%%
    +
    +start:
    +    top_statement_list                                      { $$ = $this->handleNamespaces($1); }
    +;
    +
    +top_statement_list_ex:
    +      top_statement_list_ex top_statement                   { pushNormalizing($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +top_statement_list:
    +      top_statement_list_ex
    +          { makeNop($nop, $this->lookaheadStartAttributes);
    +            if ($nop !== null) { $1[] = $nop; } $$ = $1; }
    +;
    +
    +reserved_non_modifiers:
    +      T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
    +    | T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
    +    | T_ENDWHILE | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH
    +    | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
    +    | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
    +    | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
    +    | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER
    +;
    +
    +semi_reserved:
    +      reserved_non_modifiers
    +    | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
    +;
    +
    +identifier:
    +      T_STRING                                              { $$ = $1; }
    +    | semi_reserved                                         { $$ = $1; }
    +;
    +
    +namespace_name_parts:
    +      T_STRING                                              { init($1); }
    +    | namespace_name_parts T_NS_SEPARATOR T_STRING          { push($1, $3); }
    +;
    +
    +namespace_name:
    +      namespace_name_parts                                  { $$ = Name[$1]; }
    +;
    +
    +top_statement:
    +      statement                                             { $$ = $1; }
    +    | function_declaration_statement                        { $$ = $1; }
    +    | class_declaration_statement                           { $$ = $1; }
    +    | T_HALT_COMPILER
    +          { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
    +    | T_NAMESPACE namespace_name ';'                        { $$ = Stmt\Namespace_[$2, null]; }
    +    | T_NAMESPACE namespace_name '{' top_statement_list '}' { $$ = Stmt\Namespace_[$2, $4]; }
    +    | T_NAMESPACE '{' top_statement_list '}'                { $$ = Stmt\Namespace_[null,     $3]; }
    +    | T_USE use_declarations ';'                            { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
    +    | T_USE use_type use_declarations ';'                   { $$ = Stmt\Use_[$3, $2]; }
    +    | group_use_declaration ';'                             { $$ = $1; }
    +    | T_CONST constant_declaration_list ';'                 { $$ = Stmt\Const_[$2]; }
    +;
    +
    +use_type:
    +      T_FUNCTION                                            { $$ = Stmt\Use_::TYPE_FUNCTION; }
    +    | T_CONST                                               { $$ = Stmt\Use_::TYPE_CONSTANT; }
    +;
    +
    +/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
    +group_use_declaration:
    +      T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
    +          { $$ = Stmt\GroupUse[Name[$3], $6, $2]; }
    +    | T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
    +          { $$ = Stmt\GroupUse[Name[$4], $7, $2]; }
    +    | T_USE namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
    +          { $$ = Stmt\GroupUse[Name[$2], $5, Stmt\Use_::TYPE_UNKNOWN]; }
    +    | T_USE T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
    +          { $$ = Stmt\GroupUse[Name[$3], $6, Stmt\Use_::TYPE_UNKNOWN]; }
    +;
    +
    +unprefixed_use_declarations:
    +      unprefixed_use_declarations ',' unprefixed_use_declaration
    +          { push($1, $3); }
    +    | unprefixed_use_declaration                            { init($1); }
    +;
    +
    +use_declarations:
    +      use_declarations ',' use_declaration                  { push($1, $3); }
    +    | use_declaration                                       { init($1); }
    +;
    +
    +inline_use_declarations:
    +      inline_use_declarations ',' inline_use_declaration    { push($1, $3); }
    +    | inline_use_declaration                                { init($1); }
    +;
    +
    +unprefixed_use_declaration:
    +      namespace_name                                        { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; }
    +    | namespace_name T_AS T_STRING                          { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; }
    +;
    +
    +use_declaration:
    +      unprefixed_use_declaration                            { $$ = $1; }
    +    | T_NS_SEPARATOR unprefixed_use_declaration             { $$ = $2; }
    +;
    +
    +inline_use_declaration:
    +      unprefixed_use_declaration                            { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; }
    +    | use_type unprefixed_use_declaration                   { $$ = $2; $$->type = $1; }
    +;
    +
    +constant_declaration_list:
    +      constant_declaration_list ',' constant_declaration    { push($1, $3); }
    +    | constant_declaration                                  { init($1); }
    +;
    +
    +constant_declaration:
    +    T_STRING '=' expr                                       { $$ = Node\Const_[$1, $3]; }
    +;
    +
    +class_const_list:
    +      class_const_list ',' class_const                      { push($1, $3); }
    +    | class_const                                           { init($1); }
    +;
    +
    +class_const:
    +    identifier '=' expr                                     { $$ = Node\Const_[$1, $3]; }
    +;
    +
    +inner_statement_list_ex:
    +      inner_statement_list_ex inner_statement               { pushNormalizing($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +inner_statement_list:
    +      inner_statement_list_ex
    +          { makeNop($nop, $this->lookaheadStartAttributes);
    +            if ($nop !== null) { $1[] = $nop; } $$ = $1; }
    +;
    +
    +inner_statement:
    +      statement                                             { $$ = $1; }
    +    | function_declaration_statement                        { $$ = $1; }
    +    | class_declaration_statement                           { $$ = $1; }
    +    | T_HALT_COMPILER
    +          { throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
    +;
    +
    +non_empty_statement:
    +      '{' inner_statement_list '}'                          { $$ = $2; prependLeadingComments($$); }
    +    | T_IF '(' expr ')' statement elseif_list else_single
    +          { $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
    +    | T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
    +          { $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; }
    +    | T_WHILE '(' expr ')' while_statement                  { $$ = Stmt\While_[$3, $5]; }
    +    | T_DO statement T_WHILE '(' expr ')' ';'               { $$ = Stmt\Do_   [$5, toArray($2)]; }
    +    | T_FOR '(' for_expr ';'  for_expr ';' for_expr ')' for_statement
    +          { $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
    +    | T_SWITCH '(' expr ')' switch_case_list                { $$ = Stmt\Switch_[$3, $5]; }
    +    | T_BREAK optional_expr ';'                             { $$ = Stmt\Break_[$2]; }
    +    | T_CONTINUE optional_expr ';'                          { $$ = Stmt\Continue_[$2]; }
    +    | T_RETURN optional_expr ';'                            { $$ = Stmt\Return_[$2]; }
    +    | T_GLOBAL global_var_list ';'                          { $$ = Stmt\Global_[$2]; }
    +    | T_STATIC static_var_list ';'                          { $$ = Stmt\Static_[$2]; }
    +    | T_ECHO expr_list ';'                                  { $$ = Stmt\Echo_[$2]; }
    +    | T_INLINE_HTML                                         { $$ = Stmt\InlineHTML[$1]; }
    +    | expr ';'                                              { $$ = $1; }
    +    | T_UNSET '(' variables_list ')' ';'                    { $$ = Stmt\Unset_[$3]; }
    +    | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
    +          { $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
    +    | T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
    +          { $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
    +    | T_DECLARE '(' declare_list ')' declare_statement      { $$ = Stmt\Declare_[$3, $5]; }
    +    | T_TRY '{' inner_statement_list '}' catches optional_finally
    +          { $$ = Stmt\TryCatch[$3, $5, $6]; }
    +    | T_THROW expr ';'                                      { $$ = Stmt\Throw_[$2]; }
    +    | T_GOTO T_STRING ';'                                   { $$ = Stmt\Goto_[$2]; }
    +    | T_STRING ':'                                          { $$ = Stmt\Label[$1]; }
    +    | expr error                                            { $$ = $1; }
    +    | error                                                 { $$ = array(); /* means: no statement */ }
    +;
    +
    +statement:
    +      non_empty_statement                                   { $$ = $1; }
    +    | ';'
    +          { makeNop($$, $this->startAttributeStack[#1]);
    +            if ($$ === null) $$ = array(); /* means: no statement */ }
    +;
    +
    +catches:
    +      /* empty */                                           { init(); }
    +    | catches catch                                         { push($1, $2); }
    +;
    +
    +catch:
    +    T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
    +        { $$ = Stmt\Catch_[$3, parseVar($4), $7]; }
    +;
    +
    +optional_finally:
    +      /* empty */                                           { $$ = null; }
    +    | T_FINALLY '{' inner_statement_list '}'                { $$ = $3; }
    +;
    +
    +variables_list:
    +      variable                                              { init($1); }
    +    | variables_list ',' variable                           { push($1, $3); }
    +;
    +
    +optional_ref:
    +      /* empty */                                           { $$ = false; }
    +    | '&'                                                   { $$ = true; }
    +;
    +
    +optional_ellipsis:
    +      /* empty */                                           { $$ = false; }
    +    | T_ELLIPSIS                                            { $$ = true; }
    +;
    +
    +function_declaration_statement:
    +    T_FUNCTION optional_ref T_STRING '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
    +        { $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
    +;
    +
    +class_declaration_statement:
    +      class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
    +          { $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; }
    +    | T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
    +          { $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; }
    +    | T_TRAIT T_STRING '{' class_statement_list '}'
    +          { $$ = Stmt\Trait_[$2, $4]; }
    +;
    +
    +class_entry_type:
    +      T_CLASS                                               { $$ = 0; }
    +    | T_ABSTRACT T_CLASS                                    { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
    +    | T_FINAL T_CLASS                                       { $$ = Stmt\Class_::MODIFIER_FINAL; }
    +;
    +
    +extends_from:
    +      /* empty */                                           { $$ = null; }
    +    | T_EXTENDS name                                        { $$ = $2; }
    +;
    +
    +interface_extends_list:
    +      /* empty */                                           { $$ = array(); }
    +    | T_EXTENDS name_list                                   { $$ = $2; }
    +;
    +
    +implements_list:
    +      /* empty */                                           { $$ = array(); }
    +    | T_IMPLEMENTS name_list                                { $$ = $2; }
    +;
    +
    +name_list:
    +      name                                                  { init($1); }
    +    | name_list ',' name                                    { push($1, $3); }
    +;
    +
    +for_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDFOR ';'                 { $$ = $2; }
    +;
    +
    +foreach_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDFOREACH ';'             { $$ = $2; }
    +;
    +
    +declare_statement:
    +      non_empty_statement                                   { $$ = toArray($1); }
    +    | ';'                                                   { $$ = null; }
    +    | ':' inner_statement_list T_ENDDECLARE ';'             { $$ = $2; }
    +;
    +
    +declare_list:
    +      declare_list_element                                  { init($1); }
    +    | declare_list ',' declare_list_element                 { push($1, $3); }
    +;
    +
    +declare_list_element:
    +      T_STRING '=' expr                                     { $$ = Stmt\DeclareDeclare[$1, $3]; }
    +;
    +
    +switch_case_list:
    +      '{' case_list '}'                                     { $$ = $2; }
    +    | '{' ';' case_list '}'                                 { $$ = $3; }
    +    | ':' case_list T_ENDSWITCH ';'                         { $$ = $2; }
    +    | ':' ';' case_list T_ENDSWITCH ';'                     { $$ = $3; }
    +;
    +
    +case_list:
    +      /* empty */                                           { init(); }
    +    | case_list case                                        { push($1, $2); }
    +;
    +
    +case:
    +      T_CASE expr case_separator inner_statement_list       { $$ = Stmt\Case_[$2, $4]; }
    +    | T_DEFAULT case_separator inner_statement_list         { $$ = Stmt\Case_[null, $3]; }
    +;
    +
    +case_separator:
    +      ':'
    +    | ';'
    +;
    +
    +while_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDWHILE ';'               { $$ = $2; }
    +;
    +
    +elseif_list:
    +      /* empty */                                           { init(); }
    +    | elseif_list elseif                                    { push($1, $2); }
    +;
    +
    +elseif:
    +      T_ELSEIF '(' expr ')' statement                       { $$ = Stmt\ElseIf_[$3, toArray($5)]; }
    +;
    +
    +new_elseif_list:
    +      /* empty */                                           { init(); }
    +    | new_elseif_list new_elseif                            { push($1, $2); }
    +;
    +
    +new_elseif:
    +     T_ELSEIF '(' expr ')' ':' inner_statement_list         { $$ = Stmt\ElseIf_[$3, $6]; }
    +;
    +
    +else_single:
    +      /* empty */                                           { $$ = null; }
    +    | T_ELSE statement                                      { $$ = Stmt\Else_[toArray($2)]; }
    +;
    +
    +new_else_single:
    +      /* empty */                                           { $$ = null; }
    +    | T_ELSE ':' inner_statement_list                       { $$ = Stmt\Else_[$3]; }
    +;
    +
    +foreach_variable:
    +      variable                                              { $$ = array($1, false); }
    +    | '&' variable                                          { $$ = array($2, true); }
    +    | list_expr                                             { $$ = array($1, false); }
    +;
    +
    +parameter_list:
    +      non_empty_parameter_list                              { $$ = $1; }
    +    | /* empty */                                           { $$ = array(); }
    +;
    +
    +non_empty_parameter_list:
    +      parameter                                             { init($1); }
    +    | non_empty_parameter_list ',' parameter                { push($1, $3); }
    +;
    +
    +parameter:
    +      optional_param_type optional_ref optional_ellipsis T_VARIABLE
    +          { $$ = Node\Param[parseVar($4), null, $1, $2, $3]; }
    +    | optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' expr
    +          { $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; }
    +;
    +
    +type:
    +      name                                                  { $$ = $this->handleScalarTypes($1); }
    +    | T_ARRAY                                               { $$ = 'array'; }
    +    | T_CALLABLE                                            { $$ = 'callable'; }
    +;
    +
    +optional_param_type:
    +      /* empty */                                           { $$ = null; }
    +    | type                                                  { $$ = $1; }
    +;
    +
    +optional_return_type:
    +      /* empty */                                           { $$ = null; }
    +    | ':' type                                              { $$ = $2; }
    +;
    +
    +argument_list:
    +      '(' ')'                                               { $$ = array(); }
    +    | '(' non_empty_argument_list ')'                       { $$ = $2; }
    +;
    +
    +non_empty_argument_list:
    +      argument                                              { init($1); }
    +    | non_empty_argument_list ',' argument                  { push($1, $3); }
    +;
    +
    +argument:
    +      expr                                                  { $$ = Node\Arg[$1, false, false]; }
    +    | '&' variable                                          { $$ = Node\Arg[$2, true, false]; }
    +    | T_ELLIPSIS expr                                       { $$ = Node\Arg[$2, false, true]; }
    +;
    +
    +global_var_list:
    +      global_var_list ',' global_var                        { push($1, $3); }
    +    | global_var                                            { init($1); }
    +;
    +
    +global_var:
    +      simple_variable                                       { $$ = Expr\Variable[$1]; }
    +;
    +
    +static_var_list:
    +      static_var_list ',' static_var                        { push($1, $3); }
    +    | static_var                                            { init($1); }
    +;
    +
    +static_var:
    +      T_VARIABLE                                            { $$ = Stmt\StaticVar[parseVar($1), null]; }
    +    | T_VARIABLE '=' expr                                   { $$ = Stmt\StaticVar[parseVar($1), $3]; }
    +;
    +
    +class_statement_list:
    +      class_statement_list class_statement                  { push($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +class_statement:
    +      variable_modifiers property_declaration_list ';'      { $$ = Stmt\Property[$1, $2]; }
    +    | T_CONST class_const_list ';'                          { $$ = Stmt\ClassConst[$2]; }
    +    | method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
    +          { $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]]; }
    +    | T_USE name_list trait_adaptations                     { $$ = Stmt\TraitUse[$2, $3]; }
    +;
    +
    +trait_adaptations:
    +      ';'                                                   { $$ = array(); }
    +    | '{' trait_adaptation_list '}'                         { $$ = $2; }
    +;
    +
    +trait_adaptation_list:
    +      /* empty */                                           { init(); }
    +    | trait_adaptation_list trait_adaptation                { push($1, $2); }
    +;
    +
    +trait_adaptation:
    +      trait_method_reference_fully_qualified T_INSTEADOF name_list ';'
    +          { $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
    +    | trait_method_reference T_AS member_modifier identifier ';'
    +          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
    +    | trait_method_reference T_AS member_modifier ';'
    +          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
    +    | trait_method_reference T_AS T_STRING ';'
    +          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
    +    | trait_method_reference T_AS reserved_non_modifiers ';'
    +          { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
    +;
    +
    +trait_method_reference_fully_qualified:
    +      name T_PAAMAYIM_NEKUDOTAYIM identifier                { $$ = array($1, $3); }
    +;
    +trait_method_reference:
    +      trait_method_reference_fully_qualified                { $$ = $1; }
    +    | identifier                                            { $$ = array(null, $1); }
    +;
    +
    +method_body:
    +      ';' /* abstract method */                             { $$ = null; }
    +    | '{' inner_statement_list '}'                          { $$ = $2; }
    +;
    +
    +variable_modifiers:
    +      non_empty_member_modifiers                            { $$ = $1; }
    +    | T_VAR                                                 { $$ = 0; }
    +;
    +
    +method_modifiers:
    +      /* empty */                                           { $$ = 0; }
    +    | non_empty_member_modifiers                            { $$ = $1; }
    +;
    +
    +non_empty_member_modifiers:
    +      member_modifier                                       { $$ = $1; }
    +    | non_empty_member_modifiers member_modifier            { Stmt\Class_::verifyModifier($1, $2); $$ = $1 | $2; }
    +;
    +
    +member_modifier:
    +      T_PUBLIC                                              { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
    +    | T_PROTECTED                                           { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
    +    | T_PRIVATE                                             { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
    +    | T_STATIC                                              { $$ = Stmt\Class_::MODIFIER_STATIC; }
    +    | T_ABSTRACT                                            { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
    +    | T_FINAL                                               { $$ = Stmt\Class_::MODIFIER_FINAL; }
    +;
    +
    +property_declaration_list:
    +      property_declaration                                  { init($1); }
    +    | property_declaration_list ',' property_declaration    { push($1, $3); }
    +;
    +
    +property_declaration:
    +      T_VARIABLE                                            { $$ = Stmt\PropertyProperty[parseVar($1), null]; }
    +    | T_VARIABLE '=' expr                                   { $$ = Stmt\PropertyProperty[parseVar($1), $3]; }
    +;
    +
    +expr_list:
    +      expr_list ',' expr                                    { push($1, $3); }
    +    | expr                                                  { init($1); }
    +;
    +
    +for_expr:
    +      /* empty */                                           { $$ = array(); }
    +    | expr_list                                             { $$ = $1; }
    +;
    +
    +expr:
    +      variable                                              { $$ = $1; }
    +    | list_expr '=' expr                                    { $$ = Expr\Assign[$1, $3]; }
    +    | variable '=' expr                                     { $$ = Expr\Assign[$1, $3]; }
    +    | variable '=' '&' variable                             { $$ = Expr\AssignRef[$1, $4]; }
    +    | new_expr                                              { $$ = $1; }
    +    | T_CLONE expr                                          { $$ = Expr\Clone_[$2]; }
    +    | variable T_PLUS_EQUAL expr                            { $$ = Expr\AssignOp\Plus      [$1, $3]; }
    +    | variable T_MINUS_EQUAL expr                           { $$ = Expr\AssignOp\Minus     [$1, $3]; }
    +    | variable T_MUL_EQUAL expr                             { $$ = Expr\AssignOp\Mul       [$1, $3]; }
    +    | variable T_DIV_EQUAL expr                             { $$ = Expr\AssignOp\Div       [$1, $3]; }
    +    | variable T_CONCAT_EQUAL expr                          { $$ = Expr\AssignOp\Concat    [$1, $3]; }
    +    | variable T_MOD_EQUAL expr                             { $$ = Expr\AssignOp\Mod       [$1, $3]; }
    +    | variable T_AND_EQUAL expr                             { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; }
    +    | variable T_OR_EQUAL expr                              { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; }
    +    | variable T_XOR_EQUAL expr                             { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; }
    +    | variable T_SL_EQUAL expr                              { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
    +    | variable T_SR_EQUAL expr                              { $$ = Expr\AssignOp\ShiftRight[$1, $3]; }
    +    | variable T_POW_EQUAL expr                             { $$ = Expr\AssignOp\Pow       [$1, $3]; }
    +    | variable T_INC                                        { $$ = Expr\PostInc[$1]; }
    +    | T_INC variable                                        { $$ = Expr\PreInc [$2]; }
    +    | variable T_DEC                                        { $$ = Expr\PostDec[$1]; }
    +    | T_DEC variable                                        { $$ = Expr\PreDec [$2]; }
    +    | expr T_BOOLEAN_OR expr                                { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
    +    | expr T_BOOLEAN_AND expr                               { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
    +    | expr T_LOGICAL_OR expr                                { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
    +    | expr T_LOGICAL_AND expr                               { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
    +    | expr T_LOGICAL_XOR expr                               { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
    +    | expr '|' expr                                         { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
    +    | expr '&' expr                                         { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
    +    | expr '^' expr                                         { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
    +    | expr '.' expr                                         { $$ = Expr\BinaryOp\Concat    [$1, $3]; }
    +    | expr '+' expr                                         { $$ = Expr\BinaryOp\Plus      [$1, $3]; }
    +    | expr '-' expr                                         { $$ = Expr\BinaryOp\Minus     [$1, $3]; }
    +    | expr '*' expr                                         { $$ = Expr\BinaryOp\Mul       [$1, $3]; }
    +    | expr '/' expr                                         { $$ = Expr\BinaryOp\Div       [$1, $3]; }
    +    | expr '%' expr                                         { $$ = Expr\BinaryOp\Mod       [$1, $3]; }
    +    | expr T_SL expr                                        { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
    +    | expr T_SR expr                                        { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
    +    | expr T_POW expr                                       { $$ = Expr\BinaryOp\Pow       [$1, $3]; }
    +    | '+' expr %prec T_INC                                  { $$ = Expr\UnaryPlus [$2]; }
    +    | '-' expr %prec T_INC                                  { $$ = Expr\UnaryMinus[$2]; }
    +    | '!' expr                                              { $$ = Expr\BooleanNot[$2]; }
    +    | '~' expr                                              { $$ = Expr\BitwiseNot[$2]; }
    +    | expr T_IS_IDENTICAL expr                              { $$ = Expr\BinaryOp\Identical     [$1, $3]; }
    +    | expr T_IS_NOT_IDENTICAL expr                          { $$ = Expr\BinaryOp\NotIdentical  [$1, $3]; }
    +    | expr T_IS_EQUAL expr                                  { $$ = Expr\BinaryOp\Equal         [$1, $3]; }
    +    | expr T_IS_NOT_EQUAL expr                              { $$ = Expr\BinaryOp\NotEqual      [$1, $3]; }
    +    | expr T_SPACESHIP expr                                 { $$ = Expr\BinaryOp\Spaceship     [$1, $3]; }
    +    | expr '<' expr                                         { $$ = Expr\BinaryOp\Smaller       [$1, $3]; }
    +    | expr T_IS_SMALLER_OR_EQUAL expr                       { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
    +    | expr '>' expr                                         { $$ = Expr\BinaryOp\Greater       [$1, $3]; }
    +    | expr T_IS_GREATER_OR_EQUAL expr                       { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
    +    | expr T_INSTANCEOF class_name_reference                { $$ = Expr\Instanceof_[$1, $3]; }
    +    | '(' expr ')'                                          { $$ = $2; }
    +    | expr '?' expr ':' expr                                { $$ = Expr\Ternary[$1, $3,   $5]; }
    +    | expr '?' ':' expr                                     { $$ = Expr\Ternary[$1, null, $4]; }
    +    | expr T_COALESCE expr                                  { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }
    +    | T_ISSET '(' variables_list ')'                        { $$ = Expr\Isset_[$3]; }
    +    | T_EMPTY '(' expr ')'                                  { $$ = Expr\Empty_[$3]; }
    +    | T_INCLUDE expr                                        { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; }
    +    | T_INCLUDE_ONCE expr                                   { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; }
    +    | T_EVAL '(' expr ')'                                   { $$ = Expr\Eval_[$3]; }
    +    | T_REQUIRE expr                                        { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
    +    | T_REQUIRE_ONCE expr                                   { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
    +    | T_INT_CAST expr                                       { $$ = Expr\Cast\Int_    [$2]; }
    +    | T_DOUBLE_CAST expr                                    { $$ = Expr\Cast\Double  [$2]; }
    +    | T_STRING_CAST expr                                    { $$ = Expr\Cast\String_ [$2]; }
    +    | T_ARRAY_CAST expr                                     { $$ = Expr\Cast\Array_  [$2]; }
    +    | T_OBJECT_CAST expr                                    { $$ = Expr\Cast\Object_ [$2]; }
    +    | T_BOOL_CAST expr                                      { $$ = Expr\Cast\Bool_   [$2]; }
    +    | T_UNSET_CAST expr                                     { $$ = Expr\Cast\Unset_  [$2]; }
    +    | T_EXIT exit_expr
    +          { $attrs = attributes();
    +            $attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
    +            $$ = new Expr\Exit_($2, $attrs); }
    +    | '@' expr                                              { $$ = Expr\ErrorSuppress[$2]; }
    +    | scalar                                                { $$ = $1; }
    +    | '`' backticks_expr '`'                                { $$ = Expr\ShellExec[$2]; }
    +    | T_PRINT expr                                          { $$ = Expr\Print_[$2]; }
    +    | T_YIELD                                               { $$ = Expr\Yield_[null, null]; }
    +    | T_YIELD expr                                          { $$ = Expr\Yield_[$2, null]; }
    +    | T_YIELD expr T_DOUBLE_ARROW expr                      { $$ = Expr\Yield_[$4, $2]; }
    +    | T_YIELD_FROM expr                                     { $$ = Expr\YieldFrom[$2]; }
    +    | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
    +      '{' inner_statement_list '}'
    +          { $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $9]]; }
    +    | T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
    +      '{' inner_statement_list '}'
    +          { $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $10]]; }
    +;
    +
    +anonymous_class:
    +      T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
    +          { $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2); }
    +
    +new_expr:
    +      T_NEW class_name_reference ctor_arguments             { $$ = Expr\New_[$2, $3]; }
    +    | T_NEW anonymous_class
    +          { list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
    +;
    +
    +lexical_vars:
    +      /* empty */                                           { $$ = array(); }
    +    | T_USE '(' lexical_var_list ')'                        { $$ = $3; }
    +;
    +
    +lexical_var_list:
    +      lexical_var                                           { init($1); }
    +    | lexical_var_list ',' lexical_var                      { push($1, $3); }
    +;
    +
    +lexical_var:
    +      optional_ref T_VARIABLE                               { $$ = Expr\ClosureUse[parseVar($2), $1]; }
    +;
    +
    +function_call:
    +      name argument_list                                    { $$ = Expr\FuncCall[$1, $2]; }
    +    | callable_expr argument_list                           { $$ = Expr\FuncCall[$1, $2]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
    +          { $$ = Expr\StaticCall[$1, $3, $4]; }
    +;
    +
    +class_name:
    +      T_STATIC                                              { $$ = Name[$1]; }
    +    | name                                                  { $$ = $1; }
    +;
    +
    +name:
    +      namespace_name_parts                                  { $$ = Name[$1]; }
    +    | T_NS_SEPARATOR namespace_name_parts                   { $$ = Name\FullyQualified[$2]; }
    +    | T_NAMESPACE T_NS_SEPARATOR namespace_name_parts       { $$ = Name\Relative[$3]; }
    +;
    +
    +class_name_reference:
    +      class_name                                            { $$ = $1; }
    +    | new_variable                                          { $$ = $1; }
    +;
    +
    +class_name_or_var:
    +      class_name                                            { $$ = $1; }
    +    | dereferencable                                        { $$ = $1; }
    +;
    +
    +exit_expr:
    +      /* empty */                                           { $$ = null; }
    +    | '(' optional_expr ')'                                 { $$ = $2; }
    +;
    +
    +backticks_expr:
    +      /* empty */                                           { $$ = array(); }
    +    | T_ENCAPSED_AND_WHITESPACE
    +          { $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
    +    | encaps_list                                           { parseEncapsed($1, '`', true); $$ = $1; }
    +;
    +
    +ctor_arguments:
    +      /* empty */                                           { $$ = array(); }
    +    | argument_list                                         { $$ = $1; }
    +;
    +
    +constant:
    +      name                                                  { $$ = Expr\ConstFetch[$1]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier
    +          { $$ = Expr\ClassConstFetch[$1, $3]; }
    +;
    +
    +dereferencable_scalar:
    +      T_ARRAY '(' array_pair_list ')'
    +          { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
    +            $$ = new Expr\Array_($3, $attrs); }
    +    | '[' array_pair_list ']'
    +          { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT;
    +            $$ = new Expr\Array_($2, $attrs); }
    +    | T_CONSTANT_ENCAPSED_STRING
    +          { $attrs = attributes(); $attrs['kind'] = strKind($1);
    +            $$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
    +;
    +
    +scalar:
    +      T_LNUMBER                                             { $$ = Scalar\LNumber::fromString($1, attributes()); }
    +    | T_DNUMBER                                             { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
    +    | T_LINE                                                { $$ = Scalar\MagicConst\Line[]; }
    +    | T_FILE                                                { $$ = Scalar\MagicConst\File[]; }
    +    | T_DIR                                                 { $$ = Scalar\MagicConst\Dir[]; }
    +    | T_CLASS_C                                             { $$ = Scalar\MagicConst\Class_[]; }
    +    | T_TRAIT_C                                             { $$ = Scalar\MagicConst\Trait_[]; }
    +    | T_METHOD_C                                            { $$ = Scalar\MagicConst\Method[]; }
    +    | T_FUNC_C                                              { $$ = Scalar\MagicConst\Function_[]; }
    +    | T_NS_C                                                { $$ = Scalar\MagicConst\Namespace_[]; }
    +    | dereferencable_scalar                                 { $$ = $1; }
    +    | constant                                              { $$ = $1; }
    +    | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
    +          { $attrs = attributes(); setDocStringAttrs($attrs, $1);
    +            $$ = new Scalar\String_(Scalar\String_::parseDocString($1, $2), $attrs); }
    +    | T_START_HEREDOC T_END_HEREDOC
    +          { $attrs = attributes(); setDocStringAttrs($attrs, $1);
    +            $$ = new Scalar\String_('', $attrs); }
    +    | '"' encaps_list '"'
    +          { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
    +            parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
    +    | T_START_HEREDOC encaps_list T_END_HEREDOC
    +          { $attrs = attributes(); setDocStringAttrs($attrs, $1);
    +            parseEncapsedDoc($2, true); $$ = new Scalar\Encapsed($2, $attrs); }
    +;
    +
    +optional_comma:
    +      /* empty */
    +    | ','
    +;
    +
    +optional_expr:
    +      /* empty */                                           { $$ = null; }
    +    | expr                                                  { $$ = $1; }
    +;
    +
    +dereferencable:
    +      variable                                              { $$ = $1; }
    +    | '(' expr ')'                                          { $$ = $2; }
    +    | dereferencable_scalar                                 { $$ = $1; }
    +;
    +
    +callable_expr:
    +      callable_variable                                     { $$ = $1; }
    +    | '(' expr ')'                                          { $$ = $2; }
    +    | dereferencable_scalar                                 { $$ = $1; }
    +;
    +
    +callable_variable:
    +      simple_variable                                       { $$ = Expr\Variable[$1]; }
    +    | dereferencable '[' optional_expr ']'                  { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | constant '[' optional_expr ']'                        { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | dereferencable '{' expr '}'                           { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | function_call                                         { $$ = $1; }
    +    | dereferencable T_OBJECT_OPERATOR property_name argument_list
    +          { $$ = Expr\MethodCall[$1, $3, $4]; }
    +;
    +
    +variable:
    +      callable_variable                                     { $$ = $1; }
    +    | static_member                                         { $$ = $1; }
    +    | dereferencable T_OBJECT_OPERATOR property_name        { $$ = Expr\PropertyFetch[$1, $3]; }
    +;
    +
    +simple_variable:
    +      T_VARIABLE                                            { $$ = parseVar($1); }
    +    | '$' '{' expr '}'                                      { $$ = $3; }
    +    | '$' simple_variable                                   { $$ = Expr\Variable[$2]; }
    +;
    +
    +static_member:
    +      class_name_or_var T_PAAMAYIM_NEKUDOTAYIM simple_variable
    +          { $$ = Expr\StaticPropertyFetch[$1, $3]; }
    +;
    +
    +new_variable:
    +      simple_variable                                       { $$ = Expr\Variable[$1]; }
    +    | new_variable '[' optional_expr ']'                    { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | new_variable '{' expr '}'                             { $$ = Expr\ArrayDimFetch[$1, $3]; }
    +    | new_variable T_OBJECT_OPERATOR property_name          { $$ = Expr\PropertyFetch[$1, $3]; }
    +    | class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable     { $$ = Expr\StaticPropertyFetch[$1, $3]; }
    +    | new_variable T_PAAMAYIM_NEKUDOTAYIM simple_variable   { $$ = Expr\StaticPropertyFetch[$1, $3]; }
    +;
    +
    +member_name:
    +      identifier                                            { $$ = $1; }
    +    | '{' expr '}'	                                        { $$ = $2; }
    +    | simple_variable	                                    { $$ = Expr\Variable[$1]; }
    +;
    +
    +property_name:
    +      T_STRING                                              { $$ = $1; }
    +    | '{' expr '}'	                                        { $$ = $2; }
    +    | simple_variable	                                    { $$ = Expr\Variable[$1]; }
    +;
    +
    +list_expr:
    +      T_LIST '(' list_expr_elements ')'                     { $$ = Expr\List_[$3]; }
    +;
    +
    +list_expr_elements:
    +      list_expr_elements ',' list_expr_element              { push($1, $3); }
    +    | list_expr_element                                     { init($1); }
    +;
    +
    +list_expr_element:
    +      variable                                              { $$ = $1; }
    +    | list_expr                                             { $$ = $1; }
    +    | /* empty */                                           { $$ = null; }
    +;
    +
    +array_pair_list:
    +      /* empty */                                           { $$ = array(); }
    +    | non_empty_array_pair_list optional_comma              { $$ = $1; }
    +;
    +
    +non_empty_array_pair_list:
    +      non_empty_array_pair_list ',' array_pair              { push($1, $3); }
    +    | array_pair                                            { init($1); }
    +;
    +
    +array_pair:
    +      expr T_DOUBLE_ARROW expr                              { $$ = Expr\ArrayItem[$3, $1,   false]; }
    +    | expr                                                  { $$ = Expr\ArrayItem[$1, null, false]; }
    +    | expr T_DOUBLE_ARROW '&' variable                      { $$ = Expr\ArrayItem[$4, $1,   true]; }
    +    | '&' variable                                          { $$ = Expr\ArrayItem[$2, null, true]; }
    +;
    +
    +encaps_list:
    +      encaps_list encaps_var                                { push($1, $2); }
    +    | encaps_list encaps_string_part                        { push($1, $2); }
    +    | encaps_var                                            { init($1); }
    +    | encaps_string_part encaps_var                         { init($1, $2); }
    +;
    +
    +encaps_string_part:
    +      T_ENCAPSED_AND_WHITESPACE                             { $$ = Scalar\EncapsedStringPart[$1]; }
    +;
    +
    +encaps_var:
    +      T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    +    | T_VARIABLE '[' encaps_var_offset ']'                  { $$ = Expr\ArrayDimFetch[Expr\Variable[parseVar($1)], $3]; }
    +    | T_VARIABLE T_OBJECT_OPERATOR T_STRING                 { $$ = Expr\PropertyFetch[Expr\Variable[parseVar($1)], $3]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES expr '}'                   { $$ = Expr\Variable[$2]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}'       { $$ = Expr\Variable[$2]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
    +          { $$ = Expr\ArrayDimFetch[Expr\Variable[$2], $4]; }
    +    | T_CURLY_OPEN variable '}'                             { $$ = $2; }
    +;
    +
    +encaps_var_offset:
    +      T_STRING                                              { $$ = Scalar\String_[$1]; }
    +    | T_NUM_STRING                                          { $$ = Scalar\String_[$1]; }
    +    | T_VARIABLE                                            { $$ = Expr\Variable[parseVar($1)]; }
    +;
    +
    +%%
    diff --git a/core/vendor/nikic/php-parser/grammar/rebuildParsers.php b/core/vendor/nikic/php-parser/grammar/rebuildParsers.php
    new file mode 100644
    index 0000000..5b538ce
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/grammar/rebuildParsers.php
    @@ -0,0 +1,257 @@
    + 'Php5',
    +    __DIR__ . '/php7.y' => 'Php7',
    +];
    +
    +$tokensFile     = __DIR__ . '/tokens.y';
    +$tokensTemplate = __DIR__ . '/tokens.template';
    +$skeletonFile   = __DIR__ . '/parser.template';
    +$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy';
    +$tmpResultFile  = __DIR__ . '/tmp_parser.php';
    +$resultDir = __DIR__ . '/../lib/PhpParser/Parser';
    +$tokensResultsFile = $resultDir . '/Tokens.php';
    +
    +// check for kmyacc.exe binary in this directory, otherwise fall back to global name
    +$kmyacc = __DIR__ . '/kmyacc.exe';
    +if (!file_exists($kmyacc)) {
    +    $kmyacc = 'kmyacc';
    +}
    +
    +$options = array_flip($argv);
    +$optionDebug = isset($options['--debug']);
    +$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);
    +
    +///////////////////////////////
    +/// Utility regex constants ///
    +///////////////////////////////
    +
    +const LIB = '(?(DEFINE)
    +    (?\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
    +    (?"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
    +    (?(?&singleQuotedString)|(?&doubleQuotedString))
    +    (?/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
    +    (?\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
    +)';
    +
    +const PARAMS = '\[(?[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]';
    +const ARGS   = '\((?[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
    +
    +///////////////////
    +/// Main script ///
    +///////////////////
    +
    +$tokens = file_get_contents($tokensFile);
    +
    +foreach ($grammarFileToName as $grammarFile => $name) {
    +    echo "Building temporary $name grammar file.\n";
    +
    +    $grammarCode = file_get_contents($grammarFile);
    +    $grammarCode = str_replace('%tokens', $tokens, $grammarCode);
    +
    +    $grammarCode = resolveNodes($grammarCode);
    +    $grammarCode = resolveMacros($grammarCode);
    +    $grammarCode = resolveStackAccess($grammarCode);
    +
    +    file_put_contents($tmpGrammarFile, $grammarCode);
    +
    +    $additionalArgs = $optionDebug ? '-t -v' : '';
    +
    +    echo "Building $name parser.\n";
    +    $output = trim(shell_exec("$kmyacc $additionalArgs -l -m $skeletonFile -p $name $tmpGrammarFile 2>&1"));
    +    echo "Output: \"$output\"\n";
    +
    +    $resultCode = file_get_contents($tmpResultFile);
    +    $resultCode = removeTrailingWhitespace($resultCode);
    +
    +    ensureDirExists($resultDir);
    +    file_put_contents("$resultDir/$name.php", $resultCode);
    +    unlink($tmpResultFile);
    +
    +    echo "Building token definition.\n";
    +    $output = trim(shell_exec("$kmyacc -l -m $tokensTemplate $tmpGrammarFile 2>&1"));
    +    assert($output === '');
    +    rename($tmpResultFile, $tokensResultsFile);
    +
    +    if (!$optionKeepTmpGrammar) {
    +        unlink($tmpGrammarFile);
    +    }
    +}
    +
    +///////////////////////////////
    +/// Preprocessing functions ///
    +///////////////////////////////
    +
    +function resolveNodes($code) {
    +    return preg_replace_callback(
    +        '~\b(?[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
    +        function($matches) {
    +            // recurse
    +            $matches['params'] = resolveNodes($matches['params']);
    +
    +            $params = magicSplit(
    +                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
    +                $matches['params']
    +            );
    +
    +            $paramCode = '';
    +            foreach ($params as $param) {
    +                $paramCode .= $param . ', ';
    +            }
    +
    +            return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
    +        },
    +        $code
    +    );
    +}
    +
    +function resolveMacros($code) {
    +    return preg_replace_callback(
    +        '~\b(?)(?!array\()(?[a-z][A-Za-z]++)' . ARGS . '~',
    +        function($matches) {
    +            // recurse
    +            $matches['args'] = resolveMacros($matches['args']);
    +
    +            $name = $matches['name'];
    +            $args = magicSplit(
    +                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
    +                $matches['args']
    +            );
    +
    +            if ('attributes' == $name) {
    +                assertArgs(0, $args, $name);
    +                return '$this->startAttributeStack[#1] + $this->endAttributes';
    +            }
    +
    +            if ('init' == $name) {
    +                return '$$ = array(' . implode(', ', $args) . ')';
    +            }
    +
    +            if ('push' == $name) {
    +                assertArgs(2, $args, $name);
    +
    +                return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
    +            }
    +
    +            if ('pushNormalizing' == $name) {
    +                assertArgs(2, $args, $name);
    +
    +                return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
    +                     . ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
    +            }
    +
    +            if ('toArray' == $name) {
    +                assertArgs(1, $args, $name);
    +
    +                return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
    +            }
    +
    +            if ('parseVar' == $name) {
    +                assertArgs(1, $args, $name);
    +
    +                return 'substr(' . $args[0] . ', 1)';
    +            }
    +
    +            if ('parseEncapsed' == $name) {
    +                assertArgs(3, $args, $name);
    +
    +                return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
    +                     . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
    +            }
    +
    +            if ('parseEncapsedDoc' == $name) {
    +                assertArgs(2, $args, $name);
    +
    +                return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
    +                     . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, ' . $args[1] . '); } }'
    +                     . ' $s->value = preg_replace(\'~(\r\n|\n|\r)\z~\', \'\', $s->value);'
    +                     . ' if (\'\' === $s->value) array_pop(' . $args[0] . ');';
    +            }
    +
    +            if ('makeNop' == $name) {
    +                assertArgs(2, $args, $name);
    +
    +                return '$startAttributes = ' . $args[1] . ';'
    +                . ' if (isset($startAttributes[\'comments\']))'
    +                . ' { ' . $args[0] . ' = new Stmt\Nop([\'comments\' => $startAttributes[\'comments\']]); }'
    +                . ' else { ' . $args[0] . ' = null; }';
    +            }
    +
    +            if ('strKind' == $name) {
    +                assertArgs(1, $args, $name);
    +
    +                return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
    +                     . '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
    +                     . '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
    +            }
    +
    +            if ('setDocStringAttrs' == $name) {
    +                assertArgs(2, $args, $name);
    +
    +                return $args[0] . '[\'kind\'] = strpos(' . $args[1] . ', "\'") === false '
    +                     . '? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; '
    +                     . 'preg_match(\'/\A[bB]?<<<[ \t]*[\\\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\\\'"]?(?:\r\n|\n|\r)\z/\', ' . $args[1] . ', $matches); '
    +                     . $args[0] . '[\'docLabel\'] = $matches[1];';
    +            }
    +
    +            if ('prependLeadingComments' == $name) {
    +                assertArgs(1, $args, $name);
    +
    +                return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
    +                . 'if (!empty($attrs[\'comments\']) && isset($stmts[0])) {'
    +                . '$stmts[0]->setAttribute(\'comments\', '
    +                . 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
    +            }
    +
    +            return $matches[0];
    +        },
    +        $code
    +    );
    +}
    +
    +function assertArgs($num, $args, $name) {
    +    if ($num != count($args)) {
    +        die('Wrong argument count for ' . $name . '().');
    +    }
    +}
    +
    +function resolveStackAccess($code) {
    +    $code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
    +    $code = preg_replace('/#(\d+)/', '$$1', $code);
    +    return $code;
    +}
    +
    +function removeTrailingWhitespace($code) {
    +    $lines = explode("\n", $code);
    +    $lines = array_map('rtrim', $lines);
    +    return implode("\n", $lines);
    +}
    +
    +function ensureDirExists($dir) {
    +    if (!is_dir($dir)) {
    +        mkdir($dir, 0777, true);
    +    }
    +}
    +
    +//////////////////////////////
    +/// Regex helper functions ///
    +//////////////////////////////
    +
    +function regex($regex) {
    +    return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
    +}
    +
    +function magicSplit($regex, $string) {
    +    $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
    +
    +    foreach ($pieces as &$piece) {
    +        $piece = trim($piece);
    +    }
    +
    +    if ($pieces === ['']) {
    +        return [];
    +    }
    +
    +    return $pieces;
    +}
    diff --git a/core/vendor/nikic/php-parser/grammar/tokens.template b/core/vendor/nikic/php-parser/grammar/tokens.template
    new file mode 100644
    index 0000000..ba4e490
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/grammar/tokens.template
    @@ -0,0 +1,17 @@
    +semValue
    +#semval($,%t) $this->semValue
    +#semval(%n) $this->stackPos-(%l-%n)
    +#semval(%n,%t) $this->stackPos-(%l-%n)
    +
    +namespace PhpParser\Parser;
    +#include;
    +
    +/* GENERATED file based on grammar/tokens.y */
    +final class Tokens
    +{
    +#tokenval
    +    const %s = %n;
    +#endtokenval
    +}
    diff --git a/core/vendor/nikic/php-parser/grammar/tokens.y b/core/vendor/nikic/php-parser/grammar/tokens.y
    new file mode 100644
    index 0000000..2b54f80
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/grammar/tokens.y
    @@ -0,0 +1,113 @@
    +/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for
    + * both. This is enforced by sharing this token file. */
    +
    +%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
    +%left ','
    +%left T_LOGICAL_OR
    +%left T_LOGICAL_XOR
    +%left T_LOGICAL_AND
    +%right T_PRINT
    +%right T_YIELD
    +%right T_DOUBLE_ARROW
    +%right T_YIELD_FROM
    +%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL
    +%left '?' ':'
    +%right T_COALESCE
    +%left T_BOOLEAN_OR
    +%left T_BOOLEAN_AND
    +%left '|'
    +%left '^'
    +%left '&'
    +%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
    +%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
    +%left T_SL T_SR
    +%left '+' '-' '.'
    +%left '*' '/' '%'
    +%right '!'
    +%nonassoc T_INSTANCEOF
    +%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
    +%right T_POW
    +%right '['
    +%nonassoc T_NEW T_CLONE
    +%token T_EXIT
    +%token T_IF
    +%left T_ELSEIF
    +%left T_ELSE
    +%left T_ENDIF
    +%token T_LNUMBER
    +%token T_DNUMBER
    +%token T_STRING
    +%token T_STRING_VARNAME
    +%token T_VARIABLE
    +%token T_NUM_STRING
    +%token T_INLINE_HTML
    +%token T_CHARACTER
    +%token T_BAD_CHARACTER
    +%token T_ENCAPSED_AND_WHITESPACE
    +%token T_CONSTANT_ENCAPSED_STRING
    +%token T_ECHO
    +%token T_DO
    +%token T_WHILE
    +%token T_ENDWHILE
    +%token T_FOR
    +%token T_ENDFOR
    +%token T_FOREACH
    +%token T_ENDFOREACH
    +%token T_DECLARE
    +%token T_ENDDECLARE
    +%token T_AS
    +%token T_SWITCH
    +%token T_ENDSWITCH
    +%token T_CASE
    +%token T_DEFAULT
    +%token T_BREAK
    +%token T_CONTINUE
    +%token T_GOTO
    +%token T_FUNCTION
    +%token T_CONST
    +%token T_RETURN
    +%token T_TRY
    +%token T_CATCH
    +%token T_FINALLY
    +%token T_THROW
    +%token T_USE
    +%token T_INSTEADOF
    +%token T_GLOBAL
    +%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
    +%token T_VAR
    +%token T_UNSET
    +%token T_ISSET
    +%token T_EMPTY
    +%token T_HALT_COMPILER
    +%token T_CLASS
    +%token T_TRAIT
    +%token T_INTERFACE
    +%token T_EXTENDS
    +%token T_IMPLEMENTS
    +%token T_OBJECT_OPERATOR
    +%token T_DOUBLE_ARROW
    +%token T_LIST
    +%token T_ARRAY
    +%token T_CALLABLE
    +%token T_CLASS_C
    +%token T_TRAIT_C
    +%token T_METHOD_C
    +%token T_FUNC_C
    +%token T_LINE
    +%token T_FILE
    +%token T_COMMENT
    +%token T_DOC_COMMENT
    +%token T_OPEN_TAG
    +%token T_OPEN_TAG_WITH_ECHO
    +%token T_CLOSE_TAG
    +%token T_WHITESPACE
    +%token T_START_HEREDOC
    +%token T_END_HEREDOC
    +%token T_DOLLAR_OPEN_CURLY_BRACES
    +%token T_CURLY_OPEN
    +%token T_PAAMAYIM_NEKUDOTAYIM
    +%token T_NAMESPACE
    +%token T_NS_C
    +%token T_DIR
    +%token T_NS_SEPARATOR
    +%token T_ELLIPSIS
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Autoloader.php b/core/vendor/nikic/php-parser/lib/PhpParser/Autoloader.php
    new file mode 100644
    index 0000000..809a06e
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Autoloader.php
    @@ -0,0 +1,40 @@
    +name = $name;
    +    }
    +
    +    /**
    +     * Extends a class.
    +     *
    +     * @param Name|string $class Name of class to extend
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function extend($class) {
    +        $this->extends = $this->normalizeName($class);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Implements one or more interfaces.
    +     *
    +     * @param Name|string ...$interfaces Names of interfaces to implement
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function implement() {
    +        foreach (func_get_args() as $interface) {
    +            $this->implements[] = $this->normalizeName($interface);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the class abstract.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeAbstract() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_ABSTRACT);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the class final.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeFinal() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_FINAL);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param Stmt|PhpParser\Builder $stmt The statement to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        $stmt = $this->normalizeNode($stmt);
    +
    +        $targets = array(
    +            'Stmt_TraitUse'    => &$this->uses,
    +            'Stmt_ClassConst'  => &$this->constants,
    +            'Stmt_Property'    => &$this->properties,
    +            'Stmt_ClassMethod' => &$this->methods,
    +        );
    +
    +        $type = $stmt->getType();
    +        if (!isset($targets[$type])) {
    +            throw new \LogicException(sprintf('Unexpected node of type "%s"', $type));
    +        }
    +
    +        $targets[$type][] = $stmt;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built class node.
    +     *
    +     * @return Stmt\Class_ The built class node
    +     */
    +    public function getNode() {
    +        return new Stmt\Class_($this->name, array(
    +            'type' => $this->type,
    +            'extends' => $this->extends,
    +            'implements' => $this->implements,
    +            'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
    +        ), $this->attributes);
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php
    new file mode 100644
    index 0000000..f6a322c
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php
    @@ -0,0 +1,44 @@
    +addStmt($stmt);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Sets doc comment for the declaration.
    +     *
    +     * @param PhpParser\Comment\Doc|string $docComment Doc comment to set
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function setDocComment($docComment) {
    +        $this->attributes['comments'] = array(
    +            $this->normalizeDocComment($docComment)
    +        );
    +
    +        return $this;
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php
    new file mode 100644
    index 0000000..f7b1d2c
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php
    @@ -0,0 +1,78 @@
    +returnByRef = true;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a parameter.
    +     *
    +     * @param Node\Param|Param $param The parameter to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addParam($param) {
    +        $param = $this->normalizeNode($param);
    +
    +        if (!$param instanceof Node\Param) {
    +            throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType()));
    +        }
    +
    +        $this->params[] = $param;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds multiple parameters.
    +     *
    +     * @param array $params The parameters to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addParams(array $params) {
    +        foreach ($params as $param) {
    +            $this->addParam($param);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Sets the return type for PHP 7.
    +     *
    +     * @param string|Node\Name $type One of array, callable, string, int, float, bool,
    +     *                               or a class/interface name.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function setReturnType($type)
    +    {
    +        if (in_array($type, array('array', 'callable', 'string', 'int', 'float', 'bool'))) {
    +            $this->returnType = $type;
    +        } else {
    +            $this->returnType = $this->normalizeName($type);
    +        }
    +
    +        return $this;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php
    new file mode 100644
    index 0000000..228bdfa
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php
    @@ -0,0 +1,49 @@
    +name = $name;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param Node|PhpParser\Builder $stmt The statement to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        $this->stmts[] = $this->normalizeNode($stmt);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built function node.
    +     *
    +     * @return Stmt\Function_ The built function node
    +     */
    +    public function getNode() {
    +        return new Stmt\Function_($this->name, array(
    +            'byRef'      => $this->returnByRef,
    +            'params'     => $this->params,
    +            'returnType' => $this->returnType,
    +            'stmts'      => $this->stmts,
    +        ), $this->attributes);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php
    new file mode 100644
    index 0000000..8ebb292
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php
    @@ -0,0 +1,80 @@
    +name = $name;
    +    }
    +
    +    /**
    +     * Extends one or more interfaces.
    +     *
    +     * @param Name|string ...$interfaces Names of interfaces to extend
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function extend() {
    +        foreach (func_get_args() as $interface) {
    +            $this->extends[] = $this->normalizeName($interface);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param Stmt|PhpParser\Builder $stmt The statement to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        $stmt = $this->normalizeNode($stmt);
    +
    +        $type = $stmt->getType();
    +        switch ($type) {
    +            case 'Stmt_ClassConst':
    +                $this->constants[] = $stmt;
    +                break;
    +
    +            case 'Stmt_ClassMethod':
    +                // we erase all statements in the body of an interface method
    +                $stmt->stmts = null;
    +                $this->methods[] = $stmt;
    +                break;
    +
    +            default:
    +                throw new \LogicException(sprintf('Unexpected node of type "%s"', $type));
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built interface node.
    +     *
    +     * @return Stmt\Interface_ The built interface node
    +     */
    +    public function getNode() {
    +        return new Stmt\Interface_($this->name, array(
    +            'extends' => $this->extends,
    +            'stmts' => array_merge($this->constants, $this->methods),
    +        ), $this->attributes);
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php
    new file mode 100644
    index 0000000..21698f1
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php
    @@ -0,0 +1,126 @@
    +name = $name;
    +    }
    +
    +    /**
    +     * Makes the method public.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makePublic() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method protected.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeProtected() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method private.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makePrivate() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method static.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeStatic() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_STATIC);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method abstract.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeAbstract() {
    +        if (!empty($this->stmts)) {
    +            throw new \LogicException('Cannot make method with statements abstract');
    +        }
    +
    +        $this->setModifier(Stmt\Class_::MODIFIER_ABSTRACT);
    +        $this->stmts = null; // abstract methods don't have statements
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method final.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeFinal() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_FINAL);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param Node|PhpParser\Builder $stmt The statement to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        if (null === $this->stmts) {
    +            throw new \LogicException('Cannot add statements to an abstract method');
    +        }
    +
    +        $this->stmts[] = $this->normalizeNode($stmt);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built method node.
    +     *
    +     * @return Stmt\ClassMethod The built method node
    +     */
    +    public function getNode() {
    +        return new Stmt\ClassMethod($this->name, array(
    +            'type'       => $this->type,
    +            'byRef'      => $this->returnByRef,
    +            'params'     => $this->params,
    +            'returnType' => $this->returnType,
    +            'stmts'      => $this->stmts,
    +        ), $this->attributes);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php
    new file mode 100644
    index 0000000..432fcc7
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php
    @@ -0,0 +1,59 @@
    +name = null !== $name ? $this->normalizeName($name) : null;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param Node|PhpParser\Builder $stmt The statement to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        $this->stmts[] = $this->normalizeNode($stmt);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds multiple statements.
    +     *
    +     * @param array $stmts The statements to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addStmts(array $stmts) {
    +        foreach ($stmts as $stmt) {
    +            $this->addStmt($stmt);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built node.
    +     *
    +     * @return Node The built node
    +     */
    +    public function getNode() {
    +        return new Stmt\Namespace_($this->name, $this->stmts);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php
    new file mode 100644
    index 0000000..c9efc97
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php
    @@ -0,0 +1,76 @@
    +name = $name;
    +    }
    +
    +    /**
    +     * Sets default value for the parameter.
    +     *
    +     * @param mixed $value Default value to use
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function setDefault($value) {
    +        $this->default = $this->normalizeValue($value);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Sets type hint for the parameter.
    +     *
    +     * @param string|Node\Name $type Type hint to use
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function setTypeHint($type) {
    +        if (in_array($type, array('array', 'callable', 'string', 'int', 'float', 'bool'))) {
    +            $this->type = $type;
    +        } else {
    +            $this->type = $this->normalizeName($type);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Make the parameter accept the value by reference.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeByRef() {
    +        $this->byRef = true;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built parameter node.
    +     *
    +     * @return Node\Param The built parameter node
    +     */
    +    public function getNode() {
    +        return new Node\Param(
    +            $this->name, $this->default, $this->type, $this->byRef
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php
    new file mode 100644
    index 0000000..2d59d4c
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php
    @@ -0,0 +1,111 @@
    +name = $name;
    +    }
    +
    +    /**
    +     * Makes the property public.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makePublic() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the property protected.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeProtected() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the property private.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makePrivate() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the property static.
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function makeStatic() {
    +        $this->setModifier(Stmt\Class_::MODIFIER_STATIC);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Sets default value for the property.
    +     *
    +     * @param mixed $value Default value to use
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function setDefault($value) {
    +        $this->default = $this->normalizeValue($value);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Sets doc comment for the property.
    +     *
    +     * @param PhpParser\Comment\Doc|string $docComment Doc comment to set
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function setDocComment($docComment) {
    +        $this->attributes = array(
    +            'comments' => array($this->normalizeDocComment($docComment))
    +        );
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built class node.
    +     *
    +     * @return Stmt\Property The built property node
    +     */
    +    public function getNode() {
    +        return new Stmt\Property(
    +            $this->type !== 0 ? $this->type : Stmt\Class_::MODIFIER_PUBLIC,
    +            array(
    +                new Stmt\PropertyProperty($this->name, $this->default)
    +            ),
    +            $this->attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php
    new file mode 100644
    index 0000000..9722e35
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php
    @@ -0,0 +1,55 @@
    +name = $name;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param Stmt|PhpParser\Builder $stmt The statement to add
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        $stmt = $this->normalizeNode($stmt);
    +
    +        if ($stmt instanceof Stmt\Property) {
    +            $this->properties[] = $stmt;
    +        } else if ($stmt instanceof Stmt\ClassMethod) {
    +            $this->methods[] = $stmt;
    +        } else {
    +            throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built trait node.
    +     *
    +     * @return Stmt\Trait_ The built interface node
    +     */
    +    public function getNode() {
    +        return new Stmt\Trait_(
    +            $this->name, array_merge($this->properties, $this->methods), $this->attributes
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php
    new file mode 100644
    index 0000000..f6d3a85
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php
    @@ -0,0 +1,58 @@
    +name = $this->normalizeName($name);
    +        $this->type = $type;
    +    }
    +
    +    /**
    +     * Sets alias for used name.
    +     *
    +     * @param string $alias Alias to use (last component of full name by default)
    +     *
    +     * @return $this The builder instance (for fluid interface)
    +     */
    +    protected function as_($alias) {
    +        $this->alias = $alias;
    +        return $this;
    +    }
    +    public function __call($name, $args) {
    +        if (method_exists($this, $name . '_')) {
    +            return call_user_func_array(array($this, $name . '_'), $args);
    +        }
    +
    +        throw new \LogicException(sprintf('Method "%s" does not exist', $name));
    +    }
    +
    +    /**
    +     * Returns the built node.
    +     *
    +     * @return Node The built node
    +     */
    +    public function getNode() {
    +        $alias = null !== $this->alias ? $this->alias : $this->name->getLast();
    +        return new Stmt\Use_(array(
    +            new Stmt\UseUse($this->name, $alias)
    +        ), $this->type);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/BuilderAbstract.php b/core/vendor/nikic/php-parser/lib/PhpParser/BuilderAbstract.php
    new file mode 100644
    index 0000000..626bedd
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/BuilderAbstract.php
    @@ -0,0 +1,131 @@
    +getNode();
    +        } elseif ($node instanceof Node) {
    +            return $node;
    +        }
    +
    +        throw new \LogicException('Expected node or builder object');
    +    }
    +
    +    /**
    +     * Normalizes a name: Converts plain string names to PhpParser\Node\Name.
    +     *
    +     * @param Name|string $name The name to normalize
    +     *
    +     * @return Name The normalized name
    +     */
    +    protected function normalizeName($name) {
    +        if ($name instanceof Name) {
    +            return $name;
    +        } elseif (is_string($name)) {
    +            if (!$name) {
    +                throw new \LogicException('Name cannot be empty');
    +            }
    +
    +            if ($name[0] == '\\') {
    +                return new Name\FullyQualified(substr($name, 1));
    +            } elseif (0 === strpos($name, 'namespace\\')) {
    +                return new Name\Relative(substr($name, strlen('namespace\\')));
    +            } else {
    +                return new Name($name);
    +            }
    +        }
    +
    +        throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
    +    }
    +
    +    /**
    +     * Normalizes a value: Converts nulls, booleans, integers,
    +     * floats, strings and arrays into their respective nodes
    +     *
    +     * @param mixed $value The value to normalize
    +     *
    +     * @return Expr The normalized value
    +     */
    +    protected function normalizeValue($value) {
    +        if ($value instanceof Node) {
    +            return $value;
    +        } elseif (is_null($value)) {
    +            return new Expr\ConstFetch(
    +                new Name('null')
    +            );
    +        } elseif (is_bool($value)) {
    +            return new Expr\ConstFetch(
    +                new Name($value ? 'true' : 'false')
    +            );
    +        } elseif (is_int($value)) {
    +            return new Scalar\LNumber($value);
    +        } elseif (is_float($value)) {
    +            return new Scalar\DNumber($value);
    +        } elseif (is_string($value)) {
    +            return new Scalar\String_($value);
    +        } elseif (is_array($value)) {
    +            $items = array();
    +            $lastKey = -1;
    +            foreach ($value as $itemKey => $itemValue) {
    +                // for consecutive, numeric keys don't generate keys
    +                if (null !== $lastKey && ++$lastKey === $itemKey) {
    +                    $items[] = new Expr\ArrayItem(
    +                        $this->normalizeValue($itemValue)
    +                    );
    +                } else {
    +                    $lastKey = null;
    +                    $items[] = new Expr\ArrayItem(
    +                        $this->normalizeValue($itemValue),
    +                        $this->normalizeValue($itemKey)
    +                    );
    +                }
    +            }
    +
    +            return new Expr\Array_($items);
    +        } else {
    +            throw new \LogicException('Invalid value');
    +        }
    +    }
    +
    +    /**
    +     * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc.
    +     *
    +     * @param Comment\Doc|string $docComment The doc comment to normalize
    +     *
    +     * @return Comment\Doc The normalized doc comment
    +     */
    +    protected function normalizeDocComment($docComment) {
    +        if ($docComment instanceof Comment\Doc) {
    +            return $docComment;
    +        } else if (is_string($docComment)) {
    +            return new Comment\Doc($docComment);
    +        } else {
    +            throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
    +        }
    +    }
    +
    +    /**
    +     * Sets a modifier in the $this->type property.
    +     *
    +     * @param int $modifier Modifier to set
    +     */
    +    protected function setModifier($modifier) {
    +        Stmt\Class_::verifyModifier($this->type, $modifier);
    +        $this->type |= $modifier;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php b/core/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php
    new file mode 100644
    index 0000000..42c7f61
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php
    @@ -0,0 +1,127 @@
    +text = $text;
    +        $this->line = $startLine;
    +        $this->filePos = $startFilePos;
    +    }
    +
    +    /**
    +     * Gets the comment text.
    +     *
    +     * @return string The comment text (including comment delimiters like /*)
    +     */
    +    public function getText() {
    +        return $this->text;
    +    }
    +
    +    /**
    +     * Sets the comment text.
    +     *
    +     * @param string $text The comment text (including comment delimiters like /*)
    +     *
    +     * @deprecated Construct a new comment instead
    +     */
    +    public function setText($text) {
    +        $this->text = $text;
    +    }
    +
    +    /**
    +     * Gets the line number the comment started on.
    +     *
    +     * @return int Line number
    +     */
    +    public function getLine() {
    +        return $this->line;
    +    }
    +
    +    /**
    +     * Sets the line number the comment started on.
    +     *
    +     * @param int $line Line number
    +     *
    +     * @deprecated Construct a new comment instead
    +     */
    +    public function setLine($line) {
    +        $this->line = $line;
    +    }
    +
    +    /**
    +     * Gets the file offset the comment started on.
    +     *
    +     * @return int File offset
    +     */
    +    public function getFilePos() {
    +        return $this->filePos;
    +    }
    +
    +    /**
    +     * Gets the comment text.
    +     *
    +     * @return string The comment text (including comment delimiters like /*)
    +     */
    +    public function __toString() {
    +        return $this->text;
    +    }
    +
    +    /**
    +     * Gets the reformatted comment text.
    +     *
    +     * "Reformatted" here means that we try to clean up the whitespace at the
    +     * starts of the lines. This is necessary because we receive the comments
    +     * without trailing whitespace on the first line, but with trailing whitespace
    +     * on all subsequent lines.
    +     *
    +     * @return mixed|string
    +     */
    +    public function getReformattedText() {
    +        $text = trim($this->text);
    +        $newlinePos = strpos($text, "\n");
    +        if (false === $newlinePos) {
    +            // Single line comments don't need further processing
    +            return $text;
    +        } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
    +            // Multi line comment of the type
    +            //
    +            //     /*
    +            //      * Some text.
    +            //      * Some more text.
    +            //      */
    +            //
    +            // is handled by replacing the whitespace sequences before the * by a single space
    +            return preg_replace('(^\s+\*)m', ' *', $this->text);
    +        } elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
    +            // Multi line comment of the type
    +            //
    +            //    /*
    +            //        Some text.
    +            //        Some more text.
    +            //    */
    +            //
    +            // is handled by removing the whitespace sequence on the line before the closing
    +            // */ on all lines. So if the last line is "    */", then "    " is removed at the
    +            // start of all lines.
    +            return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
    +        } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
    +            // Multi line comment of the type
    +            //
    +            //     /* Some text.
    +            //        Some more text.
    +            //          Indented text.
    +            //        Even more text. */
    +            //
    +            // is handled by removing the difference between the shortest whitespace prefix on all
    +            // lines and the length of the "/* " opening sequence.
    +            $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1));
    +            $removeLen = $prefixLen - strlen($matches[0]);
    +            return preg_replace('(^\s{' . $removeLen . '})m', '', $text);
    +        }
    +
    +        // No idea how to format this comment, so simply return as is
    +        return $text;
    +    }
    +
    +    private function getShortestWhitespacePrefixLen($str) {
    +        $lines = explode("\n", $str);
    +        $shortestPrefixLen = INF;
    +        foreach ($lines as $line) {
    +            preg_match('(^\s*)', $line, $matches);
    +            $prefixLen = strlen($matches[0]);
    +            if ($prefixLen < $shortestPrefixLen) {
    +                $shortestPrefixLen = $prefixLen;
    +            }
    +        }
    +        return $shortestPrefixLen;
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php b/core/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php
    new file mode 100644
    index 0000000..24fc6c9
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php
    @@ -0,0 +1,7 @@
    +rawMessage = (string) $message;
    +        if (is_array($attributes)) {
    +            $this->attributes = $attributes;
    +        } else {
    +            $this->attributes = array('startLine' => $attributes);
    +        }
    +        $this->updateMessage();
    +    }
    +
    +    /**
    +     * Gets the error message
    +     *
    +     * @return string Error message
    +     */
    +    public function getRawMessage() {
    +        return $this->rawMessage;
    +    }
    +
    +    /**
    +     * Gets the line the error starts in.
    +     *
    +     * @return int Error start line
    +     */
    +    public function getStartLine() {
    +        return isset($this->attributes['startLine']) ? $this->attributes['startLine'] : -1;
    +    }
    +
    +    /**
    +     * Gets the line the error ends in.
    +     *
    +     * @return int Error end line
    +     */
    +    public function getEndLine() {
    +        return isset($this->attributes['endLine']) ? $this->attributes['endLine'] : -1;
    +    }
    +
    +
    +    /**
    +     * Gets the attributes of the node/token the error occurred at.
    +     *
    +     * @return array
    +     */
    +    public function getAttributes() {
    +        return $this->attributes;
    +    }
    +
    +    /**
    +     * Sets the line of the PHP file the error occurred in.
    +     *
    +     * @param string $message Error message
    +     */
    +    public function setRawMessage($message) {
    +        $this->rawMessage = (string) $message;
    +        $this->updateMessage();
    +    }
    +
    +    /**
    +     * Sets the line the error starts in.
    +     *
    +     * @param int $line Error start line
    +     */
    +    public function setStartLine($line) {
    +        $this->attributes['startLine'] = (int) $line;
    +        $this->updateMessage();
    +    }
    +
    +    /**
    +     * Returns whether the error has start and end column information.
    +     *
    +     * For column information enable the startFilePos and endFilePos in the lexer options.
    +     *
    +     * @return bool
    +     */
    +    public function hasColumnInfo() {
    +        return isset($this->attributes['startFilePos']) && isset($this->attributes['endFilePos']);
    +    }
    +
    +    /**
    +     * Gets the start column (1-based) into the line where the error started.
    +     *
    +     * @param string $code Source code of the file
    +     * @return int
    +     */
    +    public function getStartColumn($code) {
    +        if (!$this->hasColumnInfo()) {
    +            throw new \RuntimeException('Error does not have column information');
    +        }
    +
    +        return $this->toColumn($code, $this->attributes['startFilePos']);
    +    }
    +
    +    /**
    +     * Gets the end column (1-based) into the line where the error ended.
    +     *
    +     * @param string $code Source code of the file
    +     * @return int
    +     */
    +    public function getEndColumn($code) {
    +        if (!$this->hasColumnInfo()) {
    +            throw new \RuntimeException('Error does not have column information');
    +        }
    +
    +        return $this->toColumn($code, $this->attributes['endFilePos']);
    +    }
    +
    +    private function toColumn($code, $pos) {
    +        if ($pos > strlen($code)) {
    +            throw new \RuntimeException('Invalid position information');
    +        }
    +
    +        $lineStartPos = strrpos($code, "\n", $pos - strlen($code));
    +        if (false === $lineStartPos) {
    +            $lineStartPos = -1;
    +        }
    +
    +        return $pos - $lineStartPos;
    +    }
    +
    +    /**
    +     * Updates the exception message after a change to rawMessage or rawLine.
    +     */
    +    protected function updateMessage() {
    +        $this->message = $this->rawMessage;
    +
    +        if (-1 === $this->getStartLine()) {
    +            $this->message .= ' on unknown line';
    +        } else {
    +            $this->message .= ' on line ' . $this->getStartLine();
    +        }
    +    }
    +
    +    /** @deprecated Use getStartLine() instead */
    +    public function getRawLine() {
    +        return $this->getStartLine();
    +    }
    +
    +    /** @deprecated Use setStartLine() instead */
    +    public function setRawLine($line) {
    +        $this->setStartLine($line);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Lexer.php b/core/vendor/nikic/php-parser/lib/PhpParser/Lexer.php
    new file mode 100644
    index 0000000..cf7bf56
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Lexer.php
    @@ -0,0 +1,286 @@
    +tokenMap = $this->createTokenMap();
    +
    +        // map of tokens to drop while lexing (the map is only used for isset lookup,
    +        // that's why the value is simply set to 1; the value is never actually used.)
    +        $this->dropTokens = array_fill_keys(
    +            array(T_WHITESPACE, T_OPEN_TAG, T_COMMENT, T_DOC_COMMENT), 1
    +        );
    +
    +        // the usedAttributes member is a map of the used attribute names to a dummy
    +        // value (here "true")
    +        $options += array(
    +            'usedAttributes' => array('comments', 'startLine', 'endLine'),
    +        );
    +        $this->usedAttributes = array_fill_keys($options['usedAttributes'], true);
    +    }
    +
    +    /**
    +     * Initializes the lexer for lexing the provided source code.
    +     *
    +     * @param string $code The source code to lex
    +     *
    +     * @throws Error on lexing errors (unterminated comment or unexpected character)
    +     */
    +    public function startLexing($code) {
    +        $scream = ini_set('xdebug.scream', '0');
    +
    +        $this->resetErrors();
    +        $this->tokens = @token_get_all($code);
    +        $this->handleErrors();
    +
    +        if (false !== $scream) {
    +            ini_set('xdebug.scream', $scream);
    +        }
    +
    +        $this->code = $code; // keep the code around for __halt_compiler() handling
    +        $this->pos  = -1;
    +        $this->line =  1;
    +        $this->filePos = 0;
    +    }
    +
    +    protected function resetErrors() {
    +        if (function_exists('error_clear_last')) {
    +            error_clear_last();
    +        } else {
    +            // set error_get_last() to defined state by forcing an undefined variable error
    +            set_error_handler(function() { return false; }, 0);
    +            @$undefinedVariable;
    +            restore_error_handler();
    +        }
    +    }
    +
    +    protected function handleErrors() {
    +        $error = error_get_last();
    +        if (null === $error) {
    +            return;
    +        }
    +
    +        if (preg_match(
    +            '~^Unterminated comment starting line ([0-9]+)$~',
    +            $error['message'], $matches
    +        )) {
    +            throw new Error('Unterminated comment', (int) $matches[1]);
    +        }
    +
    +        if (preg_match(
    +            '~^Unexpected character in input:  \'(.)\' \(ASCII=([0-9]+)\)~s',
    +            $error['message'], $matches
    +        )) {
    +            throw new Error(sprintf(
    +                'Unexpected character "%s" (ASCII %d)',
    +                $matches[1], $matches[2]
    +            ));
    +        }
    +
    +        // PHP cuts error message after null byte, so need special case
    +        if (preg_match('~^Unexpected character in input:  \'$~', $error['message'])) {
    +            throw new Error('Unexpected null byte');
    +        }
    +    }
    +
    +    /**
    +     * Fetches the next token.
    +     *
    +     * The available attributes are determined by the 'usedAttributes' option, which can
    +     * be specified in the constructor. The following attributes are supported:
    +     *
    +     *  * 'comments'      => Array of PhpParser\Comment or PhpParser\Comment\Doc instances,
    +     *                       representing all comments that occurred between the previous
    +     *                       non-discarded token and the current one.
    +     *  * 'startLine'     => Line in which the node starts.
    +     *  * 'endLine'       => Line in which the node ends.
    +     *  * 'startTokenPos' => Offset into the token array of the first token in the node.
    +     *  * 'endTokenPos'   => Offset into the token array of the last token in the node.
    +     *  * 'startFilePos'  => Offset into the code string of the first character that is part of the node.
    +     *  * 'endFilePos'    => Offset into the code string of the last character that is part of the node.
    +     *
    +     * @param mixed $value           Variable to store token content in
    +     * @param mixed $startAttributes Variable to store start attributes in
    +     * @param mixed $endAttributes   Variable to store end attributes in
    +     *
    +     * @return int Token id
    +     */
    +    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
    +        $startAttributes = array();
    +        $endAttributes   = array();
    +
    +        while (1) {
    +            if (isset($this->tokens[++$this->pos])) {
    +                $token = $this->tokens[$this->pos];
    +            } else {
    +                // EOF token with ID 0
    +                $token = "\0";
    +            }
    +
    +            if (isset($this->usedAttributes['startLine'])) {
    +                $startAttributes['startLine'] = $this->line;
    +            }
    +            if (isset($this->usedAttributes['startTokenPos'])) {
    +                $startAttributes['startTokenPos'] = $this->pos;
    +            }
    +            if (isset($this->usedAttributes['startFilePos'])) {
    +                $startAttributes['startFilePos'] = $this->filePos;
    +            }
    +
    +            if (\is_string($token)) {
    +                $value = $token;
    +                if (isset($token[1])) {
    +                    // bug in token_get_all
    +                    $this->filePos += 2;
    +                    $id = ord('"');
    +                } else {
    +                    $this->filePos += 1;
    +                    $id = ord($token);
    +                }
    +            } elseif (!isset($this->dropTokens[$token[0]])) {
    +                $value = $token[1];
    +                $id = $this->tokenMap[$token[0]];
    +
    +                $this->line += substr_count($value, "\n");
    +                $this->filePos += \strlen($value);
    +            } else {
    +                if (T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0]) {
    +                    if (isset($this->usedAttributes['comments'])) {
    +                        $comment = T_DOC_COMMENT === $token[0]
    +                            ? new Comment\Doc($token[1], $this->line, $this->filePos)
    +                            : new Comment($token[1], $this->line, $this->filePos);
    +                        $startAttributes['comments'][] = $comment;
    +                    }
    +                }
    +
    +                $this->line += substr_count($token[1], "\n");
    +                $this->filePos += \strlen($token[1]);
    +                continue;
    +            }
    +
    +            if (isset($this->usedAttributes['endLine'])) {
    +                $endAttributes['endLine'] = $this->line;
    +            }
    +            if (isset($this->usedAttributes['endTokenPos'])) {
    +                $endAttributes['endTokenPos'] = $this->pos;
    +            }
    +            if (isset($this->usedAttributes['endFilePos'])) {
    +                $endAttributes['endFilePos'] = $this->filePos - 1;
    +            }
    +
    +            return $id;
    +        }
    +
    +        throw new \RuntimeException('Reached end of lexer loop');
    +    }
    +
    +    /**
    +     * Returns the token array for current code.
    +     *
    +     * The token array is in the same format as provided by the
    +     * token_get_all() function and does not discard tokens (i.e.
    +     * whitespace and comments are included). The token position
    +     * attributes are against this token array.
    +     *
    +     * @return array Array of tokens in token_get_all() format
    +     */
    +    public function getTokens() {
    +        return $this->tokens;
    +    }
    +
    +    /**
    +     * Handles __halt_compiler() by returning the text after it.
    +     *
    +     * @return string Remaining text
    +     */
    +    public function handleHaltCompiler() {
    +        // text after T_HALT_COMPILER, still including ();
    +        $textAfter = substr($this->code, $this->filePos);
    +
    +        // ensure that it is followed by ();
    +        // this simplifies the situation, by not allowing any comments
    +        // in between of the tokens.
    +        if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
    +            throw new Error('__HALT_COMPILER must be followed by "();"');
    +        }
    +
    +        // prevent the lexer from returning any further tokens
    +        $this->pos = count($this->tokens);
    +
    +        // return with (); removed
    +        return (string) substr($textAfter, strlen($matches[0])); // (string) converts false to ''
    +    }
    +
    +    /**
    +     * Creates the token map.
    +     *
    +     * The token map maps the PHP internal token identifiers
    +     * to the identifiers used by the Parser. Additionally it
    +     * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'.
    +     *
    +     * @return array The token map
    +     */
    +    protected function createTokenMap() {
    +        $tokenMap = array();
    +
    +        // 256 is the minimum possible token number, as everything below
    +        // it is an ASCII value
    +        for ($i = 256; $i < 1000; ++$i) {
    +            if (T_DOUBLE_COLON === $i) {
    +                // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
    +                $tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM;
    +            } elseif(T_OPEN_TAG_WITH_ECHO === $i) {
    +                // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
    +                $tokenMap[$i] = Tokens::T_ECHO;
    +            } elseif(T_CLOSE_TAG === $i) {
    +                // T_CLOSE_TAG is equivalent to ';'
    +                $tokenMap[$i] = ord(';');
    +            } elseif ('UNKNOWN' !== $name = token_name($i)) {
    +                if ('T_HASHBANG' === $name) {
    +                    // HHVM uses a special token for #! hashbang lines
    +                    $tokenMap[$i] = Tokens::T_INLINE_HTML;
    +                } else if (defined($name = 'PhpParser\Parser\Tokens::' . $name)) {
    +                    // Other tokens can be mapped directly
    +                    $tokenMap[$i] = constant($name);
    +                }
    +            }
    +        }
    +
    +        // HHVM uses a special token for numbers that overflow to double
    +        if (defined('T_ONUMBER')) {
    +            $tokenMap[T_ONUMBER] = Tokens::T_DNUMBER;
    +        }
    +        // HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
    +        if (defined('T_COMPILER_HALT_OFFSET')) {
    +            $tokenMap[T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
    +        }
    +
    +        return $tokenMap;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/core/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php
    new file mode 100644
    index 0000000..257f086
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php
    @@ -0,0 +1,205 @@
    + array(
    +                'finally'       => Tokens::T_FINALLY,
    +                'yield'         => Tokens::T_YIELD,
    +            ),
    +        );
    +
    +        $this->newKeywords = array();
    +        foreach ($newKeywordsPerVersion as $version => $newKeywords) {
    +            if (version_compare(PHP_VERSION, $version, '>=')) {
    +                break;
    +            }
    +
    +            $this->newKeywords += $newKeywords;
    +        }
    +
    +        if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
    +            return;
    +        }
    +        $this->tokenMap[self::T_COALESCE]   = Tokens::T_COALESCE;
    +        $this->tokenMap[self::T_SPACESHIP]  = Tokens::T_SPACESHIP;
    +        $this->tokenMap[self::T_YIELD_FROM] = Tokens::T_YIELD_FROM;
    +
    +        if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
    +            return;
    +        }
    +        $this->tokenMap[self::T_ELLIPSIS]  = Tokens::T_ELLIPSIS;
    +        $this->tokenMap[self::T_POW]       = Tokens::T_POW;
    +        $this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
    +    }
    +
    +    public function startLexing($code) {
    +        $this->inObjectAccess = false;
    +
    +        $preprocessedCode = $this->preprocessCode($code);
    +        parent::startLexing($preprocessedCode);
    +        if ($preprocessedCode !== $code) {
    +            $this->postprocessTokens();
    +        }
    +
    +        // Set code property back to the original code, so __halt_compiler()
    +        // handling and (start|end)FilePos attributes use the correct offsets
    +        $this->code = $code;
    +    }
    +
    +    /*
    +     * Replaces new features in the code by ~__EMU__{NAME}__{DATA}__~ sequences.
    +     * ~LABEL~ is never valid PHP code, that's why we can (to some degree) safely
    +     * use it here.
    +     * Later when preprocessing the tokens these sequences will either be replaced
    +     * by real tokens or replaced with their original content (e.g. if they occurred
    +     * inside a string, i.e. a place where they don't have a special meaning).
    +     */
    +    protected function preprocessCode($code) {
    +        if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
    +            return $code;
    +        }
    +
    +        $code = str_replace('??', '~__EMU__COALESCE__~', $code);
    +        $code = str_replace('<=>', '~__EMU__SPACESHIP__~', $code);
    +        $code = preg_replace_callback('(yield[ \n\r\t]+from)', function($matches) {
    +            // Encoding $0 in order to preserve exact whitespace
    +            return '~__EMU__YIELDFROM__' . bin2hex($matches[0]) . '__~';
    +        }, $code);
    +
    +        if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
    +            return $code;
    +        }
    +
    +        $code = str_replace('...', '~__EMU__ELLIPSIS__~', $code);
    +        $code = preg_replace('((?tokens); $i < $c; ++$i) {
    +            // first check that the following tokens are of form ~LABEL~,
    +            // then match the __EMU__... sequence.
    +            if ('~' === $this->tokens[$i]
    +                && isset($this->tokens[$i + 2])
    +                && '~' === $this->tokens[$i + 2]
    +                && T_STRING === $this->tokens[$i + 1][0]
    +                && preg_match('(^__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?$)', $this->tokens[$i + 1][1], $matches)
    +            ) {
    +                if ('ELLIPSIS' === $matches[1]) {
    +                    $replace = array(
    +                        array(self::T_ELLIPSIS, '...', $this->tokens[$i + 1][2])
    +                    );
    +                } else if ('POW' === $matches[1]) {
    +                    $replace = array(
    +                        array(self::T_POW, '**', $this->tokens[$i + 1][2])
    +                    );
    +                } else if ('POWEQUAL' === $matches[1]) {
    +                    $replace = array(
    +                        array(self::T_POW_EQUAL, '**=', $this->tokens[$i + 1][2])
    +                    );
    +                } else if ('COALESCE' === $matches[1]) {
    +                    $replace = array(
    +                        array(self::T_COALESCE, '??', $this->tokens[$i + 1][2])
    +                    );
    +                } else if ('SPACESHIP' === $matches[1]) {
    +                    $replace = array(
    +                        array(self::T_SPACESHIP, '<=>', $this->tokens[$i + 1][2]),
    +                    );
    +                } else if ('YIELDFROM' === $matches[1]) {
    +                    $content = hex2bin($matches[2]);
    +                    $replace = array(
    +                        array(self::T_YIELD_FROM, $content, $this->tokens[$i + 1][2] - substr_count($content, "\n"))
    +                    );
    +                } else {
    +                    throw new \RuntimeException('Invalid __EMU__ sequence');
    +                }
    +
    +                array_splice($this->tokens, $i, 3, $replace);
    +                $c -= 3 - count($replace);
    +            // for multichar tokens (e.g. strings) replace any ~__EMU__...~ sequences
    +            // in their content with the original character sequence
    +            } elseif (is_array($this->tokens[$i])
    +                      && 0 !== strpos($this->tokens[$i][1], '__EMU__')
    +            ) {
    +                $this->tokens[$i][1] = preg_replace_callback(
    +                    '(~__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?~)',
    +                    array($this, 'restoreContentCallback'),
    +                    $this->tokens[$i][1]
    +                );
    +            }
    +        }
    +    }
    +
    +    /*
    +     * This method is a callback for restoring EMU sequences in
    +     * multichar tokens (like strings) to their original value.
    +     */
    +    public function restoreContentCallback(array $matches) {
    +        if ('ELLIPSIS' === $matches[1]) {
    +            return '...';
    +        } else if ('POW' === $matches[1]) {
    +            return '**';
    +        } else if ('POWEQUAL' === $matches[1]) {
    +            return '**=';
    +        } else if ('COALESCE' === $matches[1]) {
    +            return '??';
    +        } else if ('SPACESHIP' === $matches[1]) {
    +            return '<=>';
    +        } else if ('YIELDFROM' === $matches[1]) {
    +            return hex2bin($matches[2]);
    +        } else {
    +            return $matches[0];
    +        }
    +    }
    +
    +    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
    +        $token = parent::getNextToken($value, $startAttributes, $endAttributes);
    +
    +        // replace new keywords by their respective tokens. This is not done
    +        // if we currently are in an object access (e.g. in $obj->namespace
    +        // "namespace" stays a T_STRING tokens and isn't converted to T_NAMESPACE)
    +        if (Tokens::T_STRING === $token && !$this->inObjectAccess) {
    +            if (isset($this->newKeywords[strtolower($value)])) {
    +                return $this->newKeywords[strtolower($value)];
    +            }
    +        } else {
    +            // keep track of whether we currently are in an object access (after ->)
    +            $this->inObjectAccess = Tokens::T_OBJECT_OPERATOR === $token;
    +        }
    +
    +        return $token;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node.php
    new file mode 100644
    index 0000000..a28ccc1
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node.php
    @@ -0,0 +1,77 @@
    +value = $value;
    +        $this->byRef = $byRef;
    +        $this->unpack = $unpack;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('value', 'byRef', 'unpack');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Const_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Const_.php
    new file mode 100644
    index 0000000..26322d4
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Const_.php
    @@ -0,0 +1,30 @@
    +name = $name;
    +        $this->value = $value;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name', 'value');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php
    new file mode 100644
    index 0000000..2dae5bf
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php
    @@ -0,0 +1,9 @@
    +var = $var;
    +        $this->dim = $dim;
    +    }
    +
    +    public function getSubnodeNames() {
    +        return array('var', 'dim');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php
    new file mode 100644
    index 0000000..de73ef4
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php
    @@ -0,0 +1,34 @@
    +key = $key;
    +        $this->value = $value;
    +        $this->byRef = $byRef;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('key', 'value', 'byRef');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php
    new file mode 100644
    index 0000000..9151f27
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php
    @@ -0,0 +1,30 @@
    +items = $items;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('items');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php
    new file mode 100644
    index 0000000..1f280ff
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php
    @@ -0,0 +1,30 @@
    +var = $var;
    +        $this->expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var', 'expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php
    new file mode 100644
    index 0000000..83540a0
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php
    @@ -0,0 +1,30 @@
    +var = $var;
    +        $this->expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var', 'expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php
    new file mode 100644
    index 0000000..9e3ed82
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php
    @@ -0,0 +1,9 @@
    +var = $var;
    +        $this->expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var', 'expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php
    new file mode 100644
    index 0000000..6f87e23
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php
    @@ -0,0 +1,30 @@
    +left = $left;
    +        $this->right = $right;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('left', 'right');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php
    new file mode 100644
    index 0000000..bd6c5c1
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php
    @@ -0,0 +1,9 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php
    new file mode 100644
    index 0000000..8f543b8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php
    new file mode 100644
    index 0000000..bf850dc
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php
    new file mode 100644
    index 0000000..08b3a38
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php
    @@ -0,0 +1,9 @@
    +class = $class;
    +        $this->name = $name;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('class', 'name');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php
    new file mode 100644
    index 0000000..198ec4d
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php
    new file mode 100644
    index 0000000..2686086
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php
    @@ -0,0 +1,65 @@
    + false  : Whether the closure is static
    +     *                          'byRef'      => false  : Whether to return by reference
    +     *                          'params'     => array(): Parameters
    +     *                          'uses'       => array(): use()s
    +     *                          'returnType' => null   : Return type
    +     *                          'stmts'      => array(): Statements
    +     * @param array $attributes Additional attributes
    +     */
    +    public function __construct(array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->static = isset($subNodes['static']) ? $subNodes['static'] : false;
    +        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
    +        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
    +        $this->uses = isset($subNodes['uses']) ? $subNodes['uses'] : array();
    +        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
    +        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('static', 'byRef', 'params', 'uses', 'returnType', 'stmts');
    +    }
    +
    +    public function returnsByRef() {
    +        return $this->byRef;
    +    }
    +
    +    public function getParams() {
    +        return $this->params;
    +    }
    +
    +    public function getReturnType() {
    +        return $this->returnType;
    +    }
    +
    +    public function getStmts() {
    +        return $this->stmts;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php
    new file mode 100644
    index 0000000..f4d0202
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php
    @@ -0,0 +1,30 @@
    +var = $var;
    +        $this->byRef = $byRef;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var', 'byRef');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php
    new file mode 100644
    index 0000000..5f1a92d
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php
    @@ -0,0 +1,27 @@
    +name = $name;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php
    new file mode 100644
    index 0000000..0c0509e
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php
    new file mode 100644
    index 0000000..750c814
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php
    new file mode 100644
    index 0000000..eadffd0
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php
    new file mode 100644
    index 0000000..b0b94cf
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php
    @@ -0,0 +1,30 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php
    new file mode 100644
    index 0000000..60d0050
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php
    @@ -0,0 +1,31 @@
    +name = $name;
    +        $this->args = $args;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name', 'args');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php
    new file mode 100644
    index 0000000..b27f3af
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php
    @@ -0,0 +1,35 @@
    +expr = $expr;
    +        $this->type = $type;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr', 'type');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php
    new file mode 100644
    index 0000000..3ef8ad1
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php
    @@ -0,0 +1,31 @@
    +expr = $expr;
    +        $this->class = $class;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr', 'class');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php
    new file mode 100644
    index 0000000..4ec1d02
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php
    @@ -0,0 +1,26 @@
    +vars = $vars;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('vars');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php
    new file mode 100644
    index 0000000..28ba612
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php
    @@ -0,0 +1,26 @@
    +vars = $vars;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('vars');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php
    new file mode 100644
    index 0000000..a6cdd07
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php
    @@ -0,0 +1,35 @@
    +var = $var;
    +        $this->name = $name;
    +        $this->args = $args;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var', 'name', 'args');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php
    new file mode 100644
    index 0000000..a8c8779
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php
    @@ -0,0 +1,31 @@
    +class = $class;
    +        $this->args = $args;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('class', 'args');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php
    new file mode 100644
    index 0000000..06ce547
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php
    @@ -0,0 +1,26 @@
    +var = $var;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php
    new file mode 100644
    index 0000000..54865ba
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php
    @@ -0,0 +1,26 @@
    +var = $var;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php
    new file mode 100644
    index 0000000..db30f51
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php
    @@ -0,0 +1,26 @@
    +var = $var;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php
    new file mode 100644
    index 0000000..0635602
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php
    @@ -0,0 +1,26 @@
    +var = $var;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php
    new file mode 100644
    index 0000000..0666ab8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php
    new file mode 100644
    index 0000000..adcf21c
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php
    @@ -0,0 +1,30 @@
    +var = $var;
    +        $this->name = $name;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('var', 'name');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php
    new file mode 100644
    index 0000000..9516a32
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php
    @@ -0,0 +1,26 @@
    +parts = $parts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('parts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php
    new file mode 100644
    index 0000000..5118f34
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php
    @@ -0,0 +1,35 @@
    +class = $class;
    +        $this->name = $name;
    +        $this->args = $args;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('class', 'name', 'args');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php
    new file mode 100644
    index 0000000..5126bda
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php
    @@ -0,0 +1,31 @@
    +class = $class;
    +        $this->name = $name;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('class', 'name');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php
    new file mode 100644
    index 0000000..5730139
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php
    @@ -0,0 +1,34 @@
    +cond = $cond;
    +        $this->if = $if;
    +        $this->else = $else;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('cond', 'if', 'else');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php
    new file mode 100644
    index 0000000..94635d6
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php
    new file mode 100644
    index 0000000..6514120
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php
    new file mode 100644
    index 0000000..3ae3ecd
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php
    @@ -0,0 +1,26 @@
    +name = $name;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php
    new file mode 100644
    index 0000000..993c82c
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php
    new file mode 100644
    index 0000000..f3ca88e
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php
    @@ -0,0 +1,30 @@
    +key = $key;
    +        $this->value = $value;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('key', 'value');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php
    new file mode 100644
    index 0000000..efe4333
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php
    @@ -0,0 +1,36 @@
    +parts = $parts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('parts');
    +    }
    +
    +    /**
    +     * Gets the first part of the name, i.e. everything before the first namespace separator.
    +     *
    +     * @return string First part of the name
    +     */
    +    public function getFirst() {
    +        return $this->parts[0];
    +    }
    +
    +    /**
    +     * Gets the last part of the name, i.e. everything after the last namespace separator.
    +     *
    +     * @return string Last part of the name
    +     */
    +    public function getLast() {
    +        return $this->parts[count($this->parts) - 1];
    +    }
    +
    +    /**
    +     * Checks whether the name is unqualified. (E.g. Name)
    +     *
    +     * @return bool Whether the name is unqualified
    +     */
    +    public function isUnqualified() {
    +        return 1 == count($this->parts);
    +    }
    +
    +    /**
    +     * Checks whether the name is qualified. (E.g. Name\Name)
    +     *
    +     * @return bool Whether the name is qualified
    +     */
    +    public function isQualified() {
    +        return 1 < count($this->parts);
    +    }
    +
    +    /**
    +     * Checks whether the name is fully qualified. (E.g. \Name)
    +     *
    +     * @return bool Whether the name is fully qualified
    +     */
    +    public function isFullyQualified() {
    +        return false;
    +    }
    +
    +    /**
    +     * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
    +     *
    +     * @return bool Whether the name is relative
    +     */
    +    public function isRelative() {
    +        return false;
    +    }
    +
    +    /**
    +     * Returns a string representation of the name by imploding the namespace parts with a separator.
    +     *
    +     * @param string $separator The separator to use (defaults to the namespace separator \)
    +     *
    +     * @return string String representation
    +     */
    +    public function toString($separator = '\\') {
    +        return implode($separator, $this->parts);
    +    }
    +
    +    /**
    +     * Returns a string representation of the name by imploding the namespace parts with the
    +     * namespace separator.
    +     *
    +     * @return string String representation
    +     */
    +    public function __toString() {
    +        return implode('\\', $this->parts);
    +    }
    +
    +    /**
    +     * Sets the whole name.
    +     *
    +     * @deprecated Create a new Name instead, or manually modify the $parts property
    +     *
    +     * @param string|array|self $name The name to set the whole name to
    +     */
    +    public function set($name) {
    +        $this->parts = self::prepareName($name);
    +    }
    +
    +    /**
    +     * Prepends a name to this name.
    +     *
    +     * @deprecated Use Name::concat($name1, $name2) instead
    +     *
    +     * @param string|array|self $name Name to prepend
    +     */
    +    public function prepend($name) {
    +        $this->parts = array_merge(self::prepareName($name), $this->parts);
    +    }
    +
    +    /**
    +     * Appends a name to this name.
    +     *
    +     * @deprecated Use Name::concat($name1, $name2) instead
    +     *
    +     * @param string|array|self $name Name to append
    +     */
    +    public function append($name) {
    +        $this->parts = array_merge($this->parts, self::prepareName($name));
    +    }
    +
    +    /**
    +     * Sets the first part of the name.
    +     *
    +     * @deprecated Use concat($first, $name->slice(1)) instead
    +     *
    +     * @param string|array|self $name The name to set the first part to
    +     */
    +    public function setFirst($name) {
    +        array_splice($this->parts, 0, 1, self::prepareName($name));
    +    }
    +
    +    /**
    +     * Sets the last part of the name.
    +     *
    +     * @param string|array|self $name The name to set the last part to
    +     */
    +    public function setLast($name) {
    +        array_splice($this->parts, -1, 1, self::prepareName($name));
    +    }
    +
    +    /**
    +     * Gets a slice of a name (similar to array_slice).
    +     *
    +     * This method returns a new instance of the same type as the original and with the same
    +     * attributes.
    +     *
    +     * If the slice is empty, a Name with an empty parts array is returned. While this is
    +     * meaningless in itself, it works correctly in conjunction with concat().
    +     *
    +     * @param int $offset Offset to start the slice at
    +     *
    +     * @return static Sliced name
    +     */
    +    public function slice($offset) {
    +        // TODO negative offset and length
    +        if ($offset < 0 || $offset > count($this->parts)) {
    +            throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset));
    +        }
    +
    +        return new static(array_slice($this->parts, $offset), $this->attributes);
    +    }
    +
    +    /**
    +     * Concatenate two names, yielding a new Name instance.
    +     *
    +     * The type of the generated instance depends on which class this method is called on, for
    +     * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance.
    +     *
    +     * @param string|array|self $name1      The first name
    +     * @param string|array|self $name2      The second name
    +     * @param array             $attributes Attributes to assign to concatenated name
    +     *
    +     * @return static Concatenated name
    +     */
    +    public static function concat($name1, $name2, array $attributes = []) {
    +        return new static(
    +            array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
    +        );
    +    }
    +
    +    /**
    +     * Prepares a (string, array or Name node) name for use in name changing methods by converting
    +     * it to an array.
    +     *
    +     * @param string|array|self $name Name to prepare
    +     *
    +     * @return array Prepared name
    +     */
    +    private static function prepareName($name) {
    +        if (is_string($name)) {
    +            return explode('\\', $name);
    +        } elseif (is_array($name)) {
    +            return $name;
    +        } elseif ($name instanceof self) {
    +            return $name->parts;
    +        }
    +
    +        throw new \InvalidArgumentException(
    +            'When changing a name you need to pass either a string, an array or a Name node'
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php
    new file mode 100644
    index 0000000..97cc111
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php
    @@ -0,0 +1,42 @@
    +type = $type;
    +        $this->byRef = $byRef;
    +        $this->variadic = $variadic;
    +        $this->name = $name;
    +        $this->default = $default;
    +
    +        if ($variadic && null !== $default) {
    +            throw new Error('Variadic parameter cannot have a default value', $default->getAttributes());
    +        }
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('type', 'byRef', 'variadic', 'name', 'default');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php
    new file mode 100644
    index 0000000..0117753
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php
    @@ -0,0 +1,7 @@
    +value = $value;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('value');
    +    }
    +
    +    /**
    +     * @internal
    +     *
    +     * Parses a DNUMBER token like PHP would.
    +     *
    +     * @param string $str A string number
    +     *
    +     * @return float The parsed number
    +     */
    +    public static function parse($str) {
    +        // if string contains any of .eE just cast it to float
    +        if (false !== strpbrk($str, '.eE')) {
    +            return (float) $str;
    +        }
    +
    +        // otherwise it's an integer notation that overflowed into a float
    +        // if it starts with 0 it's one of the special integer notations
    +        if ('0' === $str[0]) {
    +            // hex
    +            if ('x' === $str[1] || 'X' === $str[1]) {
    +                return hexdec($str);
    +            }
    +
    +            // bin
    +            if ('b' === $str[1] || 'B' === $str[1]) {
    +                return bindec($str);
    +            }
    +
    +            // oct
    +            // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
    +            // so that only the digits before that are used
    +            return octdec(substr($str, 0, strcspn($str, '89')));
    +        }
    +
    +        // dec
    +        return (float) $str;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php
    new file mode 100644
    index 0000000..ec3e971
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php
    @@ -0,0 +1,26 @@
    +parts = $parts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('parts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php
    new file mode 100644
    index 0000000..50cb1af
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php
    @@ -0,0 +1,26 @@
    +value = $value;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('value');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php
    new file mode 100644
    index 0000000..3559b98
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php
    @@ -0,0 +1,67 @@
    +value = $value;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('value');
    +    }
    +
    +    /**
    +     * Constructs an LNumber node from a string number literal.
    +     *
    +     * @param string $str               String number literal (decimal, octal, hex or binary)
    +     * @param array  $attributes        Additional attributes
    +     * @param bool   $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5)
    +     *
    +     * @return LNumber The constructed LNumber, including kind attribute
    +     */
    +    public static function fromString($str, array $attributes = array(), $allowInvalidOctal = false) {
    +        if ('0' !== $str[0] || '0' === $str) {
    +            $attributes['kind'] = LNumber::KIND_DEC;
    +            return new LNumber((int) $str, $attributes);
    +        }
    +
    +        if ('x' === $str[1] || 'X' === $str[1]) {
    +            $attributes['kind'] = LNumber::KIND_HEX;
    +            return new LNumber(hexdec($str), $attributes);
    +        }
    +
    +        if ('b' === $str[1] || 'B' === $str[1]) {
    +            $attributes['kind'] = LNumber::KIND_BIN;
    +            return new LNumber(bindec($str), $attributes);
    +        }
    +
    +        if (!$allowInvalidOctal && strpbrk($str, '89')) {
    +            throw new Error('Invalid numeric literal', $attributes);
    +        }
    +
    +        // use intval instead of octdec to get proper cutting behavior with malformed numbers
    +        $attributes['kind'] = LNumber::KIND_OCT;
    +        return new LNumber(intval($str, 8), $attributes);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php
    new file mode 100644
    index 0000000..a50d68f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php
    @@ -0,0 +1,28 @@
    + '\\',
    +        '$'  =>  '$',
    +        'n'  => "\n",
    +        'r'  => "\r",
    +        't'  => "\t",
    +        'f'  => "\f",
    +        'v'  => "\v",
    +        'e'  => "\x1B",
    +    );
    +
    +    /**
    +     * Constructs a string scalar node.
    +     *
    +     * @param string $value      Value of the string
    +     * @param array  $attributes Additional attributes
    +     */
    +    public function __construct($value, array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->value = $value;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('value');
    +    }
    +
    +    /**
    +     * @internal
    +     *
    +     * Parses a string token.
    +     *
    +     * @param string $str String token content
    +     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
    +     *
    +     * @return string The parsed string
    +     */
    +    public static function parse($str, $parseUnicodeEscape = true) {
    +        $bLength = 0;
    +        if ('b' === $str[0] || 'B' === $str[0]) {
    +            $bLength = 1;
    +        }
    +
    +        if ('\'' === $str[$bLength]) {
    +            return str_replace(
    +                array('\\\\', '\\\''),
    +                array(  '\\',   '\''),
    +                substr($str, $bLength + 1, -1)
    +            );
    +        } else {
    +            return self::parseEscapeSequences(
    +                substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape
    +            );
    +        }
    +    }
    +
    +    /**
    +     * @internal
    +     *
    +     * Parses escape sequences in strings (all string types apart from single quoted).
    +     *
    +     * @param string      $str   String without quotes
    +     * @param null|string $quote Quote type
    +     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
    +     *
    +     * @return string String with escape sequences parsed
    +     */
    +    public static function parseEscapeSequences($str, $quote, $parseUnicodeEscape = true) {
    +        if (null !== $quote) {
    +            $str = str_replace('\\' . $quote, $quote, $str);
    +        }
    +
    +        $extra = '';
    +        if ($parseUnicodeEscape) {
    +            $extra = '|u\{([0-9a-fA-F]+)\}';
    +        }
    +
    +        return preg_replace_callback(
    +            '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~',
    +            function($matches) {
    +                $str = $matches[1];
    +
    +                if (isset(self::$replacements[$str])) {
    +                    return self::$replacements[$str];
    +                } elseif ('x' === $str[0] || 'X' === $str[0]) {
    +                    return chr(hexdec($str));
    +                } elseif ('u' === $str[0]) {
    +                    return self::codePointToUtf8(hexdec($matches[2]));
    +                } else {
    +                    return chr(octdec($str));
    +                }
    +            },
    +            $str
    +        );
    +    }
    +
    +    private static function codePointToUtf8($num) {
    +        if ($num <= 0x7F) {
    +            return chr($num);
    +        }
    +        if ($num <= 0x7FF) {
    +            return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80);
    +        }
    +        if ($num <= 0xFFFF) {
    +            return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
    +        }
    +        if ($num <= 0x1FFFFF) {
    +            return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80)
    +                 . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
    +        }
    +        throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
    +    }
    +
    +    /**
    +     * @internal
    +     *
    +     * Parses a constant doc string.
    +     *
    +     * @param string $startToken Doc string start token content (<<num = $num;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('num');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php
    new file mode 100644
    index 0000000..03f892c
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php
    @@ -0,0 +1,30 @@
    +cond = $cond;
    +        $this->stmts = $stmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('cond', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php
    new file mode 100644
    index 0000000..2656afd
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php
    @@ -0,0 +1,34 @@
    +type = $type;
    +        $this->var = $var;
    +        $this->stmts = $stmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('type', 'var', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php
    new file mode 100644
    index 0000000..e8e2d6f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php
    @@ -0,0 +1,26 @@
    +consts = $consts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('consts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php
    new file mode 100644
    index 0000000..f6831a6
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php
    @@ -0,0 +1,44 @@
    +stmts as $stmt) {
    +            if ($stmt instanceof ClassMethod) {
    +                $methods[] = $stmt;
    +            }
    +        }
    +        return $methods;
    +    }
    +
    +    /**
    +     * Gets method with the given name defined directly in this class/interface/trait.
    +     *
    +     * @param string $name Name of the method (compared case-insensitively)
    +     *
    +     * @return ClassMethod|null Method node or null if the method does not exist
    +     */
    +    public function getMethod($name) {
    +        $lowerName = strtolower($name);
    +        foreach ($this->stmts as $stmt) {
    +            if ($stmt instanceof ClassMethod && $lowerName === strtolower($stmt->name)) {
    +                return $stmt;
    +            }
    +        }
    +        return null;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php
    new file mode 100644
    index 0000000..dfdd8ab
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php
    @@ -0,0 +1,101 @@
    + MODIFIER_PUBLIC: Type
    +     *                                'byRef'      => false          : Whether to return by reference
    +     *                                'params'     => array()        : Parameters
    +     *                                'returnType' => null           : Return type
    +     *                                'stmts'      => array()        : Statements
    +     * @param array       $attributes Additional attributes
    +     */
    +    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->type = isset($subNodes['type']) ? $subNodes['type'] : 0;
    +        $this->byRef = isset($subNodes['byRef'])  ? $subNodes['byRef']  : false;
    +        $this->name = $name;
    +        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
    +        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
    +        $this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : array();
    +
    +        if ($this->type & Class_::MODIFIER_STATIC) {
    +            switch (strtolower($this->name)) {
    +                case '__construct':
    +                    throw new Error(sprintf('Constructor %s() cannot be static', $this->name));
    +                case '__destruct':
    +                    throw new Error(sprintf('Destructor %s() cannot be static', $this->name));
    +                case '__clone':
    +                    throw new Error(sprintf('Clone method %s() cannot be static', $this->name));
    +            }
    +        }
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('type', 'byRef', 'name', 'params', 'returnType', 'stmts');
    +    }
    +
    +    public function returnsByRef() {
    +        return $this->byRef;
    +    }
    +
    +    public function getParams() {
    +        return $this->params;
    +    }
    +
    +    public function getReturnType() {
    +        return $this->returnType;
    +    }
    +
    +    public function getStmts() {
    +        return $this->stmts;
    +    }
    +
    +    public function isPublic() {
    +        return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
    +            || ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
    +    }
    +
    +    public function isProtected() {
    +        return (bool) ($this->type & Class_::MODIFIER_PROTECTED);
    +    }
    +
    +    public function isPrivate() {
    +        return (bool) ($this->type & Class_::MODIFIER_PRIVATE);
    +    }
    +
    +    public function isAbstract() {
    +        return (bool) ($this->type & Class_::MODIFIER_ABSTRACT);
    +    }
    +
    +    public function isFinal() {
    +        return (bool) ($this->type & Class_::MODIFIER_FINAL);
    +    }
    +
    +    public function isStatic() {
    +        return (bool) ($this->type & Class_::MODIFIER_STATIC);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php
    new file mode 100644
    index 0000000..6ef093d
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php
    @@ -0,0 +1,112 @@
    + true,
    +        'parent' => true,
    +        'static' => true,
    +    );
    +
    +    /**
    +     * Constructs a class node.
    +     *
    +     * @param string|null $name       Name
    +     * @param array       $subNodes   Array of the following optional subnodes:
    +     *                                'type'       => 0      : Type
    +     *                                'extends'    => null   : Name of extended class
    +     *                                'implements' => array(): Names of implemented interfaces
    +     *                                'stmts'      => array(): Statements
    +     * @param array       $attributes Additional attributes
    +     */
    +    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->type = isset($subNodes['type']) ? $subNodes['type'] : 0;
    +        $this->name = $name;
    +        $this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : null;
    +        $this->implements = isset($subNodes['implements']) ? $subNodes['implements'] : array();
    +        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    +
    +        if (null !== $this->name && isset(self::$specialNames[strtolower($this->name)])) {
    +            throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
    +        }
    +
    +        if (isset(self::$specialNames[strtolower($this->extends)])) {
    +            throw new Error(
    +                sprintf('Cannot use \'%s\' as class name as it is reserved', $this->extends),
    +                $this->extends->getAttributes()
    +            );
    +        }
    +
    +        foreach ($this->implements as $interface) {
    +            if (isset(self::$specialNames[strtolower($interface)])) {
    +                throw new Error(
    +                    sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
    +                    $interface->getAttributes()
    +                );
    +            }
    +        }
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('type', 'name', 'extends', 'implements', 'stmts');
    +    }
    +
    +    public function isAbstract() {
    +        return (bool) ($this->type & self::MODIFIER_ABSTRACT);
    +    }
    +
    +    public function isFinal() {
    +        return (bool) ($this->type & self::MODIFIER_FINAL);
    +    }
    +
    +    public function isAnonymous() {
    +        return null === $this->name;
    +    }
    +
    +    /**
    +     * @internal
    +     */
    +    public static function verifyModifier($a, $b) {
    +        if ($a & self::VISIBILITY_MODIFER_MASK && $b & self::VISIBILITY_MODIFER_MASK) {
    +            throw new Error('Multiple access type modifiers are not allowed');
    +        }
    +
    +        if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
    +            throw new Error('Multiple abstract modifiers are not allowed');
    +        }
    +
    +        if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) {
    +            throw new Error('Multiple static modifiers are not allowed');
    +        }
    +
    +        if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
    +            throw new Error('Multiple final modifiers are not allowed');
    +        }
    +
    +        if ($a & 48 && $b & 48) {
    +            throw new Error('Cannot use the final modifier on an abstract class member');
    +        }
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php
    new file mode 100644
    index 0000000..8b2eecd
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php
    @@ -0,0 +1,26 @@
    +consts = $consts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('consts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php
    new file mode 100644
    index 0000000..f78e19a
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php
    @@ -0,0 +1,26 @@
    +num = $num;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('num');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php
    new file mode 100644
    index 0000000..829dbaf
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php
    @@ -0,0 +1,30 @@
    +value pair node.
    +     *
    +     * @param string    $key        Key
    +     * @param Node\Expr $value      Value
    +     * @param array     $attributes Additional attributes
    +     */
    +    public function __construct($key, Node\Expr $value, array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->key = $key;
    +        $this->value = $value;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('key', 'value');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php
    new file mode 100644
    index 0000000..64b9efc
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php
    @@ -0,0 +1,29 @@
    +declares = $declares;
    +        $this->stmts = $stmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('declares', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php
    new file mode 100644
    index 0000000..dd4c6c8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php
    @@ -0,0 +1,30 @@
    +cond = $cond;
    +        $this->stmts = $stmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('cond', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php
    new file mode 100644
    index 0000000..11e1070
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php
    @@ -0,0 +1,26 @@
    +exprs = $exprs;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('exprs');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php
    new file mode 100644
    index 0000000..608878f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php
    @@ -0,0 +1,30 @@
    +cond = $cond;
    +        $this->stmts = $stmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('cond', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php
    new file mode 100644
    index 0000000..c91a148
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php
    @@ -0,0 +1,26 @@
    +stmts = $stmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php
    new file mode 100644
    index 0000000..2ca88a3
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php
    @@ -0,0 +1,39 @@
    + array(): Init expressions
    +     *                          'cond'  => array(): Loop conditions
    +     *                          'loop'  => array(): Loop expressions
    +     *                          'stmts' => array(): Statements
    +     * @param array $attributes Additional attributes
    +     */
    +    public function __construct(array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->init = isset($subNodes['init']) ? $subNodes['init'] : array();
    +        $this->cond = isset($subNodes['cond']) ? $subNodes['cond'] : array();
    +        $this->loop = isset($subNodes['loop']) ? $subNodes['loop'] : array();
    +        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('init', 'cond', 'loop', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php
    new file mode 100644
    index 0000000..d2c6432
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php
    @@ -0,0 +1,43 @@
    + null   : Variable to assign key to
    +     *                              'byRef'  => false  : Whether to assign value by reference
    +     *                              'stmts'  => array(): Statements
    +     * @param array     $attributes Additional attributes
    +     */
    +    public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->expr = $expr;
    +        $this->keyVar = isset($subNodes['keyVar']) ? $subNodes['keyVar'] : null;
    +        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
    +        $this->valueVar = $valueVar;
    +        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr', 'keyVar', 'byRef', 'valueVar', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php
    new file mode 100644
    index 0000000..2df68e5
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php
    @@ -0,0 +1,60 @@
    + false  : Whether to return by reference
    +     *                           'params'     => array(): Parameters
    +     *                           'returnType' => null   : Return type
    +     *                           'stmts'      => array(): Statements
    +     * @param array  $attributes Additional attributes
    +     */
    +    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
    +        $this->name = $name;
    +        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
    +        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
    +        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('byRef', 'name', 'params', 'returnType', 'stmts');
    +    }
    +
    +    public function returnsByRef() {
    +        return $this->byRef;
    +    }
    +
    +    public function getParams() {
    +        return $this->params;
    +    }
    +
    +    public function getReturnType() {
    +        return $this->returnType;
    +    }
    +
    +    public function getStmts() {
    +        return $this->stmts;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php
    new file mode 100644
    index 0000000..29fbc48
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php
    @@ -0,0 +1,26 @@
    +vars = $vars;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('vars');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php
    new file mode 100644
    index 0000000..b087fe0
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php
    @@ -0,0 +1,26 @@
    +name = $name;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php
    new file mode 100644
    index 0000000..d5f46d0
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php
    @@ -0,0 +1,35 @@
    +type = $type;
    +        $this->prefix = $prefix;
    +        $this->uses = $uses;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('type', 'prefix', 'uses');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php
    new file mode 100644
    index 0000000..c33ec9f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php
    @@ -0,0 +1,26 @@
    +remaining = $remaining;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('remaining');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php
    new file mode 100644
    index 0000000..98bda35
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php
    @@ -0,0 +1,39 @@
    + array(): Statements
    +     *                              'elseifs' => array(): Elseif clauses
    +     *                              'else'    => null   : Else clause
    +     * @param array     $attributes Additional attributes
    +     */
    +    public function __construct(Node\Expr $cond, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->cond = $cond;
    +        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    +        $this->elseifs = isset($subNodes['elseifs']) ? $subNodes['elseifs'] : array();
    +        $this->else = isset($subNodes['else']) ? $subNodes['else'] : null;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('cond', 'stmts', 'elseifs', 'else');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php
    new file mode 100644
    index 0000000..accebe6
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php
    @@ -0,0 +1,26 @@
    +value = $value;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('value');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php
    new file mode 100644
    index 0000000..cb82480
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php
    @@ -0,0 +1,51 @@
    + true,
    +        'parent' => true,
    +        'static' => true,
    +    );
    +
    +    /**
    +     * Constructs a class node.
    +     *
    +     * @param string $name       Name
    +     * @param array  $subNodes   Array of the following optional subnodes:
    +     *                           'extends' => array(): Name of extended interfaces
    +     *                           'stmts'   => array(): Statements
    +     * @param array  $attributes Additional attributes
    +     */
    +    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->name = $name;
    +        $this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : array();
    +        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    +
    +        if (isset(self::$specialNames[strtolower($this->name)])) {
    +            throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
    +        }
    +
    +        foreach ($this->extends as $interface) {
    +            if (isset(self::$specialNames[strtolower($interface)])) {
    +                throw new Error(
    +                    sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
    +                    $interface->getAttributes()
    +                );
    +            }
    +        }
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name', 'extends', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php
    new file mode 100644
    index 0000000..edd0ee9
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php
    @@ -0,0 +1,26 @@
    +name = $name;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php
    new file mode 100644
    index 0000000..fa23121
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php
    @@ -0,0 +1,52 @@
    + true,
    +        'parent' => true,
    +        'static' => true,
    +    );
    +
    +    /**
    +     * Constructs a namespace node.
    +     *
    +     * @param null|Node\Name $name       Name
    +     * @param null|Node[]    $stmts      Statements
    +     * @param array          $attributes Additional attributes
    +     */
    +    public function __construct(Node\Name $name = null, $stmts = array(), array $attributes = array()) {
    +        parent::__construct($attributes);
    +        $this->name = $name;
    +        $this->stmts = $stmts;
    +
    +        if (isset(self::$specialNames[strtolower($this->name)])) {
    +            throw new Error(
    +                sprintf('Cannot use \'%s\' as namespace name', $this->name),
    +                $this->name->getAttributes()
    +            );
    +        }
    +
    +        if (null !== $this->stmts) {
    +            foreach ($this->stmts as $stmt) {
    +                if ($stmt instanceof self) {
    +                    throw new Error('Namespace declarations cannot be nested', $stmt->getAttributes());
    +                }
    +            }
    +        }
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php
    new file mode 100644
    index 0000000..270dd09
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php
    @@ -0,0 +1,13 @@
    +type = $type;
    +        $this->props = $props;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('type', 'props');
    +    }
    +
    +    public function isPublic() {
    +        return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
    +            || ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
    +    }
    +
    +    public function isProtected() {
    +        return (bool) ($this->type & Class_::MODIFIER_PROTECTED);
    +    }
    +
    +    public function isPrivate() {
    +        return (bool) ($this->type & Class_::MODIFIER_PRIVATE);
    +    }
    +
    +    public function isStatic() {
    +        return (bool) ($this->type & Class_::MODIFIER_STATIC);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php
    new file mode 100644
    index 0000000..b2d29dc
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php
    @@ -0,0 +1,30 @@
    +name = $name;
    +        $this->default = $default;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name', 'default');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php
    new file mode 100644
    index 0000000..b642841
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php
    new file mode 100644
    index 0000000..4bc5dd2
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php
    @@ -0,0 +1,30 @@
    +name = $name;
    +        $this->default = $default;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name', 'default');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php
    new file mode 100644
    index 0000000..37cc0b3
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php
    @@ -0,0 +1,26 @@
    +vars = $vars;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('vars');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php
    new file mode 100644
    index 0000000..72d667b
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php
    @@ -0,0 +1,30 @@
    +cond = $cond;
    +        $this->cases = $cases;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('cond', 'cases');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php
    new file mode 100644
    index 0000000..f8ff6aa
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php
    @@ -0,0 +1,26 @@
    +expr = $expr;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('expr');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php
    new file mode 100644
    index 0000000..a29874b
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php
    @@ -0,0 +1,31 @@
    +traits = $traits;
    +        $this->adaptations = $adaptations;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('traits', 'adaptations');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php
    new file mode 100644
    index 0000000..c6038c8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php
    @@ -0,0 +1,13 @@
    +trait = $trait;
    +        $this->method = $method;
    +        $this->newModifier = $newModifier;
    +        $this->newName = $newName;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('trait', 'method', 'newModifier', 'newName');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php
    new file mode 100644
    index 0000000..1f6bc1f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php
    @@ -0,0 +1,30 @@
    +trait = $trait;
    +        $this->method = $method;
    +        $this->insteadof = $insteadof;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('trait', 'method', 'insteadof');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php
    new file mode 100644
    index 0000000..356039f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php
    @@ -0,0 +1,25 @@
    +name = $name;
    +        $this->stmts = $stmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('name', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php
    new file mode 100644
    index 0000000..f6d2c84
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php
    @@ -0,0 +1,39 @@
    +stmts = $stmts;
    +        $this->catches = $catches;
    +        $this->finallyStmts = $finallyStmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('stmts', 'catches', 'finallyStmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php
    new file mode 100644
    index 0000000..0f00fe9
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php
    @@ -0,0 +1,26 @@
    +vars = $vars;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('vars');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php
    new file mode 100644
    index 0000000..2508f18
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php
    @@ -0,0 +1,46 @@
    +getLast();
    +        }
    +
    +        if ('self' == strtolower($alias) || 'parent' == strtolower($alias)) {
    +            throw new Error(sprintf(
    +                'Cannot use %s as %s because \'%2$s\' is a special class name',
    +                $name, $alias
    +            ));
    +        }
    +
    +        parent::__construct($attributes);
    +        $this->type = $type;
    +        $this->name = $name;
    +        $this->alias = $alias;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('type', 'name', 'alias');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php
    new file mode 100644
    index 0000000..6c89ebb
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php
    @@ -0,0 +1,43 @@
    +type = $type;
    +        $this->uses = $uses;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('type', 'uses');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php
    new file mode 100644
    index 0000000..afad1b2
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php
    @@ -0,0 +1,30 @@
    +cond = $cond;
    +        $this->stmts = $stmts;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('cond', 'stmts');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php b/core/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php
    new file mode 100644
    index 0000000..ab09bf9
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php
    @@ -0,0 +1,85 @@
    +attributes = $attributes;
    +    }
    +
    +    /**
    +     * Gets the type of the node.
    +     *
    +     * @return string Type of the node
    +     */
    +    public function getType() {
    +        return strtr(substr(rtrim(get_class($this), '_'), 15), '\\', '_');
    +    }
    +
    +    /**
    +     * Gets line the node started in.
    +     *
    +     * @return int Line
    +     */
    +    public function getLine() {
    +        return $this->getAttribute('startLine', -1);
    +    }
    +
    +    /**
    +     * Sets line the node started in.
    +     *
    +     * @param int $line Line
    +     */
    +    public function setLine($line) {
    +        $this->setAttribute('startLine', (int) $line);
    +    }
    +
    +    /**
    +     * Gets the doc comment of the node.
    +     *
    +     * The doc comment has to be the last comment associated with the node.
    +     *
    +     * @return null|Comment\Doc Doc comment object or null
    +     */
    +    public function getDocComment() {
    +        $comments = $this->getAttribute('comments');
    +        if (!$comments) {
    +            return null;
    +        }
    +
    +        $lastComment = $comments[count($comments) - 1];
    +        if (!$lastComment instanceof Comment\Doc) {
    +            return null;
    +        }
    +
    +        return $lastComment;
    +    }
    +
    +    public function setAttribute($key, $value) {
    +        $this->attributes[$key] = $value;
    +    }
    +
    +    public function hasAttribute($key) {
    +        return array_key_exists($key, $this->attributes);
    +    }
    +
    +    public function &getAttribute($key, $default = null) {
    +        if (!array_key_exists($key, $this->attributes)) {
    +            return $default;
    +        } else {
    +            return $this->attributes[$key];
    +        }
    +    }
    +
    +    public function getAttributes() {
    +        return $this->attributes;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php b/core/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php
    new file mode 100644
    index 0000000..b931ada
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php
    @@ -0,0 +1,76 @@
    +dumpComments = !empty($options['dumpComments']);
    +    }
    +
    +    /**
    +     * Dumps a node or array.
    +     *
    +     * @param array|Node $node Node or array to dump
    +     *
    +     * @return string Dumped value
    +     */
    +    public function dump($node) {
    +        if ($node instanceof Node) {
    +            $r = $node->getType() . '(';
    +
    +            foreach ($node->getSubNodeNames() as $key) {
    +                $r .= "\n    " . $key . ': ';
    +
    +                $value = $node->$key;
    +                if (null === $value) {
    +                    $r .= 'null';
    +                } elseif (false === $value) {
    +                    $r .= 'false';
    +                } elseif (true === $value) {
    +                    $r .= 'true';
    +                } elseif (is_scalar($value)) {
    +                    $r .= $value;
    +                } else {
    +                    $r .= str_replace("\n", "\n    ", $this->dump($value));
    +                }
    +            }
    +
    +            if ($this->dumpComments && $comments = $node->getAttribute('comments')) {
    +                $r .= "\n    comments: " . str_replace("\n", "\n    ", $this->dump($comments));
    +            }
    +        } elseif (is_array($node)) {
    +            $r = 'array(';
    +
    +            foreach ($node as $key => $value) {
    +                $r .= "\n    " . $key . ': ';
    +
    +                if (null === $value) {
    +                    $r .= 'null';
    +                } elseif (false === $value) {
    +                    $r .= 'false';
    +                } elseif (true === $value) {
    +                    $r .= 'true';
    +                } elseif (is_scalar($value)) {
    +                    $r .= $value;
    +                } else {
    +                    $r .= str_replace("\n", "\n    ", $this->dump($value));
    +                }
    +            }
    +        } elseif ($node instanceof Comment) {
    +            return $node->getReformattedText();
    +        } else {
    +            throw new \InvalidArgumentException('Can only dump nodes and arrays.');
    +        }
    +
    +        return $r . "\n)";
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php b/core/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php
    new file mode 100644
    index 0000000..1523e31
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php
    @@ -0,0 +1,162 @@
    +visitors = array();
    +        $this->cloneNodes = $cloneNodes;
    +    }
    +
    +    /**
    +     * Adds a visitor.
    +     *
    +     * @param NodeVisitor $visitor Visitor to add
    +     */
    +    public function addVisitor(NodeVisitor $visitor) {
    +        $this->visitors[] = $visitor;
    +    }
    +
    +    /**
    +     * Removes an added visitor.
    +     *
    +     * @param NodeVisitor $visitor
    +     */
    +    public function removeVisitor(NodeVisitor $visitor) {
    +        foreach ($this->visitors as $index => $storedVisitor) {
    +            if ($storedVisitor === $visitor) {
    +                unset($this->visitors[$index]);
    +                break;
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Traverses an array of nodes using the registered visitors.
    +     *
    +     * @param Node[] $nodes Array of nodes
    +     *
    +     * @return Node[] Traversed array of nodes
    +     */
    +    public function traverse(array $nodes) {
    +        foreach ($this->visitors as $visitor) {
    +            if (null !== $return = $visitor->beforeTraverse($nodes)) {
    +                $nodes = $return;
    +            }
    +        }
    +
    +        $nodes = $this->traverseArray($nodes);
    +
    +        foreach ($this->visitors as $visitor) {
    +            if (null !== $return = $visitor->afterTraverse($nodes)) {
    +                $nodes = $return;
    +            }
    +        }
    +
    +        return $nodes;
    +    }
    +
    +    protected function traverseNode(Node $node) {
    +        if ($this->cloneNodes) {
    +            $node = clone $node;
    +        }
    +
    +        foreach ($node->getSubNodeNames() as $name) {
    +            $subNode =& $node->$name;
    +
    +            if (is_array($subNode)) {
    +                $subNode = $this->traverseArray($subNode);
    +            } elseif ($subNode instanceof Node) {
    +                $traverseChildren = true;
    +                foreach ($this->visitors as $visitor) {
    +                    $return = $visitor->enterNode($subNode);
    +                    if (self::DONT_TRAVERSE_CHILDREN === $return) {
    +                        $traverseChildren = false;
    +                    } else if (null !== $return) {
    +                        $subNode = $return;
    +                    }
    +                }
    +
    +                if ($traverseChildren) {
    +                    $subNode = $this->traverseNode($subNode);
    +                }
    +
    +                foreach ($this->visitors as $visitor) {
    +                    if (null !== $return = $visitor->leaveNode($subNode)) {
    +                        if (is_array($return)) {
    +                            throw new \LogicException(
    +                                'leaveNode() may only return an array ' .
    +                                'if the parent structure is an array'
    +                            );
    +                        }
    +                        $subNode = $return;
    +                    }
    +                }
    +            }
    +        }
    +
    +        return $node;
    +    }
    +
    +    protected function traverseArray(array $nodes) {
    +        $doNodes = array();
    +
    +        foreach ($nodes as $i => &$node) {
    +            if (is_array($node)) {
    +                $node = $this->traverseArray($node);
    +            } elseif ($node instanceof Node) {
    +                $traverseChildren = true;
    +                foreach ($this->visitors as $visitor) {
    +                    $return = $visitor->enterNode($node);
    +                    if (self::DONT_TRAVERSE_CHILDREN === $return) {
    +                        $traverseChildren = false;
    +                    } else if (null !== $return) {
    +                        $node = $return;
    +                    }
    +                }
    +
    +                if ($traverseChildren) {
    +                    $node = $this->traverseNode($node);
    +                }
    +
    +                foreach ($this->visitors as $visitor) {
    +                    $return = $visitor->leaveNode($node);
    +
    +                    if (self::REMOVE_NODE === $return) {
    +                        $doNodes[] = array($i, array());
    +                        break;
    +                    } elseif (is_array($return)) {
    +                        $doNodes[] = array($i, $return);
    +                        break;
    +                    } elseif (null !== $return) {
    +                        $node = $return;
    +                    }
    +                }
    +            }
    +        }
    +
    +        if (!empty($doNodes)) {
    +            while (list($i, $replace) = array_pop($doNodes)) {
    +                array_splice($nodes, $i, 1, $replace);
    +            }
    +        }
    +
    +        return $nodes;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php b/core/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php
    new file mode 100644
    index 0000000..0752de2
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php
    @@ -0,0 +1,48 @@
    + [aliasName => originalName]] */
    +    protected $aliases;
    +
    +    public function beforeTraverse(array $nodes) {
    +        $this->resetState();
    +    }
    +
    +    public function enterNode(Node $node) {
    +        if ($node instanceof Stmt\Namespace_) {
    +            $this->resetState($node->name);
    +        } elseif ($node instanceof Stmt\Use_) {
    +            foreach ($node->uses as $use) {
    +                $this->addAlias($use, $node->type, null);
    +            }
    +        } elseif ($node instanceof Stmt\GroupUse) {
    +            foreach ($node->uses as $use) {
    +                $this->addAlias($use, $node->type, $node->prefix);
    +            }
    +        } elseif ($node instanceof Stmt\Class_) {
    +            if (null !== $node->extends) {
    +                $node->extends = $this->resolveClassName($node->extends);
    +            }
    +
    +            foreach ($node->implements as &$interface) {
    +                $interface = $this->resolveClassName($interface);
    +            }
    +
    +            if (null !== $node->name) {
    +                $this->addNamespacedName($node);
    +            }
    +        } elseif ($node instanceof Stmt\Interface_) {
    +            foreach ($node->extends as &$interface) {
    +                $interface = $this->resolveClassName($interface);
    +            }
    +
    +            $this->addNamespacedName($node);
    +        } elseif ($node instanceof Stmt\Trait_) {
    +            $this->addNamespacedName($node);
    +        } elseif ($node instanceof Stmt\Function_) {
    +            $this->addNamespacedName($node);
    +            $this->resolveSignature($node);
    +        } elseif ($node instanceof Stmt\ClassMethod
    +                  || $node instanceof Expr\Closure
    +        ) {
    +            $this->resolveSignature($node);
    +        } elseif ($node instanceof Stmt\Const_) {
    +            foreach ($node->consts as $const) {
    +                $this->addNamespacedName($const);
    +            }
    +        } elseif ($node instanceof Expr\StaticCall
    +                  || $node instanceof Expr\StaticPropertyFetch
    +                  || $node instanceof Expr\ClassConstFetch
    +                  || $node instanceof Expr\New_
    +                  || $node instanceof Expr\Instanceof_
    +        ) {
    +            if ($node->class instanceof Name) {
    +                $node->class = $this->resolveClassName($node->class);
    +            }
    +        } elseif ($node instanceof Stmt\Catch_) {
    +            $node->type = $this->resolveClassName($node->type);
    +        } elseif ($node instanceof Expr\FuncCall) {
    +            if ($node->name instanceof Name) {
    +                $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION);
    +            }
    +        } elseif ($node instanceof Expr\ConstFetch) {
    +            $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_CONSTANT);
    +        } elseif ($node instanceof Stmt\TraitUse) {
    +            foreach ($node->traits as &$trait) {
    +                $trait = $this->resolveClassName($trait);
    +            }
    +
    +            foreach ($node->adaptations as $adaptation) {
    +                if (null !== $adaptation->trait) {
    +                    $adaptation->trait = $this->resolveClassName($adaptation->trait);
    +                }
    +
    +                if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
    +                    foreach ($adaptation->insteadof as &$insteadof) {
    +                        $insteadof = $this->resolveClassName($insteadof);
    +                    }
    +                }
    +            }
    +
    +        }
    +    }
    +
    +    protected function resetState(Name $namespace = null) {
    +        $this->namespace = $namespace;
    +        $this->aliases   = array(
    +            Stmt\Use_::TYPE_NORMAL   => array(),
    +            Stmt\Use_::TYPE_FUNCTION => array(),
    +            Stmt\Use_::TYPE_CONSTANT => array(),
    +        );
    +    }
    +
    +    protected function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
    +        // Add prefix for group uses
    +        $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
    +        // Type is determined either by individual element or whole use declaration
    +        $type |= $use->type;
    +
    +        // Constant names are case sensitive, everything else case insensitive
    +        if ($type === Stmt\Use_::TYPE_CONSTANT) {
    +            $aliasName = $use->alias;
    +        } else {
    +            $aliasName = strtolower($use->alias);
    +        }
    +
    +        if (isset($this->aliases[$type][$aliasName])) {
    +            $typeStringMap = array(
    +                Stmt\Use_::TYPE_NORMAL   => '',
    +                Stmt\Use_::TYPE_FUNCTION => 'function ',
    +                Stmt\Use_::TYPE_CONSTANT => 'const ',
    +            );
    +
    +            throw new Error(
    +                sprintf(
    +                    'Cannot use %s%s as %s because the name is already in use',
    +                    $typeStringMap[$type], $name, $use->alias
    +                ),
    +                $use->getLine()
    +            );
    +        }
    +
    +        $this->aliases[$type][$aliasName] = $name;
    +    }
    +
    +    /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
    +    private function resolveSignature($node) {
    +        foreach ($node->params as $param) {
    +            if ($param->type instanceof Name) {
    +                $param->type = $this->resolveClassName($param->type);
    +            }
    +        }
    +        if ($node->returnType instanceof Name) {
    +            $node->returnType = $this->resolveClassName($node->returnType);
    +        }
    +    }
    +
    +    protected function resolveClassName(Name $name) {
    +        // don't resolve special class names
    +        if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
    +            if (!$name->isUnqualified()) {
    +                throw new Error(
    +                    sprintf("'\\%s' is an invalid class name", $name->toString()),
    +                    $name->getLine()
    +                );
    +            }
    +
    +            return $name;
    +        }
    +
    +        // fully qualified names are already resolved
    +        if ($name->isFullyQualified()) {
    +            return $name;
    +        }
    +
    +        $aliasName = strtolower($name->getFirst());
    +        if (!$name->isRelative() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
    +            // resolve aliases (for non-relative names)
    +            $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
    +            return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
    +        }
    +
    +        if (null !== $this->namespace) {
    +            // if no alias exists prepend current namespace
    +            return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
    +        }
    +
    +        return new FullyQualified($name->parts, $name->getAttributes());
    +    }
    +
    +    protected function resolveOtherName(Name $name, $type) {
    +        // fully qualified names are already resolved
    +        if ($name->isFullyQualified()) {
    +            return $name;
    +        }
    +
    +        // resolve aliases for qualified names
    +        $aliasName = strtolower($name->getFirst());
    +        if ($name->isQualified() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
    +            $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
    +            return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
    +        }
    +
    +        if ($name->isUnqualified()) {
    +            if ($type === Stmt\Use_::TYPE_CONSTANT) {
    +                // constant aliases are case-sensitive, function aliases case-insensitive
    +                $aliasName = $name->getFirst();
    +            }
    +
    +            if (!isset($this->aliases[$type][$aliasName])) {
    +                // unqualified, unaliased names cannot be resolved at compile-time
    +                return $name;
    +            }
    +
    +            // resolve unqualified aliases
    +            return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
    +        }
    +
    +        if (null !== $this->namespace) {
    +            // if no alias exists prepend current namespace
    +            return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
    +        }
    +
    +        return new FullyQualified($name->parts, $name->getAttributes());
    +    }
    +
    +    protected function addNamespacedName(Node $node) {
    +        if (null !== $this->namespace) {
    +            $node->namespacedName = Name::concat($this->namespace, $node->name);
    +        } else {
    +            $node->namespacedName = new Name($node->name);
    +        }
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php b/core/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php
    new file mode 100644
    index 0000000..3e1743a
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php
    @@ -0,0 +1,14 @@
    +parsers = $parsers;
    +        $this->errors = [];
    +    }
    +
    +    public function parse($code) {
    +        list($firstStmts, $firstErrors, $firstError) = $this->tryParse($this->parsers[0], $code);
    +        if ($firstErrors === []) {
    +            $this->errors = [];
    +            return $firstStmts;
    +        }
    +
    +        for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) {
    +            list($stmts, $errors) = $this->tryParse($this->parsers[$i], $code);
    +            if ($errors === []) {
    +                $this->errors = [];
    +                return $stmts;
    +            }
    +        }
    +
    +        $this->errors = $firstErrors;
    +        if ($firstError) {
    +            throw $firstError;
    +        }
    +        return $firstStmts;
    +    }
    +
    +    public function getErrors() {
    +        return $this->errors;
    +    }
    +
    +    private function tryParse(Parser $parser, $code) {
    +        $stmts = null;
    +        $error = null;
    +        try {
    +            $stmts = $parser->parse($code);
    +        } catch (Error $error) {}
    +        $errors = $parser->getErrors();
    +        return [$stmts, $errors, $error];
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php b/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php
    new file mode 100644
    index 0000000..fe40052
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php
    @@ -0,0 +1,3126 @@
    +'",
    +        "T_IS_GREATER_OR_EQUAL",
    +        "T_SL",
    +        "T_SR",
    +        "'+'",
    +        "'-'",
    +        "'.'",
    +        "'*'",
    +        "'/'",
    +        "'%'",
    +        "'!'",
    +        "T_INSTANCEOF",
    +        "'~'",
    +        "T_INC",
    +        "T_DEC",
    +        "T_INT_CAST",
    +        "T_DOUBLE_CAST",
    +        "T_STRING_CAST",
    +        "T_ARRAY_CAST",
    +        "T_OBJECT_CAST",
    +        "T_BOOL_CAST",
    +        "T_UNSET_CAST",
    +        "'@'",
    +        "T_POW",
    +        "'['",
    +        "T_NEW",
    +        "T_CLONE",
    +        "T_EXIT",
    +        "T_IF",
    +        "T_ELSEIF",
    +        "T_ELSE",
    +        "T_ENDIF",
    +        "T_LNUMBER",
    +        "T_DNUMBER",
    +        "T_STRING",
    +        "T_STRING_VARNAME",
    +        "T_VARIABLE",
    +        "T_NUM_STRING",
    +        "T_INLINE_HTML",
    +        "T_ENCAPSED_AND_WHITESPACE",
    +        "T_CONSTANT_ENCAPSED_STRING",
    +        "T_ECHO",
    +        "T_DO",
    +        "T_WHILE",
    +        "T_ENDWHILE",
    +        "T_FOR",
    +        "T_ENDFOR",
    +        "T_FOREACH",
    +        "T_ENDFOREACH",
    +        "T_DECLARE",
    +        "T_ENDDECLARE",
    +        "T_AS",
    +        "T_SWITCH",
    +        "T_ENDSWITCH",
    +        "T_CASE",
    +        "T_DEFAULT",
    +        "T_BREAK",
    +        "T_CONTINUE",
    +        "T_GOTO",
    +        "T_FUNCTION",
    +        "T_CONST",
    +        "T_RETURN",
    +        "T_TRY",
    +        "T_CATCH",
    +        "T_FINALLY",
    +        "T_THROW",
    +        "T_USE",
    +        "T_INSTEADOF",
    +        "T_GLOBAL",
    +        "T_STATIC",
    +        "T_ABSTRACT",
    +        "T_FINAL",
    +        "T_PRIVATE",
    +        "T_PROTECTED",
    +        "T_PUBLIC",
    +        "T_VAR",
    +        "T_UNSET",
    +        "T_ISSET",
    +        "T_EMPTY",
    +        "T_HALT_COMPILER",
    +        "T_CLASS",
    +        "T_TRAIT",
    +        "T_INTERFACE",
    +        "T_EXTENDS",
    +        "T_IMPLEMENTS",
    +        "T_OBJECT_OPERATOR",
    +        "T_LIST",
    +        "T_ARRAY",
    +        "T_CALLABLE",
    +        "T_CLASS_C",
    +        "T_TRAIT_C",
    +        "T_METHOD_C",
    +        "T_FUNC_C",
    +        "T_LINE",
    +        "T_FILE",
    +        "T_START_HEREDOC",
    +        "T_END_HEREDOC",
    +        "T_DOLLAR_OPEN_CURLY_BRACES",
    +        "T_CURLY_OPEN",
    +        "T_PAAMAYIM_NEKUDOTAYIM",
    +        "T_NAMESPACE",
    +        "T_NS_C",
    +        "T_DIR",
    +        "T_NS_SEPARATOR",
    +        "T_ELLIPSIS",
    +        "';'",
    +        "'{'",
    +        "'}'",
    +        "'('",
    +        "')'",
    +        "'$'",
    +        "'`'",
    +        "']'",
    +        "'\"'"
    +    );
    +
    +    protected $tokenToSymbol = array(
    +            0,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,   53,  156,  157,  153,   52,   35,  157,
    +          151,  152,   50,   47,    7,   48,   49,   51,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,   29,  148,
    +           41,   15,   43,   28,   65,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,   67,  157,  155,   34,  157,  154,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  149,   33,  150,   55,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,    1,    2,    3,    4,
    +            5,    6,    8,    9,   10,   11,   12,   13,   14,   16,
    +           17,   18,   19,   20,   21,   22,   23,   24,   25,   26,
    +           27,   30,   31,   32,   36,   37,   38,   39,   40,   42,
    +           44,   45,   46,   54,   56,   57,   58,   59,   60,   61,
    +           62,   63,   64,   66,   68,   69,   70,   71,   72,   73,
    +           74,   75,   76,   77,   78,   79,   80,   81,  157,  157,
    +           82,   83,   84,   85,   86,   87,   88,   89,   90,   91,
    +           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
    +          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
    +          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
    +          122,  123,  124,  125,  126,  127,  128,  129,  130,  131,
    +          132,  133,  134,  135,  136,  137,  157,  157,  157,  157,
    +          157,  157,  138,  139,  140,  141,  142,  143,  144,  145,
    +          146,  147
    +    );
    +
    +    protected $action = array(
    +          672,  673,  674,  675,  676,-32766,  677,  678,  679,  715,
    +          716,  216,  217,  218,  219,  220,  221,  222,  223,  224,
    +          282,  225,  226,  227,  228,  229,  230,  231,  232,  233,
    +          234,  235,  236,-32766,-32766,-32766,-32766,-32766,-32766,-32766,
    +        -32766,-32767,-32767,-32767,-32767,  356,  237,  238,-32766,-32766,
    +        -32766,-32766,  680,-32766,    0,-32766,-32766,-32766,-32766,-32766,
    +        -32766,-32767,-32767,-32767,-32767,-32767,  681,  682,  683,  684,
    +          685,  686,  687, 1178,  204,  747,-32766,-32766,-32766,-32766,
    +        -32766,   23,  688,  689,  690,  691,  692,  693,  694,  695,
    +          696,  697,  698,  718,  719,  720,  721,  722,  710,  711,
    +          712,  713,  714,  699,  700,  701,  702,  703,  704,  705,
    +          741,  742,  743,  744,  745,  746,  706,  707,  708,  709,
    +          739,  730,  728,  729,  725,  726,   46,  717,  723,  724,
    +          731,  732,  734,  733,  735,  736,   52,   53,  420,   54,
    +           55,  727,  738,  737,  447,   56,   57,  332,   58,-32766,
    +        -32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766,    7,-32767,
    +        -32767,-32767,-32767,   50,  329, 1185,  518,  945,  946,  947,
    +          944,  943,  942,  937, 1211,  306, 1213, 1212,  763,  764,
    +          821,   59,   60,-32766,-32766,-32766,  808,   61, 1172,   62,
    +          291,  292,   63,   64,   65,   66,   67,   68,   69,   70,
    +          441,   24,  299,   71,  413,-32766,-32766,-32766,  116, 1087,
    +         1088,  749,  633, 1178,  213,  214,  215,  464,-32766,-32766,
    +        -32766,  822,  407, 1099,  321,-32766,  900,-32766,-32766,-32766,
    +        -32766,-32766,-32766, 1036,  200, -269,  428,   27,-32766,  419,
    +        -32766,-32766,-32766,-32766,-32766,  120,  491,  945,  946,  947,
    +          944,  943,  942,  297,  473,  474,  283,  623,  125,-32766,
    +          893,  894,  339,  477,  478,-32766, 1093, 1094, 1095, 1096,
    +         1090, 1091,  307,  492,-32766,    8,  425,  492, 1097, 1092,
    +          425,  121, -220,  869,  460,   39,  412,  332,  318,   18,
    +          319,  421, -122, -122, -122,   -4,  822,  463,   99,  100,
    +          101,  811,  301, 1036,   38,   19,  422, -122,  465, -122,
    +          466, -122,  467, -122,  102,  423, -122, -122, -122,   28,
    +           29,  468,  424,  624,   30,  469,  425,  812,   72,  348,
    +          923,  349,  350,  470,  471,-32766,-32766,-32766,  298,  472,
    +         1036,  809,  793,  840,  475,  476,-32767,-32767,-32767,-32767,
    +           94,   95,   96,   97,   98,-32766,  440,-32766,-32766,-32766,
    +        -32766, 1137,  213,  214,  215,  295,  421,  239,  824,  638,
    +         -122,  280,  463,  893,  894,  367,  811, 1036, 1203,   38,
    +           19,  422,  200,  465, 1054,  466,  492,  467,  127,  425,
    +          423,  213,  214,  215,   28,   29,  468,  424,  414,   30,
    +          469, 1036,  870,   72,  317,  822,  349,  350,  470,  471,
    +         1036,  200,  214,  215,  472, 1182,  919,  755,  840,  475,
    +          476,  213,  214,  215,  295,  918,   76,   77,   78,   47,
    +          338,  200,  477,  644,  326,  438,   31,  294,  331,  805,
    +          334,  200,  241,  824,  638,   -4,   32,  119,   79,   80,
    +           81,   82,   83,   84,   85,   86,   87,   88,   89,   90,
    +           91,   92,   93,   94,   95,   96,   97,   98,   99,  100,
    +          101, 1208,  301,  242,  822,  421,  801,  124,-32766,-32766,
    +        -32766,  463,  899,  207,  102,  811,  909,  126,   38,   19,
    +          422,  545,  465, 1172,  466,   34,  467,  762,-32766,  423,
    +        -32766,-32766,  647,   28,   29,  468,  822, 1036,   30,  469,
    +         -216,  117,   72,  803,   49,  349,  350,-32766,-32766,-32766,
    +        -32766,-32766,-32766,  472,  123, 1036,  234,  235,  236,  213,
    +          214,  215, 1036,  115,  641, 1138,  124,-32766,  200,-32766,
    +        -32766,-32766,  237,  238,  421,   96,   97,   98,  293,  200,
    +          463,  585,  856,  638,  811,  439, 1036,   38,   19,  422,
    +          284,  465,  215,  466,  749,  467, 1178,  339,  423,  231,
    +          232,  233,   28,   29,  468,  822,  421,   30,  469,  296,
    +          200,   72,  463,  415,  349,  350,  811,-32766,-32766,   38,
    +           19,  422,  472,  465,-32766,  466,  118,  467,  377, 1064,
    +          423,-32766,-32766,  642,   28,   29,  468,  822, 1099,   30,
    +          469,-32766,  434,   72,  129,  640,  349,  350,  576,  205,
    +          492,  824,  638,  425,  472,  206,-32766,-32766,-32766,  244,
    +          492,  237,  238,  425,  243,  653,  449,   20,  429,  301,
    +          332,  454,  591,  130,  357,  421,-32766,  763,  764,  599,
    +          600,  463,  646,  824,  638,  811,  922,  666,   38,   19,
    +          422,  308,  465,  650,  466,  128,  467,  756,  643,  423,
    +          820,  934,  656,   28,   29,  468,  822,  421,   30,  469,
    +          833,  102,   72,  463,   44,  349,  350,  811,   51,   48,
    +           38,   19,  422,  472,  465,   43,  466,   41,  467,  299,
    +           45,  423,   42,  605,  513,   28,   29,  468,-32766,  632,
    +           30,  469,  579,  432,   72,  749,  750,  349,  350,  534,
    +          512,  435,  824,  638, 1206,  472,  433,   33,  103,  104,
    +          105,  106,  107,  108,  109,  110,  111,  112,  113,  114,
    +          533,  776,  517,  524,  437,  622,  421, 1057,  612,  516,
    +          602,  619,  463,  279,  824,  638,  811,  458,  595,   38,
    +           19,  422,  596,  465,  330,  466,  240,  467,  975,  977,
    +          423,  609,  582,  -80,   28,   29,  468,  537,   12,   30,
    +          469,  477,  327,   72,  208,  209,  349,  350,    9, 1098,
    +          210,  303,  211,  333,  472,  842,  841,  384,  757,  370,
    +            0,  328,    0,    0,  202,  322,    0,    0, -497,  208,
    +          209,    0, 1087, 1088,  320,  210,-32766,  211, -498,    0,
    +         1089, 1144,    0,  824,  638,    0,    0,    4,  835,  202,
    +         -398, -407,    0,    3,   11, -406,   75, 1087, 1088,    0,
    +         -497,-32766,  409,  393,  408, 1089,  385,  434,  526,  372,
    +          302, 1143,  864,  863,  796,  857,  813,  798,  819,  807,
    +            0,  761,  661,  660,   37,   36,  926,  565,  810, 1093,
    +         1094, 1095, 1096, 1090, 1091,  383,  854,  852,  929,  804,
    +          759, 1097, 1092,  806,  818,  290,  760,  928,  212,  802,
    +        -32766,  930,  565,  927, 1093, 1094, 1095, 1096, 1090, 1091,
    +          383,  872, 1209,  639,  649,  651, 1097, 1092,  652,  654,
    +          655, 1034,  658,  212,  663,-32766,  664,  665,  122,  324,
    +          325,  405,  406,    0,  758, 1210,  839,  838,  766,  453,
    +         1207, 1179, 1177, 1163, 1175, 1078,  911, 1183, 1173,  829,
    +          836, 1038, 1039,  827,  935,  794,  765,  837,  662, 1050,
    +          861,  768,  767,  862,    0,  304,  289,  281,   25,   26,
    +          203,  305,  335,   74,   73,  411,  417,   35,   40,-32766,
    +           22,    0, 1015,  569, -217, 1016, 1103,  901, 1080, 1044,
    +         1040, 1041,  629,  559,  461,  457,  455,  450,  378,   16,
    +           15,   14, -216,    0,    0, -416,    0, 1045,  603, 1157,
    +         1104, 1205, 1077, 1174, 1158, 1162, 1176, 1063, 1048, 1049,
    +         1046, 1047
    +    );
    +
    +    protected $actionCheck = array(
    +            2,    3,    4,    5,    6,    8,    8,    9,   10,   11,
    +           12,   31,   32,   33,   34,   35,   36,   37,   38,   39,
    +            7,   41,   42,   43,   44,   45,   46,   47,   48,   49,
    +           50,   51,   52,    8,    9,   10,   31,   32,   33,   34,
    +           35,   36,   37,   38,   39,    7,   66,   67,   31,   32,
    +           33,   34,   54,   28,    0,   30,   31,   32,   33,   34,
    +           35,   36,   37,   38,   39,   40,   68,   69,   70,   71,
    +           72,   73,   74,   79,    7,   77,   31,   32,   33,   34,
    +           35,    7,   84,   85,   86,   87,   88,   89,   90,   91,
    +           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
    +          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
    +          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
    +          122,  123,  124,  125,  126,  127,   67,  129,  130,  131,
    +          132,  133,  134,  135,  136,  137,    2,    3,    4,    5,
    +            6,  143,  144,  145,    7,   11,   12,  153,   14,   31,
    +           32,   33,   34,   35,   36,   37,   38,   39,  103,   41,
    +           42,   43,   44,   67,  109,  152,   82,  112,  113,  114,
    +          115,  116,  117,  118,   77,    7,   79,   80,  102,  103,
    +            1,   47,   48,    8,    9,   10,  148,   53,   79,   55,
    +           56,   57,   58,   59,   60,   61,   62,   63,   64,   65,
    +            7,   67,   68,   69,   70,    8,    9,   10,  149,   75,
    +           76,   77,   77,   79,    8,    9,   10,   83,    8,    9,
    +           10,    1,  146,  139,  128,   28,  152,   30,   31,   32,
    +           33,   34,   35,   12,   28,   79,  102,    7,   28,    7,
    +           30,   31,   32,   33,   34,  149,  112,  112,  113,  114,
    +          115,  116,  117,    7,  120,  121,   35,   77,  149,  103,
    +          130,  131,  153,  129,  130,  109,  132,  133,  134,  135,
    +          136,  137,  138,  143,  118,    7,  146,  143,  144,  145,
    +          146,    7,  152,   29,    7,  151,    7,  153,  154,  152,
    +          156,   71,   72,   73,   74,    0,    1,   77,   50,   51,
    +           52,   81,   54,   12,   84,   85,   86,   87,   88,   89,
    +           90,   91,   92,   93,   66,   95,   96,   97,   98,   99,
    +          100,  101,  102,  143,  104,  105,  146,  148,  108,    7,
    +          150,  111,  112,  113,  114,    8,    9,   10,   35,  119,
    +           12,  148,  122,  123,  124,  125,   41,   42,   43,   44,
    +           45,   46,   47,   48,   49,   28,    7,   30,   31,   32,
    +           33,  155,    8,    9,   10,   35,   71,   13,  148,  149,
    +          150,   13,   77,  130,  131,   79,   81,   12,   82,   84,
    +           85,   86,   28,   88,  152,   90,  143,   92,   67,  146,
    +           95,    8,    9,   10,   99,  100,  101,  102,  103,  104,
    +          105,   12,  148,  108,  109,    1,  111,  112,  113,  114,
    +           12,   28,    9,   10,  119,   77,  148,  122,  123,  124,
    +          125,    8,    9,   10,   35,  148,    8,    9,   10,   67,
    +           67,   28,  129,   29,    7,   29,  140,  141,  143,  148,
    +            7,   28,   29,  148,  149,  150,   28,   13,   30,   31,
    +           32,   33,   34,   35,   36,   37,   38,   39,   40,   41,
    +           42,   43,   44,   45,   46,   47,   48,   49,   50,   51,
    +           52,  150,   54,   15,    1,   71,  148,  147,    8,    9,
    +           10,   77,  152,   15,   66,   81,   79,  149,   84,   85,
    +           86,  128,   88,   79,   90,   13,   92,  148,   28,   95,
    +           30,   31,   29,   99,  100,  101,    1,   12,  104,  105,
    +          152,  149,  108,  148,   67,  111,  112,    8,    9,   10,
    +           31,   32,   33,  119,   29,   12,   50,   51,   52,    8,
    +            9,   10,   12,   15,   29,  152,  147,   28,   28,   30,
    +           31,   32,   66,   67,   71,   47,   48,   49,   35,   28,
    +           77,   82,  148,  149,   81,  149,   12,   84,   85,   86,
    +          153,   88,   10,   90,   77,   92,   79,  153,   95,   47,
    +           48,   49,   99,  100,  101,    1,   71,  104,  105,   35,
    +           28,  108,   77,  123,  111,  112,   81,    8,    9,   84,
    +           85,   86,  119,   88,   31,   90,  149,   92,   78,  112,
    +           95,   31,   32,   29,   99,  100,  101,    1,  139,  104,
    +          105,  151,  146,  108,  149,  149,  111,  112,  153,   15,
    +          143,  148,  149,  146,  119,   15,    8,    9,   10,   15,
    +          143,   66,   67,  146,   15,   29,   72,   73,  151,   54,
    +          153,   72,   73,   97,   98,   71,   28,  102,  103,  106,
    +          107,   77,   29,  148,  149,   81,  148,  149,   84,   85,
    +           86,   29,   88,   29,   90,   29,   92,  148,  149,   95,
    +           29,  148,  149,   99,  100,  101,    1,   71,  104,  105,
    +           35,   66,  108,   77,   67,  111,  112,   81,   67,   67,
    +           84,   85,   86,  119,   88,   67,   90,   67,   92,   68,
    +           67,   95,   67,   74,   77,   99,  100,  101,   82,   89,
    +          104,  105,   87,  102,  108,   77,   77,  111,  112,   77,
    +           77,   77,  148,  149,   77,  119,   77,   15,   16,   17,
    +           18,   19,   20,   21,   22,   23,   24,   25,   26,   27,
    +           77,   77,   77,   82,   86,   79,   71,   79,   79,   79,
    +           79,   91,   77,   94,  148,  149,   81,  102,   96,   84,
    +           85,   86,  109,   88,  110,   90,   29,   92,   56,   57,
    +           95,   93,   96,   94,   99,  100,  101,   94,   94,  104,
    +          105,  129,  126,  108,   47,   48,  111,  112,  142,  139,
    +           53,  151,   55,  126,  119,  123,  123,  146,  150,  142,
    +           -1,  127,   -1,   -1,   67,  128,   -1,   -1,  128,   47,
    +           48,   -1,   75,   76,  128,   53,   79,   55,  128,   -1,
    +           83,  139,   -1,  148,  149,   -1,   -1,  142,  147,   67,
    +          142,  142,   -1,  142,  142,  142,  149,   75,   76,   -1,
    +          128,   79,  146,  146,  146,   83,  146,  146,  146,  146,
    +          151,  156,  148,  148,  148,  148,  148,  148,  148,  148,
    +           -1,  148,  148,  148,  148,  148,  148,  130,  148,  132,
    +          133,  134,  135,  136,  137,  138,  148,  148,  148,  148,
    +          148,  144,  145,  148,  148,  151,  148,  148,  151,  148,
    +          153,  148,  130,  148,  132,  133,  134,  135,  136,  137,
    +          138,  148,  150,  149,  149,  149,  144,  145,  149,  149,
    +          149,  154,  149,  151,  149,  153,  149,  149,  149,  149,
    +          149,  149,  149,   -1,  150,  150,  150,  150,  150,  150,
    +          150,  150,  150,  150,  150,  150,  150,  150,  150,  150,
    +          150,  150,  150,  150,  150,  150,  150,  150,  150,  150,
    +          150,  150,  150,  150,   -1,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,   -1,  152,  152,  152,  152,  152,  152,  152,  152,
    +          152,  152,  152,  152,  152,  152,  152,  152,  152,  152,
    +          152,  152,  152,   -1,   -1,  154,   -1,  155,  155,  155,
    +          155,  155,  155,  155,  155,  155,  155,  155,  155,  155,
    +          155,  155
    +    );
    +
    +    protected $actionBase = array(
    +            0,  220,  295,  109,  109,  180,  739,   -2,   -2,   -2,
    +           -2,   -2,  135,  574,  473,  606,  473,  505,  404,  675,
    +          675,  675,  330,  389,  513,  513,  826,  513,  328,  365,
    +          291,  520,  495,  221,  544,  398,  398,  398,  398,  134,
    +          134,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
    +          398,  254,  179,  434,  482,  741,  731,  735,  736,  828,
    +          659,  823,  780,  781,  636,  782,  783,  784,  785,  786,
    +          779,  787,  843,  788,  418,  418,  418,  418,  418,  418,
    +          418,  418,  418,  418,  418,   -3,  354,  383,  413,  206,
    +          579,  521,  521,  521,  521,  521,  521,  521,  175,  175,
    +          175,  175,  175,  175,  175,  175,  175,  175,  175,  175,
    +          175,  175,  175,  175,  175,  403,  618,  618,  618,  552,
    +          737,  510,  762,  762,  762,  762,  762,  762,  762,  762,
    +          762,  762,  762,  762,  762,  762,  762,  762,  762,  762,
    +          762,  762,  762,  762,  762,  762,  762,  762,  762,  762,
    +          762,  762,  762,  762,  762,  762,  762,  762,  762,  762,
    +          762,  762,  762,  762,  762,  470,  -20,  -20,  509,  563,
    +          327,  570,  210,  489,  197,   25,   25,   25,   25,   25,
    +           17,   45,    5,    5,    5,    5,  712,  305,  305,  305,
    +          305,  118,  118,  118,  118,  776,  777,  797,  799,  303,
    +          303,  652,  652,  631,  769,  498,  498,  522,  522,  487,
    +          487,  487,  487,  487,  487,  487,  487,  487,  487,  460,
    +          156,  818,  130,  130,  130,  130,  243,   84,  243,  682,
    +          695,  248,  248,  248,  476,  476,  476,   76,  661,  296,
    +          338,  338,  338,  296,  545,  545,  545,  477,  477,  477,
    +          477,  466,  687,  477,  477,  477,  362,  626,   97,  465,
    +          676,  800,  662,  803,  508,  689,   96,  698,  696,  407,
    +          611,  564,  569,  543,  688,  406,  407,  254,  523,  447,
    +          585,  720,  642,  349,  732,   38,  193,  363,  519,   59,
    +          414,  137,  770,  738,  821,  820,   13,  321,  690,  585,
    +          585,  585,   74,  469,  771,  772,   59,  358,  565,  565,
    +          565,  565,  802,  773,  565,  565,  565,  565,  801,  796,
    +          268,  277,  778,  232,  718,  638,  638,  638,  638,  638,
    +          638,  645,  638,  808,  627,  819,  819,  663,  671,  645,
    +          817,  817,  817,  817,  645,  638,  819,  819,  645,  631,
    +          819,  230,  645,  656,  638,  667,  667,  817,  715,  714,
    +          627,  670,  674,  819,  819,  819,  674,  663,  645,  817,
    +          653,  681,   67,  819,  817,  632,  632,  653,  645,  632,
    +          671,  632,   54,  641,  630,  816,  813,  815,  643,  754,
    +          673,  672,  805,  734,  812,  665,  649,  806,  807,  702,
    +          713,  711,  644,  518,  635,  628,  617,  633,  691,  622,
    +          686,  611,  701,  615,  615,  615,  680,  685,  680,  615,
    +          615,  615,  615,  615,  615,  615,  615,  842,  657,  693,
    +          677,  658,  710,  604,  703,  683,  610,  763,  650,  702,
    +          702,  795,  829,  836,  841,  757,  639,  699,  831,  680,
    +          856,  717,  274,  468,  640,  798,  651,  664,  700,  680,
    +          804,  680,  765,  680,  827,  647,  775,  702,  774,  615,
    +          825,  855,  854,  853,  852,  851,  850,  849,  848,  621,
    +          847,  709,  625,  835,  168,  809,  688,  646,  697,  708,
    +          433,  846,  648,  680,  680,  767,  687,  680,  768,  753,
    +          716,  839,  705,  834,  845,  650,  833,  680,  655,  844,
    +          433,  623,  629,  822,  678,  704,  814,  669,  824,  811,
    +          755,  458,  619,  752,  634,  706,  838,  837,  840,  707,
    +          756,  759,  614,  660,  668,  666,  789,  760,  810,  728,
    +          790,  791,  830,  679,  701,  692,  654,  684,  620,  761,
    +          792,  832,  729,  730,  743,  793,  745,  794,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,  134,  134,
    +           -2,   -2,   -2,   -2,    0,    0,    0,    0,    0,   -2,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,    0,    0,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  418,  418,
    +          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
    +          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
    +          418,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,  418,  -20,  -20,  -20,  -20,  418,  -20,  -20,
    +          -20,  -20,  -20,  -20,  -20,  418,  418,  418,  418,  418,
    +          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
    +          418,  418,  -20,  418,  418,  418,  -20,  487,  -20,  487,
    +          487,  487,  487,  487,  487,  487,  487,  487,  487,  487,
    +          487,  487,  487,  487,  487,  487,  487,  487,  487,  487,
    +          487,  487,  487,  487,  487,  487,  487,  487,  487,  487,
    +          487,  487,  487,  487,  487,  487,  487,  487,  487,  487,
    +          487,  487,  418,    0,    0,  418,  -20,  418,  -20,  418,
    +          -20,  418,  418,  418,  418,  418,  418,  -20,  -20,  -20,
    +          -20,  -20,  -20,    0,  248,  248,  248,  248,  -20,  -20,
    +          -20,  -20,   55,   55,   55,   55,  487,  487,  487,  487,
    +          487,  487,  248,  248,  476,  476,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,  487,   55,  487,  638,
    +          638,  638,  638,  638,  296,  638,  296,  296,    0,    0,
    +            0,    0,    0,    0,  638,  296,    0,   -6,   -6,   -6,
    +            0,  638,  638,  638,  638,  638,  638,  638,  638,   -6,
    +          638,  638,  638,  819,  296,    0,   -6,  546,  546,  546,
    +          546,  433,   59,    0,  638,  638,    0,  670,    0,    0,
    +            0,  819,    0,    0,    0,    0,    0,  615,  274,  699,
    +            0,  322,    0,    0,    0,    0,    0,    0,    0,  639,
    +          322,  246,  246,    0,    0,  621,  615,  615,  615,    0,
    +            0,  639,  639,    0,    0,    0,    0,    0,    0,  427,
    +          639,    0,    0,    0,    0,  427,  279,    0,    0,  279,
    +            0,  433
    +    );
    +
    +    protected $actionDefault = array(
    +            3,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,  524,  524,32767,  481,32767,32767,
    +        32767,32767,32767,32767,32767,  287,  287,  287,32767,32767,
    +        32767,  513,  513,  513,  513,  513,  513,  513,  513,  513,
    +          513,  513,32767,32767,32767,32767,32767,  369,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,  375,  529,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,  350,  351,  353,  354,  286,  514,
    +          237,  376,  528,  285,  239,  314,  485,32767,32767,32767,
    +          316,  116,  248,  193,  484,  119,  284,  224,  368,  370,
    +          315,  291,  296,  297,  298,  299,  300,  301,  302,  303,
    +          304,  305,  306,  307,  290,  441,  347,  346,  345,  443,
    +        32767,  442,  478,  478,  481,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,  312,  469,  468,  313,  439,
    +          317,  440,  319,  444,  318,  335,  336,  333,  334,  337,
    +          446,  445,  462,  463,  460,  461,  289,  338,  339,  340,
    +          341,  464,  465,  466,  467,  271,  271,  271,  271,32767,
    +        32767,  523,  523,32767,32767,  326,  327,  453,  454,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +          272,32767,  228,  228,  228,  228,  228,32767,32767,32767,
    +        32767,  321,  322,  320,  448,  449,  447,32767,  415,32767,
    +        32767,32767,32767,  417,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,  486,32767,32767,32767,
    +        32767,32767,32767,32767,32767,  499,  404,32767,32767,32767,
    +          397,  212,  214,  161,  472,32767,32767,32767,32767,  504,
    +          331,32767,32767,32767,32767,32767,32767,  537,32767,  499,
    +        32767,32767,32767,32767,32767,32767,32767,32767,  344,  323,
    +          324,  325,32767,32767,32767,32767,  503,  497,  456,  457,
    +          458,  459,32767,32767,  450,  451,  452,  455,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,  165,32767,  412,32767,  418,  418,32767,32767,  165,
    +        32767,32767,32767,32767,  165,32767,  502,  501,  165,32767,
    +          398,  480,  165,  178,32767,  176,  176,32767,  198,  198,
    +        32767,32767,  180,  473,  492,32767,  180,32767,  165,32767,
    +          386,  167,  480,32767,32767,  230,  230,  386,  165,  230,
    +        32767,  230,32767,   82,  422,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,  399,
    +        32767,32767,32767,32767,  365,  366,  475,  488,32767,  489,
    +        32767,  397,32767,  329,  330,  332,  309,32767,  311,  355,
    +          356,  357,  358,  359,  360,  361,  363,32767,32767,  402,
    +          405,32767,32767,32767,   84,  108,  247,32767,  536,   84,
    +          400,32767,32767,  294,  536,32767,32767,32767,32767,  531,
    +        32767,32767,  288,32767,32767,32767,   84,32767,   84,  243,
    +        32767,  163,32767,  521,32767,32767,  497,  401,32767,  328,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,  498,
    +        32767,32767,32767,32767,  219,32767,  435,32767,   84,32767,
    +          179,32767,32767,  292,  238,32767,32767,  530,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,  164,32767,32767,
    +          181,32767,32767,  497,32767,32767,32767,32767,32767,32767,
    +        32767,32767,  283,32767,32767,32767,32767,32767,  497,32767,
    +        32767,32767,  223,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,   82,   60,32767,  265,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,  121,  121,
    +            3,  121,  121,    3,  121,  121,  121,  121,  121,  121,
    +          121,  121,  121,  121,  121,  121,  121,  206,  250,  209,
    +          198,  198,  158,  250,  250,  250,  257
    +    );
    +
    +    protected $goto = array(
    +          160,  160,  134,  134,  139,  134,  135,  136,  137,  142,
    +          144,  181,  162,  158,  158,  158,  158,  139,  139,  159,
    +          159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
    +          154,  155,  156,  157,  178,  133,  179,  493,  494,  360,
    +          495,  499,  500,  501,  502,  503,  504,  505,  506,  962,
    +          138,  140,  141,  143,  165,  170,  180,  196,  245,  248,
    +          250,  252,  254,  255,  256,  257,  258,  259,  267,  268,
    +          269,  270,  285,  286,  311,  312,  313,  379,  380,  381,
    +          549,  182,  183,  184,  185,  186,  187,  188,  189,  190,
    +          191,  192,  193,  194,  145,  146,  147,  161,  148,  163,
    +          149,  197,  164,  150,  151,  152,  198,  153,  131,  625,
    +          567,  753,  567,  567,  567,  567,  567,  567,  567,  567,
    +          567,  567,  567,  567,  567,  567,  567,  567,  567,  567,
    +          567,  567,  567,  567,  567,  567,  567,  567,  567,  567,
    +          567,  567,  567,  567,  567,  567,  567,  567,  567,  567,
    +          567,  567,  567,  567,  567, 1100,    6, 1100, 1100, 1100,
    +         1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
    +         1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
    +         1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
    +         1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
    +          885,  885, 1189, 1189,  583,  586,  631,  168,  341,  509,
    +          754,  509,  171,  172,  173,  388,  389,  390,  391,  167,
    +          195,  199,  201,  249,  251,  253,  260,  261,  262,  263,
    +          264,  265,  271,  272,  273,  274,  287,  288,  314,  315,
    +          316,  394,  395,  396,  397,  169,  174,  246,  247,  175,
    +          176,  177,  497,  497,  497,  497,  497,  497,  523, 1200,
    +         1200,  784,  497,  497,  497,  497,  497,  497,  497,  497,
    +          497,  497,  508, 1200,  508,  387,  608,  543,  543,  573,
    +          539,  580,  606,  790,  752,  541,  541,  496,  498,  529,
    +          546,  574,  577,  587,  593,  871, 1169,  851, 1169,  657,
    +          634,  511,  880,  875,  566,  815,  566,  566,  566,  566,
    +          566,  566,  566,  566,  566,  566,  566,  566,  566,  566,
    +          566,  566,  566,  566,  566,  566,  566,  566,  566,  566,
    +          566,  566,  566,  566,  566,  566,  566,  566,  566,  566,
    +          566,  566,  566,  566,  566,  566,  566,  566,  566,  551,
    +          552,  553,  554,  555,  556,  557,  558,  560,  589,  514,
    +          855,  550,  590,  344,  404,  522,  519,  519,  519,  443,
    +          445,  933,  636,  519, 1161, 1101,  618,  931,  522,  522,
    +          276,  277,  278,  430,  430,  430,  430,  430,  430,  538,
    +          519,  903, 1058,  430,  430,  430,  430,  430,  430,  430,
    +          430,  430,  430, 1065,  544, 1065,  892,  892,  892,  892,
    +          892,  535,  892,  659,  562,  777,  594,  868,  882,  613,
    +          867,  616,  878,  620,  621,  628,  630,  635,  637,  342,
    +          343,  849,  849,  849,  849,  323,  310,  844,  850,  615,
    +          548, 1199, 1199,  572,  941,  777,  777,  519,  519,  536,
    +          568,  519,  519, 1081,  519, 1199,  510, 1168,  510, 1168,
    +         1019,   17,   13,  355, 1061, 1062, 1193,  520, 1058, 1202,
    +          611, 1076, 1075,  617,  361,  358,  547,  561, 1184, 1184,
    +         1184, 1059, 1160, 1059,  598,  607, 1186, 1149,  362,   21,
    +         1167, 1060,  527,  375,  604, 1009,  540,  369,  369,  369,
    +          898,  889,  960,  770,  770,  778,  778,  778,  780,  369,
    +          769,  398,  451,  347,  773,  368,  386,  373,  907,  771,
    +          645,  402,   10, 1051, 1056,  446,  781,  578,  912,  859,
    +         1146,  459,  949,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,  528
    +    );
    +
    +    protected $gotoCheck = array(
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   53,
    +          112,   11,  112,  112,  112,  112,  112,  112,  112,  112,
    +          112,  112,  112,  112,  112,  112,  112,  112,  112,  112,
    +          112,  112,  112,  112,  112,  112,  112,  112,  112,  112,
    +          112,  112,  112,  112,  112,  112,  112,  112,  112,  112,
    +          112,  112,  112,  112,  112,  119,   90,  119,  119,  119,
    +          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
    +          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
    +          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
    +          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
    +           70,   70,   70,   70,   56,   56,   56,   23,   65,  112,
    +           12,  112,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,  109,  109,  109,  109,  109,  109,   93,  134,
    +          134,   25,  109,  109,  109,  109,  109,  109,  109,  109,
    +          109,  109,  109,  134,  109,   47,   47,   47,   47,   47,
    +           47,   36,   36,   10,   10,   47,   47,   47,   47,   47,
    +           47,   47,   47,   47,   47,   10,  110,   10,  110,   10,
    +            5,   10,   10,   10,   53,   46,   53,   53,   53,   53,
    +           53,   53,   53,   53,   53,   53,   53,   53,   53,   53,
    +           53,   53,   53,   53,   53,   53,   53,   53,   53,   53,
    +           53,   53,   53,   53,   53,   53,   53,   53,   53,   53,
    +           53,   53,   53,   53,   53,   53,   53,   53,   53,  102,
    +          102,  102,  102,  102,  102,  102,  102,  102,  102,    8,
    +           29,   40,   63,   63,   63,   40,    8,    8,    8,    7,
    +            7,    7,    7,    8,   75,    7,    7,    7,   40,   40,
    +           61,   61,   61,   53,   53,   53,   53,   53,   53,    8,
    +            8,   77,   75,   53,   53,   53,   53,   53,   53,   53,
    +           53,   53,   53,   53,  101,   53,   53,   53,   53,   53,
    +           53,   28,   53,   28,   28,   19,   28,   28,   28,   28,
    +           28,   28,   28,   28,   28,   28,   28,   28,   28,   65,
    +           65,   53,   53,   53,   53,  118,  118,   53,   53,   53,
    +            2,  133,  133,    2,   90,   19,   19,    8,    8,    8,
    +            8,    8,    8,   30,    8,  133,  115,  111,  115,  111,
    +           30,   30,   30,   30,   75,   75,  132,    8,   75,  133,
    +           57,  117,  117,   57,   43,   57,    8,   30,  111,  111,
    +          111,   75,   75,   75,  120,   45,  130,  124,   54,   30,
    +          111,   75,   54,   44,   30,   94,   54,  116,  116,  116,
    +           74,   72,   93,   19,   19,   19,   19,   19,   19,  116,
    +           19,   18,   54,   14,   21,    9,  116,   13,   78,   20,
    +           67,   17,   54,  105,  107,   59,   22,   60,   79,   64,
    +          123,  100,   92,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   93
    +    );
    +
    +    protected $gotoBase = array(
    +            0,    0, -200,    0,    0,  288,    0,  366,   42,  184,
    +          282,  109,  208,  170,  196,    0,    0,  115,  186,   98,
    +          171,  188,   86,    7,    0,  253,    0,    0, -228,  342,
    +           40,    0,    0,    0,    0,    0,  245,    0,    0,  -22,
    +          339,    0,    0,  436,  203,  205,  289,   -4,    0,    0,
    +            0,    0,    0,  104,   64,    0,  -99,   14,    0,   89,
    +           81, -283,    0,   34,   82, -231,    0,  163,    0,    0,
    +          -79,    0,  195,    0,  192,   38,    0,  368,  162,   87,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +          144,    0,   72,  219,  194,    0,    0,    0,    0,    0,
    +           74,  379,  307,    0,    0,  107,    0,  105,    0,  -27,
    +           -3,  158,  -90,    0,    0,  157,  177,  150,  117,  -45,
    +          281,    0,    0,   78,  283,    0,    0,    0,    0,    0,
    +          204,    0,  439,  132,  -50,    0
    +    );
    +
    +    protected $gotoDefault = array(
    +        -32768,  462,  668,    2,  669,  740,  748,  601,  479,  515,
    +          853,  791,  792,  364,  410,  480,  363,  399,  392,  779,
    +          772,  774,  782,  166,  400,  785,    1,  787,  521,  823,
    +         1010,  351,  795,  352,  592,  797,  531,  799,  800,  132,
    +          481,  365,  366,  532,  374,  581,  814,  266,  371,  816,
    +          353,  817,  826,  354,  614,  597,  563,  610,  482,  442,
    +          575,  275,  542,  570,  858,  340,  866,  648,  874,  877,
    +          483,  564,  888,  448,  896, 1086,  382,  902,  908,  913,
    +          916,  418,  401,  588,  920,  921,    5,  925,  626,  627,
    +          940,  300,  948,  961,  416, 1029, 1031,  484,  485,  525,
    +          456,  507,  530,  486, 1052,  436,  403, 1055,  487,  488,
    +          426,  427, 1073, 1070,  346, 1154,  345,  444,  309, 1141,
    +          584, 1105,  452, 1192, 1150,  336,  489,  490,  359,  376,
    +         1187,  431, 1194, 1201,  337,  571
    +    );
    +
    +    protected $ruleToNonTerminal = array(
    +            0,    1,    3,    3,    2,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    6,    6,    6,    6,    6,    6,    6,
    +            7,    7,    8,    8,    9,    4,    4,    4,    4,    4,
    +            4,    4,    4,    4,    4,    4,   14,   14,   15,   15,
    +           15,   15,   17,   17,   13,   13,   18,   18,   19,   19,
    +           20,   20,   21,   21,   16,   16,   22,   24,   24,   25,
    +           26,   26,   28,   27,   27,   27,   27,   29,   29,   29,
    +           29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
    +           29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
    +           29,   29,   29,   29,   29,   29,   10,   10,   50,   50,
    +           52,   51,   51,   44,   44,   54,   54,   55,   55,   11,
    +           12,   12,   12,   58,   58,   58,   59,   59,   62,   62,
    +           60,   60,   63,   63,   37,   37,   46,   46,   49,   49,
    +           49,   48,   48,   64,   38,   38,   38,   38,   65,   65,
    +           66,   66,   67,   67,   35,   35,   31,   31,   68,   33,
    +           33,   69,   32,   32,   34,   34,   45,   45,   45,   56,
    +           56,   71,   71,   72,   72,   74,   74,   74,   73,   73,
    +           57,   57,   75,   75,   75,   76,   76,   77,   77,   77,
    +           41,   41,   78,   78,   78,   42,   42,   79,   79,   61,
    +           61,   80,   80,   80,   80,   85,   85,   86,   86,   87,
    +           87,   87,   87,   87,   88,   89,   89,   84,   84,   81,
    +           81,   83,   83,   91,   91,   90,   90,   90,   90,   90,
    +           90,   82,   82,   92,   92,   43,   43,   36,   36,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
    +           39,   39,   39,   30,   30,   40,   40,   97,   97,   98,
    +           98,   98,   98,  104,   93,   93,  100,  100,  106,  106,
    +          107,  108,  108,  108,  108,  108,  108,  112,  112,   53,
    +           53,   53,   94,   94,  113,  113,  109,  109,  114,  114,
    +          114,  114,   95,   95,   95,   99,   99,   99,  105,  105,
    +          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
    +          119,  119,  119,   23,   23,   23,   23,   23,   23,  121,
    +          121,  121,  121,  121,  121,  121,  121,  121,  121,  121,
    +          121,  121,  121,  121,  121,  121,  121,  121,  121,  121,
    +          121,  121,  121,  121,  121,  121,  121,  121,  121,  121,
    +          121,  121,  103,  103,   96,   96,   96,   96,  120,  120,
    +          123,  123,  122,  122,  124,  124,   47,   47,   47,   47,
    +          126,  126,  125,  125,  125,  125,  125,  127,  127,  111,
    +          111,  115,  115,  110,  110,  128,  128,  128,  128,  116,
    +          116,  116,  116,  102,  102,  117,  117,  117,   70,  129,
    +          129,  130,  130,  130,  101,  101,  131,  131,  132,  132,
    +          132,  132,  118,  118,  118,  118,  134,  133,  133,  133,
    +          133,  133,  133,  133,  135,  135,  135
    +    );
    +
    +    protected $ruleToLength = array(
    +            1,    1,    2,    0,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    3,    1,    1,    1,    1,    1,    3,
    +            5,    4,    3,    4,    2,    3,    1,    1,    7,    8,
    +            6,    7,    3,    1,    3,    1,    3,    1,    1,    3,
    +            1,    2,    1,    2,    3,    1,    3,    3,    1,    3,
    +            2,    0,    1,    1,    1,    1,    1,    3,    5,    8,
    +            3,    5,    9,    3,    2,    3,    2,    3,    2,    3,
    +            2,    3,    3,    3,    1,    2,    5,    7,    9,    5,
    +            6,    3,    3,    2,    2,    1,    1,    1,    0,    2,
    +            8,    0,    4,    1,    3,    0,    1,    0,    1,   10,
    +            7,    6,    5,    1,    2,    2,    0,    2,    0,    2,
    +            0,    2,    1,    3,    1,    4,    1,    4,    1,    1,
    +            4,    1,    3,    3,    3,    4,    4,    5,    0,    2,
    +            4,    3,    1,    1,    1,    4,    0,    2,    3,    0,
    +            2,    4,    0,    2,    0,    3,    1,    2,    1,    1,
    +            0,    1,    3,    4,    6,    1,    1,    1,    0,    1,
    +            0,    2,    2,    3,    3,    1,    3,    1,    2,    2,
    +            3,    1,    1,    2,    4,    3,    1,    1,    3,    2,
    +            0,    3,    3,    9,    3,    1,    3,    0,    2,    4,
    +            5,    4,    4,    4,    3,    1,    1,    1,    3,    1,
    +            1,    0,    1,    1,    2,    1,    1,    1,    1,    1,
    +            1,    1,    3,    1,    3,    3,    1,    0,    1,    1,
    +            3,    3,    4,    4,    1,    2,    3,    3,    3,    3,
    +            3,    3,    3,    3,    3,    3,    3,    3,    2,    2,
    +            2,    2,    3,    3,    3,    3,    3,    3,    3,    3,
    +            3,    3,    3,    3,    3,    3,    3,    3,    3,    2,
    +            2,    2,    2,    3,    3,    3,    3,    3,    3,    3,
    +            3,    3,    3,    1,    3,    5,    4,    3,    4,    4,
    +            2,    2,    2,    2,    2,    2,    2,    2,    2,    2,
    +            2,    2,    2,    2,    1,    1,    1,    3,    2,    1,
    +            2,   10,   11,    3,    3,    2,    4,    4,    3,    4,
    +            4,    4,    4,    7,    3,    2,    0,    4,    1,    3,
    +            2,    2,    4,    6,    2,    2,    4,    1,    1,    1,
    +            2,    3,    1,    1,    1,    1,    1,    1,    3,    3,
    +            4,    4,    0,    2,    1,    0,    1,    1,    0,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    3,    2,    1,    3,    1,    4,    3,    1,    3,
    +            3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
    +            3,    3,    3,    3,    3,    3,    2,    2,    2,    2,
    +            3,    3,    3,    3,    3,    3,    3,    3,    5,    4,
    +            4,    3,    1,    3,    1,    1,    3,    3,    0,    2,
    +            0,    1,    3,    1,    3,    1,    1,    1,    1,    1,
    +            6,    4,    3,    4,    2,    4,    4,    1,    3,    1,
    +            2,    1,    1,    4,    1,    3,    6,    4,    4,    4,
    +            4,    1,    4,    0,    1,    1,    3,    1,    4,    3,
    +            1,    1,    1,    0,    0,    2,    3,    1,    3,    1,
    +            4,    2,    2,    2,    1,    2,    1,    1,    4,    3,
    +            3,    3,    6,    3,    1,    1,    1
    +    );
    +
    +    protected function reduceRule0() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule1() {
    +         $this->semValue = $this->handleNamespaces($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule2() {
    +         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    +    }
    +
    +    protected function reduceRule3() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule4() {
    +         $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $nop = null; };
    +            if ($nop !== null) { $this->semStack[$this->stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule5() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule6() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule7() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule8() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule9() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule10() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule11() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule12() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule13() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule14() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule15() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule16() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule17() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule18() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule19() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule20() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule21() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule22() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule23() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule24() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule25() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule26() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule27() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule28() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule29() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule30() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule31() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule32() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule33() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule34() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule35() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule36() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule37() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule38() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule39() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule40() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule41() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule42() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule43() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule44() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule45() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule46() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule47() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule48() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule49() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule50() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule51() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule52() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule53() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule54() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule55() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule56() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule57() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule58() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule59() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule60() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule61() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule62() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule63() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule64() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule65() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule66() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule67() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule68() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule69() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule70() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule71() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule72() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule73() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule74() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule75() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule76() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule77() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule78() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule79() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule80() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule81() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule82() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule83() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule84() {
    +         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule85() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule86() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule87() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule88() {
    +         $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule89() {
    +         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule90() {
    +         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule91() {
    +         $this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule92() {
    +         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule93() {
    +         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule94() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule95() {
    +         $this->semValue = new Stmt\Const_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule96() {
    +         $this->semValue = Stmt\Use_::TYPE_FUNCTION;
    +    }
    +
    +    protected function reduceRule97() {
    +         $this->semValue = Stmt\Use_::TYPE_CONSTANT;
    +    }
    +
    +    protected function reduceRule98() {
    +         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], $this->semStack[$this->stackPos-(7-2)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule99() {
    +         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(8-4)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(8-7)], $this->semStack[$this->stackPos-(8-2)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule100() {
    +         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule101() {
    +         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule102() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule103() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule104() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule105() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule106() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule107() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule108() {
    +         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule109() {
    +         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule110() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule111() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule112() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL;
    +    }
    +
    +    protected function reduceRule113() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)]; $this->semValue->type = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule114() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule115() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule116() {
    +         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule117() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule118() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule119() {
    +         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule120() {
    +         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    +    }
    +
    +    protected function reduceRule121() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule122() {
    +         $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $nop = null; };
    +            if ($nop !== null) { $this->semStack[$this->stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule123() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule124() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule125() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule126() {
    +         throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule127() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)]; $attrs = $this->startAttributeStack[$this->stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments']) && isset($stmts[0])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); };
    +    }
    +
    +    protected function reduceRule128() {
    +         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(5-2)], ['stmts' => is_array($this->semStack[$this->stackPos-(5-3)]) ? $this->semStack[$this->stackPos-(5-3)] : array($this->semStack[$this->stackPos-(5-3)]), 'elseifs' => $this->semStack[$this->stackPos-(5-4)], 'else' => $this->semStack[$this->stackPos-(5-5)]], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule129() {
    +         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(8-2)], ['stmts' => $this->semStack[$this->stackPos-(8-4)], 'elseifs' => $this->semStack[$this->stackPos-(8-5)], 'else' => $this->semStack[$this->stackPos-(8-6)]], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule130() {
    +         $this->semValue = new Stmt\While_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule131() {
    +         $this->semValue = new Stmt\Do_($this->semStack[$this->stackPos-(5-4)], is_array($this->semStack[$this->stackPos-(5-2)]) ? $this->semStack[$this->stackPos-(5-2)] : array($this->semStack[$this->stackPos-(5-2)]), $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule132() {
    +         $this->semValue = new Stmt\For_(['init' => $this->semStack[$this->stackPos-(9-3)], 'cond' => $this->semStack[$this->stackPos-(9-5)], 'loop' => $this->semStack[$this->stackPos-(9-7)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule133() {
    +         $this->semValue = new Stmt\Switch_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule134() {
    +         $this->semValue = new Stmt\Break_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule135() {
    +         $this->semValue = new Stmt\Break_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule136() {
    +         $this->semValue = new Stmt\Continue_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule137() {
    +         $this->semValue = new Stmt\Continue_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule138() {
    +         $this->semValue = new Stmt\Return_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule139() {
    +         $this->semValue = new Stmt\Return_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule140() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule141() {
    +         $this->semValue = new Stmt\Global_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule142() {
    +         $this->semValue = new Stmt\Static_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule143() {
    +         $this->semValue = new Stmt\Echo_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule144() {
    +         $this->semValue = new Stmt\InlineHTML($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule145() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule146() {
    +         $this->semValue = new Stmt\Unset_($this->semStack[$this->stackPos-(5-3)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule147() {
    +         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(7-3)], $this->semStack[$this->stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$this->stackPos-(7-5)][1], 'stmts' => $this->semStack[$this->stackPos-(7-7)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule148() {
    +         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(9-3)], $this->semStack[$this->stackPos-(9-7)][0], ['keyVar' => $this->semStack[$this->stackPos-(9-5)], 'byRef' => $this->semStack[$this->stackPos-(9-7)][1], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule149() {
    +         $this->semValue = new Stmt\Declare_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule150() {
    +         $this->semValue = new Stmt\TryCatch($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-5)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule151() {
    +         $this->semValue = new Stmt\Throw_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule152() {
    +         $this->semValue = new Stmt\Goto_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule153() {
    +         $this->semValue = new Stmt\Label($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule154() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule155() {
    +         $this->semValue = array(); /* means: no statement */
    +    }
    +
    +    protected function reduceRule156() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule157() {
    +         $startAttributes = $this->startAttributeStack[$this->stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $this->semValue = null; };
    +            if ($this->semValue === null) $this->semValue = array(); /* means: no statement */
    +    }
    +
    +    protected function reduceRule158() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule159() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule160() {
    +         $this->semValue = new Stmt\Catch_($this->semStack[$this->stackPos-(8-3)], substr($this->semStack[$this->stackPos-(8-4)], 1), $this->semStack[$this->stackPos-(8-7)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule161() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule162() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    +    }
    +
    +    protected function reduceRule163() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule164() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule165() {
    +         $this->semValue = false;
    +    }
    +
    +    protected function reduceRule166() {
    +         $this->semValue = true;
    +    }
    +
    +    protected function reduceRule167() {
    +         $this->semValue = false;
    +    }
    +
    +    protected function reduceRule168() {
    +         $this->semValue = true;
    +    }
    +
    +    protected function reduceRule169() {
    +         $this->semValue = new Stmt\Function_($this->semStack[$this->stackPos-(10-3)], ['byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-5)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule170() {
    +         $this->semValue = new Stmt\Class_($this->semStack[$this->stackPos-(7-2)], ['type' => $this->semStack[$this->stackPos-(7-1)], 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule171() {
    +         $this->semValue = new Stmt\Interface_($this->semStack[$this->stackPos-(6-2)], ['extends' => $this->semStack[$this->stackPos-(6-3)], 'stmts' => $this->semStack[$this->stackPos-(6-5)]], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule172() {
    +         $this->semValue = new Stmt\Trait_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule173() {
    +         $this->semValue = 0;
    +    }
    +
    +    protected function reduceRule174() {
    +         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    +    }
    +
    +    protected function reduceRule175() {
    +         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    +    }
    +
    +    protected function reduceRule176() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule177() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule178() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule179() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule180() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule181() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule182() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule183() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule184() {
    +         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule185() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule186() {
    +         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule187() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule188() {
    +         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule189() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule190() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule191() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule192() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule193() {
    +         $this->semValue = new Stmt\DeclareDeclare($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule194() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule195() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    +    }
    +
    +    protected function reduceRule196() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule197() {
    +         $this->semValue = $this->semStack[$this->stackPos-(5-3)];
    +    }
    +
    +    protected function reduceRule198() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule199() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule200() {
    +         $this->semValue = new Stmt\Case_($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule201() {
    +         $this->semValue = new Stmt\Case_(null, $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule202() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule203() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule204() {
    +         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule205() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule206() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule207() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule208() {
    +         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(3-2)], is_array($this->semStack[$this->stackPos-(3-3)]) ? $this->semStack[$this->stackPos-(3-3)] : array($this->semStack[$this->stackPos-(3-3)]), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule209() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule210() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule211() {
    +         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule212() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule213() {
    +         $this->semValue = new Stmt\Else_(is_array($this->semStack[$this->stackPos-(2-2)]) ? $this->semStack[$this->stackPos-(2-2)] : array($this->semStack[$this->stackPos-(2-2)]), $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule214() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule215() {
    +         $this->semValue = new Stmt\Else_($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule216() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    +    }
    +
    +    protected function reduceRule217() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(2-2)], true);
    +    }
    +
    +    protected function reduceRule218() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    +    }
    +
    +    protected function reduceRule219() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule220() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule221() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule222() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule223() {
    +         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(4-4)], 1), null, $this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule224() {
    +         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(6-4)], 1), $this->semStack[$this->stackPos-(6-6)], $this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-3)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule225() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule226() {
    +         $this->semValue = 'array';
    +    }
    +
    +    protected function reduceRule227() {
    +         $this->semValue = 'callable';
    +    }
    +
    +    protected function reduceRule228() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule229() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule230() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule231() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule232() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule233() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule234() {
    +         $this->semValue = array(new Node\Arg($this->semStack[$this->stackPos-(3-2)], false, false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes));
    +    }
    +
    +    protected function reduceRule235() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule236() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule237() {
    +         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(1-1)], false, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule238() {
    +         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], true, false, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule239() {
    +         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], false, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule240() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule241() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule242() {
    +         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule243() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule244() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule245() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule246() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule247() {
    +         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule248() {
    +         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule249() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule250() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule251() {
    +         $this->semValue = new Stmt\Property($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule252() {
    +         $this->semValue = new Stmt\ClassConst($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule253() {
    +         $this->semValue = new Stmt\ClassMethod($this->semStack[$this->stackPos-(9-4)], ['type' => $this->semStack[$this->stackPos-(9-1)], 'byRef' => $this->semStack[$this->stackPos-(9-3)], 'params' => $this->semStack[$this->stackPos-(9-6)], 'returnType' => $this->semStack[$this->stackPos-(9-8)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule254() {
    +         $this->semValue = new Stmt\TraitUse($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule255() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule256() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule257() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule258() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule259() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule260() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(5-1)][0], $this->semStack[$this->stackPos-(5-1)][1], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule261() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], null, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule262() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule263() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule264() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)]);
    +    }
    +
    +    protected function reduceRule265() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule266() {
    +         $this->semValue = array(null, $this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule267() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule268() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule269() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule270() {
    +         $this->semValue = 0;
    +    }
    +
    +    protected function reduceRule271() {
    +         $this->semValue = 0;
    +    }
    +
    +    protected function reduceRule272() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule273() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule274() {
    +         Stmt\Class_::verifyModifier($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); $this->semValue = $this->semStack[$this->stackPos-(2-1)] | $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule275() {
    +         $this->semValue = Stmt\Class_::MODIFIER_PUBLIC;
    +    }
    +
    +    protected function reduceRule276() {
    +         $this->semValue = Stmt\Class_::MODIFIER_PROTECTED;
    +    }
    +
    +    protected function reduceRule277() {
    +         $this->semValue = Stmt\Class_::MODIFIER_PRIVATE;
    +    }
    +
    +    protected function reduceRule278() {
    +         $this->semValue = Stmt\Class_::MODIFIER_STATIC;
    +    }
    +
    +    protected function reduceRule279() {
    +         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    +    }
    +
    +    protected function reduceRule280() {
    +         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    +    }
    +
    +    protected function reduceRule281() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule282() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule283() {
    +         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule284() {
    +         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule285() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule286() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule287() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule288() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule289() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule290() {
    +         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule291() {
    +         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule292() {
    +         $this->semValue = new Expr\AssignRef($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule293() {
    +         $this->semValue = new Expr\AssignRef($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule294() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule295() {
    +         $this->semValue = new Expr\Clone_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule296() {
    +         $this->semValue = new Expr\AssignOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule297() {
    +         $this->semValue = new Expr\AssignOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule298() {
    +         $this->semValue = new Expr\AssignOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule299() {
    +         $this->semValue = new Expr\AssignOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule300() {
    +         $this->semValue = new Expr\AssignOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule301() {
    +         $this->semValue = new Expr\AssignOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule302() {
    +         $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule303() {
    +         $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule304() {
    +         $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule305() {
    +         $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule306() {
    +         $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule307() {
    +         $this->semValue = new Expr\AssignOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule308() {
    +         $this->semValue = new Expr\PostInc($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule309() {
    +         $this->semValue = new Expr\PreInc($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule310() {
    +         $this->semValue = new Expr\PostDec($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule311() {
    +         $this->semValue = new Expr\PreDec($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule312() {
    +         $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule313() {
    +         $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule314() {
    +         $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule315() {
    +         $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule316() {
    +         $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule317() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule318() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule319() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule320() {
    +         $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule321() {
    +         $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule322() {
    +         $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule323() {
    +         $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule324() {
    +         $this->semValue = new Expr\BinaryOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule325() {
    +         $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule326() {
    +         $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule327() {
    +         $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule328() {
    +         $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule329() {
    +         $this->semValue = new Expr\UnaryPlus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule330() {
    +         $this->semValue = new Expr\UnaryMinus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule331() {
    +         $this->semValue = new Expr\BooleanNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule332() {
    +         $this->semValue = new Expr\BitwiseNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule333() {
    +         $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule334() {
    +         $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule335() {
    +         $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule336() {
    +         $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule337() {
    +         $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule338() {
    +         $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule339() {
    +         $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule340() {
    +         $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule341() {
    +         $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule342() {
    +         $this->semValue = new Expr\Instanceof_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule343() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule344() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule345() {
    +         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(5-1)], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule346() {
    +         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(4-1)], null, $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule347() {
    +         $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule348() {
    +         $this->semValue = new Expr\Isset_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule349() {
    +         $this->semValue = new Expr\Empty_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule350() {
    +         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule351() {
    +         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule352() {
    +         $this->semValue = new Expr\Eval_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule353() {
    +         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule354() {
    +         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule355() {
    +         $this->semValue = new Expr\Cast\Int_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule356() {
    +         $this->semValue = new Expr\Cast\Double($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule357() {
    +         $this->semValue = new Expr\Cast\String_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule358() {
    +         $this->semValue = new Expr\Cast\Array_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule359() {
    +         $this->semValue = new Expr\Cast\Object_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule360() {
    +         $this->semValue = new Expr\Cast\Bool_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule361() {
    +         $this->semValue = new Expr\Cast\Unset_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule362() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes;
    +            $attrs['kind'] = strtolower($this->semStack[$this->stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
    +            $this->semValue = new Expr\Exit_($this->semStack[$this->stackPos-(2-2)], $attrs);
    +    }
    +
    +    protected function reduceRule363() {
    +         $this->semValue = new Expr\ErrorSuppress($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule364() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule365() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule366() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule367() {
    +         $this->semValue = new Expr\ShellExec($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule368() {
    +         $this->semValue = new Expr\Print_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule369() {
    +         $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule370() {
    +         $this->semValue = new Expr\YieldFrom($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule371() {
    +         $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-4)], 'uses' => $this->semStack[$this->stackPos-(10-6)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule372() {
    +         $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$this->stackPos-(11-3)], 'params' => $this->semStack[$this->stackPos-(11-5)], 'uses' => $this->semStack[$this->stackPos-(11-7)], 'returnType' => $this->semStack[$this->stackPos-(11-8)], 'stmts' => $this->semStack[$this->stackPos-(11-10)]], $this->startAttributeStack[$this->stackPos-(11-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule373() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule374() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule375() {
    +         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(2-2)], null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule376() {
    +         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule377() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG;
    +            $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(4-3)], $attrs);
    +    }
    +
    +    protected function reduceRule378() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT;
    +            $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(3-2)], $attrs);
    +    }
    +
    +    protected function reduceRule379() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule380() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$this->stackPos-(4-1)][0] === "'" || ($this->semStack[$this->stackPos-(4-1)][1] === "'" && ($this->semStack[$this->stackPos-(4-1)][0] === 'b' || $this->semStack[$this->stackPos-(4-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
    +            $this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$this->stackPos-(4-1)]), $attrs), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule381() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule382() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule383() {
    +         $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-2)]);
    +    }
    +
    +    protected function reduceRule384() {
    +         $this->semValue = new Expr\New_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule385() {
    +         list($class, $ctorArgs) = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule386() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule387() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    +    }
    +
    +    protected function reduceRule388() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule389() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule390() {
    +         $this->semValue = new Expr\ClosureUse(substr($this->semStack[$this->stackPos-(2-2)], 1), $this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule391() {
    +         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule392() {
    +         $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule393() {
    +         $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-4)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule394() {
    +
    +            if ($this->semStack[$this->stackPos-(2-1)] instanceof Node\Expr\StaticPropertyFetch) {
    +                $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(2-1)]->class, new Expr\Variable($this->semStack[$this->stackPos-(2-1)]->name, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +            } elseif ($this->semStack[$this->stackPos-(2-1)] instanceof Node\Expr\ArrayDimFetch) {
    +                $tmp = $this->semStack[$this->stackPos-(2-1)];
    +                while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
    +                    $tmp = $tmp->var;
    +                }
    +
    +                $this->semValue = new Expr\StaticCall($tmp->var->class, $this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +                $tmp->var = new Expr\Variable($tmp->var->name, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +            } else {
    +                throw new \Exception;
    +            }
    +
    +    }
    +
    +    protected function reduceRule395() {
    +         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule396() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule397() {
    +         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule398() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule399() {
    +         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule400() {
    +         $this->semValue = new Name\FullyQualified($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule401() {
    +         $this->semValue = new Name\Relative($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule402() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule403() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule404() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule405() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule406() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule407() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule408() {
    +         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule409() {
    +         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule410() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule411() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule412() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule413() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule414() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule415() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule416() {
    +         $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$this->stackPos-(1-1)], '`', false), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes));
    +    }
    +
    +    protected function reduceRule417() {
    +         foreach ($this->semStack[$this->stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', false); } }; $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule418() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule419() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule420() {
    +         $this->semValue = Scalar\LNumber::fromString($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes, true);
    +    }
    +
    +    protected function reduceRule421() {
    +         $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$this->stackPos-(1-1)]), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule422() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$this->stackPos-(1-1)][0] === "'" || ($this->semStack[$this->stackPos-(1-1)][1] === "'" && ($this->semStack[$this->stackPos-(1-1)][0] === 'b' || $this->semStack[$this->stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
    +            $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$this->stackPos-(1-1)], false), $attrs);
    +    }
    +
    +    protected function reduceRule423() {
    +         $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule424() {
    +         $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule425() {
    +         $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule426() {
    +         $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule427() {
    +         $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule428() {
    +         $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule429() {
    +         $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule430() {
    +         $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule431() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
    +            $this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], false), $attrs);
    +    }
    +
    +    protected function reduceRule432() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(2-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(2-1)], $matches); $attrs['docLabel'] = $matches[1];;
    +            $this->semValue = new Scalar\String_('', $attrs);
    +    }
    +
    +    protected function reduceRule433() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule434() {
    +         $this->semValue = new Expr\ClassConstFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule435() {
    +         $this->semValue = new Expr\ConstFetch($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule436() {
    +         $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule437() {
    +         $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule438() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule439() {
    +         $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule440() {
    +         $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule441() {
    +         $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule442() {
    +         $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule443() {
    +         $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule444() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule445() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule446() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule447() {
    +         $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule448() {
    +         $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule449() {
    +         $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule450() {
    +         $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule451() {
    +         $this->semValue = new Expr\BinaryOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule452() {
    +         $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule453() {
    +         $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule454() {
    +         $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule455() {
    +         $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule456() {
    +         $this->semValue = new Expr\UnaryPlus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule457() {
    +         $this->semValue = new Expr\UnaryMinus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule458() {
    +         $this->semValue = new Expr\BooleanNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule459() {
    +         $this->semValue = new Expr\BitwiseNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule460() {
    +         $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule461() {
    +         $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule462() {
    +         $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule463() {
    +         $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule464() {
    +         $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule465() {
    +         $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule466() {
    +         $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule467() {
    +         $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule468() {
    +         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(5-1)], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule469() {
    +         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(4-1)], null, $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule470() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule471() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule472() {
    +         $this->semValue = new Expr\ConstFetch($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule473() {
    +         $this->semValue = new Expr\ClassConstFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule474() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule475() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule476() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
    +            foreach ($this->semStack[$this->stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $attrs);
    +    }
    +
    +    protected function reduceRule477() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
    +            foreach ($this->semStack[$this->stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, true); } } $s->value = preg_replace('~(\r\n|\n|\r)\z~', '', $s->value); if ('' === $s->value) array_pop($this->semStack[$this->stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $attrs);
    +    }
    +
    +    protected function reduceRule478() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule479() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule480() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule481() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule482() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule483() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule484() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(3-3)], $this->semStack[$this->stackPos-(3-1)], false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule485() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule486() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule487() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule488() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule489() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule490() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-5)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule491() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule492() {
    +         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule493() {
    +         $this->semValue = new Expr\MethodCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule494() {
    +         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule495() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule496() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule497() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule498() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule499() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule500() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule501() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule502() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule503() {
    +         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule504() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule505() {
    +         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], substr($this->semStack[$this->stackPos-(3-3)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule506() {
    +         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-5)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule507() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule508() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule509() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule510() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule511() {
    +         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule512() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule513() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule514() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule515() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule516() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule517() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule518() {
    +         $this->semValue = new Expr\List_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule519() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule520() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule521() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule522() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule523() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule524() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule525() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule526() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule527() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule528() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(3-3)], $this->semStack[$this->stackPos-(3-1)], false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule529() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule530() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-1)], true, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule531() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(2-2)], null, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule532() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule533() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule534() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule535() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]);
    +    }
    +
    +    protected function reduceRule536() {
    +         $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule537() {
    +         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule538() {
    +         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(4-1)], 1), $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule539() {
    +         $this->semValue = new Expr\PropertyFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule540() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule541() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule542() {
    +         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-4)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule543() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule544() {
    +         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule545() {
    +         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule546() {
    +         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php b/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php
    new file mode 100644
    index 0000000..87d0bac
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php
    @@ -0,0 +1,2707 @@
    +'",
    +        "T_IS_GREATER_OR_EQUAL",
    +        "T_SL",
    +        "T_SR",
    +        "'+'",
    +        "'-'",
    +        "'.'",
    +        "'*'",
    +        "'/'",
    +        "'%'",
    +        "'!'",
    +        "T_INSTANCEOF",
    +        "'~'",
    +        "T_INC",
    +        "T_DEC",
    +        "T_INT_CAST",
    +        "T_DOUBLE_CAST",
    +        "T_STRING_CAST",
    +        "T_ARRAY_CAST",
    +        "T_OBJECT_CAST",
    +        "T_BOOL_CAST",
    +        "T_UNSET_CAST",
    +        "'@'",
    +        "T_POW",
    +        "'['",
    +        "T_NEW",
    +        "T_CLONE",
    +        "T_EXIT",
    +        "T_IF",
    +        "T_ELSEIF",
    +        "T_ELSE",
    +        "T_ENDIF",
    +        "T_LNUMBER",
    +        "T_DNUMBER",
    +        "T_STRING",
    +        "T_STRING_VARNAME",
    +        "T_VARIABLE",
    +        "T_NUM_STRING",
    +        "T_INLINE_HTML",
    +        "T_ENCAPSED_AND_WHITESPACE",
    +        "T_CONSTANT_ENCAPSED_STRING",
    +        "T_ECHO",
    +        "T_DO",
    +        "T_WHILE",
    +        "T_ENDWHILE",
    +        "T_FOR",
    +        "T_ENDFOR",
    +        "T_FOREACH",
    +        "T_ENDFOREACH",
    +        "T_DECLARE",
    +        "T_ENDDECLARE",
    +        "T_AS",
    +        "T_SWITCH",
    +        "T_ENDSWITCH",
    +        "T_CASE",
    +        "T_DEFAULT",
    +        "T_BREAK",
    +        "T_CONTINUE",
    +        "T_GOTO",
    +        "T_FUNCTION",
    +        "T_CONST",
    +        "T_RETURN",
    +        "T_TRY",
    +        "T_CATCH",
    +        "T_FINALLY",
    +        "T_THROW",
    +        "T_USE",
    +        "T_INSTEADOF",
    +        "T_GLOBAL",
    +        "T_STATIC",
    +        "T_ABSTRACT",
    +        "T_FINAL",
    +        "T_PRIVATE",
    +        "T_PROTECTED",
    +        "T_PUBLIC",
    +        "T_VAR",
    +        "T_UNSET",
    +        "T_ISSET",
    +        "T_EMPTY",
    +        "T_HALT_COMPILER",
    +        "T_CLASS",
    +        "T_TRAIT",
    +        "T_INTERFACE",
    +        "T_EXTENDS",
    +        "T_IMPLEMENTS",
    +        "T_OBJECT_OPERATOR",
    +        "T_LIST",
    +        "T_ARRAY",
    +        "T_CALLABLE",
    +        "T_CLASS_C",
    +        "T_TRAIT_C",
    +        "T_METHOD_C",
    +        "T_FUNC_C",
    +        "T_LINE",
    +        "T_FILE",
    +        "T_START_HEREDOC",
    +        "T_END_HEREDOC",
    +        "T_DOLLAR_OPEN_CURLY_BRACES",
    +        "T_CURLY_OPEN",
    +        "T_PAAMAYIM_NEKUDOTAYIM",
    +        "T_NAMESPACE",
    +        "T_NS_C",
    +        "T_DIR",
    +        "T_NS_SEPARATOR",
    +        "T_ELLIPSIS",
    +        "';'",
    +        "'{'",
    +        "'}'",
    +        "'('",
    +        "')'",
    +        "'`'",
    +        "']'",
    +        "'\"'",
    +        "'$'"
    +    );
    +
    +    protected $tokenToSymbol = array(
    +            0,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,   53,  155,  157,  156,   52,   35,  157,
    +          151,  152,   50,   47,    7,   48,   49,   51,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,   29,  148,
    +           41,   15,   43,   28,   65,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,   67,  157,  154,   34,  157,  153,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  149,   33,  150,   55,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
    +          157,  157,  157,  157,  157,  157,    1,    2,    3,    4,
    +            5,    6,    8,    9,   10,   11,   12,   13,   14,   16,
    +           17,   18,   19,   20,   21,   22,   23,   24,   25,   26,
    +           27,   30,   31,   32,   36,   37,   38,   39,   40,   42,
    +           44,   45,   46,   54,   56,   57,   58,   59,   60,   61,
    +           62,   63,   64,   66,   68,   69,   70,   71,   72,   73,
    +           74,   75,   76,   77,   78,   79,   80,   81,  157,  157,
    +           82,   83,   84,   85,   86,   87,   88,   89,   90,   91,
    +           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
    +          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
    +          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
    +          122,  123,  124,  125,  126,  127,  128,  129,  130,  131,
    +          132,  133,  134,  135,  136,  137,  157,  157,  157,  157,
    +          157,  157,  138,  139,  140,  141,  142,  143,  144,  145,
    +          146,  147
    +    );
    +
    +    protected $action = array(
    +          559,  560,  561,  562,  563,  704,  564,  565,  566,  602,
    +          603,-32766,-32766,-32766,-32767,-32767,-32767,-32767,   88,   89,
    +           90,   91,   92,  520,-32766,-32766,-32766,-32766,-32766,-32766,
    +            0,-32766,  111,-32766,-32766,-32766,-32766,-32766,-32766,-32767,
    +        -32767,-32767,-32767,-32767,-32766,  335,-32766,-32766,-32766,-32766,
    +        -32766,-32766,  567,-32766,-32766,-32766,-32766,  279,  825,  826,
    +          827,  824,  823,  822,-32766,-32766,  568,  569,  570,  571,
    +          572,  573,  574,  265,-32766,  634,-32766,-32766,-32766,-32766,
    +        -32766,  258,  575,  576,  577,  578,  579,  580,  581,  582,
    +          583,  584,  585,  605,  606,  607,  608,  609,  597,  598,
    +          599,  600,  601,  586,  587,  588,  589,  590,  591,  592,
    +          628,  629,  630,  631,  632,  633,  593,  594,  595,  596,
    +          626,  617,  615,  616,  612,  613,   24,  604,  610,  611,
    +          618,  619,  621,  620,  622,  623,   40,   41,  373,   42,
    +           43,  614,  625,  624,    6,   44,   45,    7,   46, -262,
    +          261, -418,  695,  825,  826,  827,  824,  823,  822,  817,
    +          113,   22,   93,   94,   95,  636,  235,  985,  236,   22,
    +          650,  651, 1027,-32766, 1029, 1028, -417,  949,   96,-32766,
    +          985,   47,   48,  509, -233,  949,  219,   49,-32766,   50,
    +          214,  215,   51,   52,   53,   54,   55,   56,   57,   58,
    +          929,   22,  229,   59,  342,-32766,-32766,-32766, -450,  950,
    +          951,  636, -418,  985,  330, -459,  221,  949,-32766,-32766,
    +        -32766,  705, -160,  390,  391,-32766, -418,-32766,-32766,-32766,
    +        -32766,  400,  391, -418,  344, -421,  346, -417,-32766,  209,
    +        -32766,-32766,-32766,  361,  267,   63,  399,   28,  359,  510,
    +          118, -417,  344,   63,  386,  387,  803,  267, -417,  128,
    +         -420,  370,  219,  390,  391,   39,  955,  956,  957,  958,
    +          952,  953,  237,-32766,-32766,-32766, -460,  400,  959,  954,
    +          344,  126,-32766,-32766,-32766,   61,  211,  247,  799,  248,
    +          267,  374, -122, -122, -122,   -4,  705,  375,  117,  282,
    +         -416,  694,-32766,  782,   32,   17,  376, -122,  377, -122,
    +          378, -122,  379, -122,  985,  380, -122, -122, -122,   33,
    +           34,  381,  343,  752,   35,  382,  251,  299,   60, -233,
    +         1019,  280,  281,  383,  384,-32766,-32766,-32766,-32766,  385,
    +          288,   21,  680,  723,  388,  389,  341,  112,   90,   91,
    +           92,  269,   37, -450,  354,-32766,  122,-32766,-32766,-32766,
    +         -459, -416, -459,  362,  537, -159,  374, -160,  707,  525,
    +         -122,-32766,  375,  353,  117, -416,  694,  125, -212,   32,
    +           17,  376, -416,  377,  222,  378,  121,  379,   25,  217,
    +          380,  267,-32766,   16,   33,   34,  381,  343,  336,   35,
    +          382,  998,  798,   60,  246,  705,  280,  281,  383,  384,
    +          776,  777,  447,  250,  385,-32766,   22,  642,  723,  388,
    +          389, -460,  115, -460,  114,  426,   70,   71,   72,  494,
    +          495, 1001,  949,  529,  110,  123,-32766,  109,  263, 1024,
    +          691,  542,  753,  707,  525,   -4,   26,  235,   73,   74,
    +           75,   76,   77,   78,   79,   80,   81,   82,   83,   84,
    +           85,   86,   87,   88,   89,   90,   91,   92,   93,   94,
    +           95,  116,  235,  119,  705,  374,  238,  350,  390,  391,
    +          527,  375,  963,  703,   96,  694,  783,-32766,   32,   17,
    +          376,  922,  377,  216,  378,  692,  379,  484,   18,  380,
    +           63,  220,  530,   33,   34,  381,  705,  716,   35,  382,
    +         -159,  218,   60,   38,  649,  280,  281,  124,  290,   96,
    +        -32766,  650,  651,  385,  802,  553,  504,  479,  480,  814,
    +          543,  643,  528,  439,  531,  309,  438,  425,  776,  777,
    +          420,  419,  351,  430,  374,  349,  637,  663,  636,-32766,
    +          375, 1022,  707,  525,  694,  489,  424,   32,   17,  376,
    +         -216,  377,  508,  378,-32766,  379,  925,  493,  380,  482,
    +          435,  519,   33,   34,  381,  705,  374,   35,  382,  485,
    +          505,   60,  375,  348,  280,  281,  694,  -80,  208,   32,
    +           17,  376,  385,  377,  442,  378,   10,  379,  368,  498,
    +          380,  262,  490,  538,   33,   34,  381,  705,  477,   35,
    +          382,  259,  264,   60,  965,    0,  280,  281,  725,  962,
    +          337,  707,  525,    0,  385,  260,  718,  724,  710,    0,
    +            0,    0,    0,    0,    0,  532,    0,    0,    0,    0,
    +            0,    0,    0,    3,    0,  374,    0,    0,    0, -376,
    +            9,  375,  287,  739,  525,  694,    0,  331,   32,   17,
    +          376,  302,  377,  314,  378,  315,  379,  319,  350,  380,
    +          432,  332,  328,   33,   34,  381,  705,  374,   35,  382,
    +          648,  693,   60,  375,  808,  280,  281,  694,  552,  551,
    +           32,   17,  376,  385,  377,   31,  378,  647,  379,  701,
    +           30,  380,  646,  807,  810,   33,   34,  381,  806,  735,
    +           35,  382,  737,  683,   60,  747,  746,  280,  281,  740,
    +          755,  685,  707,  525,  696,  385,  690,  702,  689,  688,
    +           27,   97,   98,   99,  100,  101,  102,  103,  104,  105,
    +          106,  107,  108,  809,  917,  257,  374,  256,   69,  549,
    +          548,  546,  375,  544,  707,  525,  694,  541,  540,   32,
    +           17,  376,  536,  377,  535,  378,  533,  379,  526,  329,
    +          380,  854,  856,  916,   33,   34,  381,  719,  712,   35,
    +          382, 1025, -416,   60,  815,  645,  280,  281, 1026,  721,
    +          653,  652,  720,  918,  385,  744,  655,  654,  722,  545,
    +          681, 1023,  986,  979,  991,  996,  999,  745,  644,    0,
    +           36, -441,  339,  334,  266,  234,  233,  232,  231,  213,
    +          212,  210,  129,  707,  525, -421,  910,  127, -420, -419,
    +          120,   20,   23,   68,   67,   29,   62,   64,   66,   65,
    +         -443,    0,   15, -416,   19,  242,  894,  289,  456, -213,
    +          473,  893,  461,  518,  897,   11,  947, -416,  939,  515,
    +         -212,  371,  367,  365, -416,  363,   14,  964,   13,   12,
    +            0, -387,    0,  483,  990, 1021,  977,  978,  948
    +    );
    +
    +    protected $actionCheck = array(
    +            2,    3,    4,    5,    6,    1,    8,    9,   10,   11,
    +           12,    8,    9,   10,   41,   42,   43,   44,   45,   46,
    +           47,   48,   49,   77,    8,    9,   10,    8,    9,   10,
    +            0,   28,   13,   30,   31,   32,   33,   34,   35,   36,
    +           37,   38,   39,   40,   28,    7,   30,   31,   32,   33,
    +           34,   35,   54,    8,    8,    9,   10,    7,  112,  113,
    +          114,  115,  116,  117,    8,    9,   68,   69,   70,   71,
    +           72,   73,   74,    7,   28,   77,   30,   31,   32,   33,
    +           34,    7,   84,   85,   86,   87,   88,   89,   90,   91,
    +           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
    +          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
    +          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
    +          122,  123,  124,  125,  126,  127,    7,  129,  130,  131,
    +          132,  133,  134,  135,  136,  137,    2,    3,    4,    5,
    +            6,  143,  144,  145,  103,   11,   12,    7,   14,   79,
    +          109,   67,  148,  112,  113,  114,  115,  116,  117,  118,
    +            7,   67,   50,   51,   52,   77,   54,   79,    7,   67,
    +          102,  103,   77,  103,   79,   80,   67,   83,   66,  109,
    +           79,   47,   48,   77,    7,   83,   35,   53,  118,   55,
    +           56,   57,   58,   59,   60,   61,   62,   63,   64,   65,
    +          112,   67,   68,   69,   70,    8,    9,   10,    7,   75,
    +           76,   77,  128,   79,  146,    7,    7,   83,    8,    9,
    +           10,    1,    7,  129,  130,   28,  142,   30,   31,   32,
    +           33,  143,  130,  149,  146,  151,  102,  128,   28,   13,
    +           30,   31,   32,   29,  156,  151,  112,   13,    7,  143,
    +          149,  142,  146,  151,  120,  121,  150,  156,  149,   15,
    +          151,    7,   35,  129,  130,   67,  132,  133,  134,  135,
    +          136,  137,  138,    8,    9,   10,    7,  143,  144,  145,
    +          146,   15,    8,    9,   10,  151,    7,  153,  148,  155,
    +          156,   71,   72,   73,   74,    0,    1,   77,  147,    7,
    +           67,   81,   28,  152,   84,   85,   86,   87,   88,   89,
    +           90,   91,   92,   93,   79,   95,   96,   97,   98,   99,
    +          100,  101,  102,   29,  104,  105,  128,   79,  108,  152,
    +           82,  111,  112,  113,  114,    8,    9,   10,   79,  119,
    +          142,    7,  122,  123,  124,  125,    7,  149,   47,   48,
    +           49,   67,   67,  152,    7,   28,   67,   30,   31,   79,
    +          152,  128,  154,  149,   29,    7,   71,  152,  148,  149,
    +          150,  112,   77,    7,  147,  142,   81,   15,  152,   84,
    +           85,   86,  149,   88,   35,   90,   15,   92,  140,  141,
    +           95,  156,  112,  152,   99,  100,  101,  102,  103,  104,
    +          105,   77,  148,  108,  109,    1,  111,  112,  113,  114,
    +          130,  131,  128,  128,  119,  156,   67,  122,  123,  124,
    +          125,  152,   15,  154,   15,   82,    8,    9,   10,   72,
    +           73,  152,   83,   29,  149,   29,  156,   15,  143,  150,
    +          148,   29,  148,  148,  149,  150,   28,   54,   30,   31,
    +           32,   33,   34,   35,   36,   37,   38,   39,   40,   41,
    +           42,   43,   44,   45,   46,   47,   48,   49,   50,   51,
    +           52,   29,   54,  149,    1,   71,   29,  146,  129,  130,
    +          149,   77,  139,   29,   66,   81,  152,   79,   84,   85,
    +           86,  152,   88,   35,   90,  148,   92,   72,   73,   95,
    +          151,   35,   29,   99,  100,  101,    1,   35,  104,  105,
    +          152,   35,  108,   67,  148,  111,  112,   97,   98,   66,
    +          112,  102,  103,  119,  148,  149,   74,  106,  107,  148,
    +          149,  148,  149,   77,   29,   78,   77,   77,  130,  131,
    +           77,   77,   77,   82,   71,   77,   77,   77,   77,   82,
    +           77,   77,  148,  149,   81,   93,   79,   84,   85,   86,
    +          152,   88,   79,   90,  156,   92,   79,   79,   95,   79,
    +           86,   89,   99,  100,  101,    1,   71,  104,  105,   87,
    +           91,  108,   77,  102,  111,  112,   81,   94,   94,   84,
    +           85,   86,  119,   88,   94,   90,   94,   92,  102,   96,
    +           95,  110,   96,   29,   99,  100,  101,    1,  109,  104,
    +          105,  126,  126,  108,  139,   -1,  111,  112,  123,  139,
    +          123,  148,  149,   -1,  119,  127,  147,  123,  150,   -1,
    +           -1,   -1,   -1,   -1,   -1,   29,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,  142,   -1,   71,   -1,   -1,   -1,  142,
    +          142,   77,  142,  148,  149,   81,   -1,  146,   84,   85,
    +           86,  146,   88,  146,   90,  146,   92,  146,  146,   95,
    +          146,  146,  149,   99,  100,  101,    1,   71,  104,  105,
    +          148,  148,  108,   77,  148,  111,  112,   81,  148,  148,
    +           84,   85,   86,  119,   88,  148,   90,  148,   92,  148,
    +          148,   95,  148,  148,  148,   99,  100,  101,  148,  148,
    +          104,  105,  148,  148,  108,  148,  148,  111,  112,  148,
    +          148,  148,  148,  149,  148,  119,  148,  148,  148,  148,
    +           15,   16,   17,   18,   19,   20,   21,   22,   23,   24,
    +           25,   26,   27,  148,  150,  149,   71,  149,  149,  149,
    +          149,  149,   77,  149,  148,  149,   81,  149,  149,   84,
    +           85,   86,  149,   88,  149,   90,  149,   92,  149,  149,
    +           95,   56,   57,  150,   99,  100,  101,  150,  150,  104,
    +          105,  150,   67,  108,  150,  150,  111,  112,  150,  150,
    +          150,  150,  150,  150,  119,  150,  150,  150,  150,  150,
    +          150,  150,  150,  150,  150,  150,  150,  150,  150,   -1,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  148,  149,  151,  153,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,   -1,  152,  128,  152,  152,  152,  152,  152,  152,
    +          152,  152,  152,  152,  152,  152,  152,  142,  152,  152,
    +          152,  152,  152,  152,  149,  152,  152,  155,  152,  152,
    +           -1,  153,   -1,  154,  154,  154,  154,  154,  154
    +    );
    +
    +    protected $actionBase = array(
    +            0,  220,  295,  101,  106,  536,   -2,   -2,   -2,   -2,
    +          -54,  473,  606,  574,  606,  404,  505,  675,  675,  675,
    +          151,  227,  458,  458,  458,  457,  442,  476,  466,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          294,    4,  234,  551,  693,  702,  696,  690,  703,  494,
    +          695,  694,  651,  652,  406,  653,  654,  655,  656,  698,
    +          719,  692,  701,  418,  418,  418,  418,  418,  418,  418,
    +          418,  418,  418,  418,  418,  418,  418,  418,  418,   45,
    +           19,   56,  265,  265,  265,  265,  265,  265,  265,  265,
    +          265,  265,  265,  265,  265,  265,  265,  265,  265,  265,
    +          274,  274,  274,  327,  210,  197,   46,  715,   16,    3,
    +            3,    3,    3,    3,  -27,  -27,  -27,  -27,  349,  349,
    +           94,   94,  102,  102,  102,  102,  102,  102,  102,  102,
    +          102,  102,  102,  648,  639,  642,  643,  301,  301,  497,
    +           70,  408,  408,  408,  408,   88,  280,  343,  280,  475,
    +          712,   84,  109,  112,  112,  112,   68,  461,  248,  248,
    +          324,  324,  233,  233,  198,  233,  419,  419,  419,  259,
    +          259,  259,  259,  331,  259,  259,  259,  599,  467,   95,
    +          506,  645,  376,  503,  657,  285,  269,  208,  511,  525,
    +          235,  481,  235,  421,  425,  357,  507,  235,  235,  214,
    +          294,  381,  393,  533,  456,  366,  554,  292,  347,  284,
    +          383,  241,  598,  549,  700,  358,  699,  201,  279,  289,
    +          393,  393,  393,  334,  596,  523,  177,  226,  647,  620,
    +          215,  646,  641,  140,  254,  640,  339,  560,  471,  471,
    +          471,  471,  471,  471,  472,  471,  463,  680,  680,  459,
    +          490,  472,  659,  472,  471,  680,  472,  119,  472,  485,
    +          471,  486,  486,  463,  477,  498,  680,  680,  498,  459,
    +          472,  541,  540,  499,  479,  447,  447,  499,  472,  447,
    +          490,  447,   30,  685,  686,  454,  688,  684,  687,  661,
    +          683,  464,  619,  504,  495,  669,  668,  682,  460,  468,
    +          670,  681,  524,  532,  465,  422,  501,  446,  678,  481,
    +          522,  453,  453,  453,  446,  674,  453,  453,  453,  453,
    +          453,  453,  453,  453,  724,  510,  484,  581,  580,  579,
    +          409,  578,  515,  500,  407,  604,  480,  524,  524,  650,
    +          718,  673,  469,  667,  706,  679,  552,  153,  371,  666,
    +          649,  517,  470,  519,  665,  602,  704,  474,  638,  524,
    +          635,  453,  660,  689,  722,  723,  677,  720,  713,  161,
    +          521,  576,   66,  721,  658,  601,  600,  547,  717,  711,
    +          710,  496,   66,  573,  492,  697,  462,  662,  488,  663,
    +          617,  362,  266,  631,  676,  572,  716,  714,  708,  571,
    +          568,  615,  613,  244,  671,  335,  452,  489,  567,  487,
    +          483,  628,  609,  664,  565,  564,  627,  623,  707,  493,
    +          522,  508,  491,  502,  482,  608,  594,  709,  412,  561,
    +          595,  556,  478,  555,  634,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,  134,  134,   -2,   -2,   -2,
    +            0,    0,    0,    0,   -2,  134,  134,  134,  134,  134,
    +          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
    +          134,  134,  134,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,  418,  418,  418,  418,  418,  418,
    +          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
    +          418,  418,  418,  418,  418,  418,  418,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,  418,  418,  418,  418,  418,  418,  418,
    +          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
    +          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
    +          418,    0,  418,  418,  418,  418,  418,  418,  112,  112,
    +          112,  112,   88,   88,   88,   88,   88,   88,   88,   88,
    +           88,   88,   88,   88,   88,   88,   88,   41,   41,   41,
    +           41,  112,  112,   88,   41,   88,   88,   88,   88,    0,
    +           88,  248,   88,  248,  248,    0,    0,    0,    0,    0,
    +          471,  248,    0,    0,  235,  235,    0,    0,    0,    0,
    +          471,  471,  471,   88,   88,   88,   88,  471,   88,   88,
    +           88,  235,  248,    0,  420,  420,   66,  420,  420,    0,
    +            0,    0,  471,  471,    0,  477,    0,    0,    0,    0,
    +          680,    0,    0,    0,    0,    0,  453,  153,  667,    0,
    +           50,    0,    0,    0,    0,    0,  469,   50,  209,    0,
    +          209,    0,    0,    0,  453,  453,  453,    0,  469,  469,
    +            0,    0,   74,  469,    0,   74,   38,    0,    0,   38,
    +            0,   66
    +    );
    +
    +    protected $actionDefault = array(
    +            3,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,  453,  453,  413,32767,32767,32767,32767,  280,
    +          280,  280,32767,  414,  414,  414,  414,  414,  414,  414,
    +        32767,32767,32767,32767,32767,  358,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,  458,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,  341,  342,  344,  345,  279,  415,  232,
    +          457,  278,  116,  241,  234,  189,  119,  277,  220,  306,
    +          359,  308,  357,  361,  307,  284,  288,  289,  290,  291,
    +          292,  293,  294,  295,  296,  297,  298,  299,  283,  360,
    +          338,  337,  336,  304,  305,  309,  311,  282,  310,  327,
    +          328,  325,  326,  329,  330,  331,  332,  333,32767,32767,
    +          452,  452,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,  264,  264,  264,  264,  318,  319,32767,
    +          265,  224,  224,  224,  224,32767,  224,32767,32767,32767,
    +        32767,  406,  335,  313,  314,  312,32767,  386,32767,  388,
    +        32767,32767,  301,  303,  381,  285,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,  383,  416,  416,32767,32767,
    +        32767,  375,32767,  157,  208,  210,  391,32767,32767,32767,
    +        32767,32767,  323,32767,32767,32767,32767,32767,32767,  466,
    +        32767,32767,32767,32767,32767,  416,32767,  416,32767,32767,
    +          315,  316,  317,32767,32767,32767,  416,  416,32767,32767,
    +          416,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,  161,32767,32767,  389,  389,32767,
    +        32767,  161,  384,  161,32767,32767,  161,  412,  161,  174,
    +        32767,  172,  172,32767,32767,  176,32767,  430,  176,32767,
    +          161,  194,  194,  367,  163,  226,  226,  367,  161,  226,
    +        32767,  226,32767,32767,32767,   82,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,  377,32767,32767,32767,32767,  407,  428,  375,
    +        32767,  321,  322,  324,32767,  418,  346,  347,  348,  349,
    +          350,  351,  352,  354,32767,  380,32767,32767,32767,32767,
    +        32767,32767,   84,  108,  240,32767,  465,   84,  378,32767,
    +          465,32767,32767,32767,32767,32767,32767,  281,32767,32767,
    +        32767,   84,32767,   84,32767,32767,32767,32767,  416,  379,
    +        32767,  320,  392,  434,32767,32767,  417,32767,32767,  215,
    +           84,32767,  175,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,  177,32767,32767,  416,32767,32767,32767,32767,
    +        32767,32767,  276,32767,32767,32767,32767,32767,  416,32767,
    +        32767,32767,32767,  219,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,   82,
    +           60,32767,  258,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,  121,  121,    3,    3,  121,
    +          121,  121,  121,  121,  121,  121,  121,  121,  121,  121,
    +          121,  121,  121,  121,  243,  154,  243,  202,  243,  243,
    +          205,  194,  194,  250
    +    );
    +
    +    protected $goto = array(
    +          159,  159,  132,  132,  132,  142,  144,  175,  160,  157,
    +          157,  157,  157,  158,  158,  158,  158,  158,  158,  158,
    +          153,  154,  155,  156,  172,  170,  173,  401,  402,  292,
    +          403,  406,  407,  408,  409,  410,  411,  412,  413,  841,
    +          133,  134,  135,  136,  137,  138,  139,  140,  141,  143,
    +          169,  171,  174,  190,  193,  194,  195,  196,  198,  199,
    +          200,  201,  202,  203,  204,  205,  206,  207,  227,  228,
    +          243,  244,  245,  310,  311,  312,  451,  176,  177,  178,
    +          179,  180,  181,  182,  183,  184,  185,  186,  187,  188,
    +          145,  189,  146,  161,  162,  163,  191,  164,  147,  148,
    +          149,  165,  150,  192,  130,  166,  167,  151,  168,  152,
    +          511,  422,  452,  813,  523,  677,  639,  503,  811,  271,
    +          641,  427,  427,  427,  640,  754,  453,  734,  427,  547,
    +            5,  416,  763,  758,  418,  421,  434,  454,  455,  457,
    +          467,  486,  440,  443,  427,  550,  474,  476,  497,  501,
    +          751,  506,  507,  765,  514,  750,  516,  522,  761,  524,
    +          317,  488,  307,  307,  305,  305,  252,  253,  276,  448,
    +          255,  316,  277,  320,  475,  404,  404,  404,  404,  404,
    +          404,  404,  404,  404,  404,  404,  404,  404,  404,  404,
    +          926,  249,  240,  427,  427,  441,  460,  427,  427,  664,
    +          427,  768,  768, 1005, 1005,  492,  415,  671,  502,  428,
    +          291,  224,  415,  225,  226,  449,  405,  405,  405,  405,
    +          405,  405,  405,  405,  405,  405,  405,  405,  405,  405,
    +          405,  664,  664,  294, 1015, 1015,  433,  521,  444,  450,
    +          464, 1016, 1016,  698, 1015,  469,  470,  517,  738,  927,
    +          364, 1016,  472,  272,  327,  785,  446,  293,    8, 1009,
    +          928,  981,  487, 1018,  306, 1002,  888,  781,  772,  278,
    +          992,  321,  300,  660,  303,  534,  658,  325,  919,  924,
    +          789,  657,  657,  665,  665,  665,  667,  358,  656,  668,
    +          466,  792,  742,  369,  829,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,  273,  274,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,  930,    0,  775,  775,  775,
    +          775,  930,  775,    0,  775,    0,    0,    0,    0,    0,
    +          821,    0,  989,    0,    0,    0,    0,    0,  989,    0,
    +            0,    0,    0,    0,    0,  732,  732,  732,  732,    0,
    +          727,  733,  500, 1000, 1000,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +          987,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,  791,    0,  791,    0,    0,    0,    0,
    +          993,  994
    +    );
    +
    +    protected $gotoCheck = array(
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           51,    8,    7,    7,    7,   10,   10,    7,    7,   63,
    +           12,    8,    8,    8,   11,   10,   77,   10,    8,   10,
    +           89,   10,   10,   10,   38,   38,   38,   38,   38,   38,
    +           35,   35,   28,    8,    8,   28,   28,   28,   28,   28,
    +           28,   28,   28,   28,   28,   28,   28,   28,   28,   28,
    +           45,   45,   45,   45,   45,   45,   45,   45,   45,   45,
    +           45,   45,   45,   45,   45,  110,  110,  110,  110,  110,
    +          110,  110,  110,  110,  110,  110,  110,  110,  110,  110,
    +           73,  109,  109,    8,    8,    8,    8,    8,    8,   19,
    +            8,   68,   68,   68,   68,   55,  106,   25,   55,    8,
    +           55,   59,  106,   59,   59,    8,  111,  111,  111,  111,
    +          111,  111,  111,  111,  111,  111,  111,  111,  111,  111,
    +          111,   19,   19,   52,  121,  121,   52,    5,   52,    2,
    +            2,  122,  122,   44,  121,   54,   54,   54,   29,   73,
    +           52,  122,   61,   61,   61,   75,  112,   41,   52,  120,
    +           73,   73,   43,  121,   42,  118,   93,   72,   70,   14,
    +          115,   18,    9,   21,   13,   65,   20,   17,   99,  101,
    +           76,   19,   19,   19,   19,   19,   19,   57,   19,   22,
    +           58,   78,   62,   97,   91,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   63,   63,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   51,   -1,   51,   51,   51,
    +           51,   51,   51,   -1,   51,   -1,   -1,   -1,   -1,   -1,
    +           89,   -1,   77,   -1,   -1,   -1,   -1,   -1,   77,   -1,
    +           -1,   -1,   -1,   -1,   -1,   51,   51,   51,   51,   -1,
    +           51,   51,   51,   77,   77,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           77,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   77,   -1,   77,   -1,   -1,   -1,   -1,
    +           77,   77
    +    );
    +
    +    protected $gotoBase = array(
    +            0,    0, -288,    0,    0,  227,    0,  109, -135,    9,
    +          114,  122,  118,   -4,   23,    0,    0,  -52,   14,  -47,
    +           -3,   15,  -64,  -20,    0,  200,    0,    0, -384,  232,
    +            0,    0,    0,    0,    0,  110,    0,    0,  100,    0,
    +            0,  225,   51,   53,  229,  -48,    0,    0,    0,    0,
    +            0,  106, -110,    0,   13, -161,    0,  -65,  -68, -335,
    +            0,   -8,  -67, -243,    0,  -15,    0,    0,   -7,    0,
    +           32,    0,   29,  -96,    0,  234,   -2,  123,  -63,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,  120,
    +            0,  -76,    0,   31,    0,    0,    0,  -74,    0,  -60,
    +            0,  -62,    0,    0,    0,    0,  -23,    0,    0,  -56,
    +          -33,    8,  233,    0,    0,   19,    0,    0,   54,    0,
    +          235,   -5,    2,    0
    +    );
    +
    +    protected $gotoDefault = array(
    +        -32768,  372,  555,    2,  556,  627,  635,  481,  392,  423,
    +          736,  678,  679,  296,  333,  393,  295,  322,  318,  666,
    +          659,  661,  669,  131,  323,  672,    1,  674,  429,  706,
    +          284,  682,  285,  496,  684,  436,  686,  687,  417,  297,
    +          298,  437,  304,  468,  697,  197,  301,  699,  283,  700,
    +          709,  286,  499,  478,  458,  491,  394,  355,  465,  223,
    +          445,  462,  741,  270,  749,  539,  757,  760,  395,  459,
    +          771,  360,  779,  944,  313,  784,  790,  976,  793,  796,
    +          340,  324,  471,  800,  801,    4,  805,  512,  513,  820,
    +          230,  828,  840,  338,  907,  909,  431,  366,  920,  352,
    +          326,  923,  980,  345,  396,  356,  936,  254,  275,  239,
    +          397,  241,  414, 1008,  398,  357,  983,  308, 1003,  347,
    +         1010, 1017,  268,  463
    +    );
    +
    +    protected $ruleToNonTerminal = array(
    +            0,    1,    3,    3,    2,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    6,    6,    6,    6,    6,    6,    6,
    +            7,    7,    8,    8,    9,    4,    4,    4,    4,    4,
    +            4,    4,    4,    4,    4,    4,   14,   14,   15,   15,
    +           15,   15,   17,   17,   13,   13,   18,   18,   19,   19,
    +           20,   20,   21,   21,   16,   16,   22,   24,   24,   25,
    +           26,   26,   28,   27,   27,   27,   27,   29,   29,   29,
    +           29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
    +           29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
    +           29,   29,   10,   10,   48,   48,   50,   49,   49,   42,
    +           42,   52,   52,   53,   53,   11,   12,   12,   12,   56,
    +           56,   56,   57,   57,   60,   60,   58,   58,   61,   61,
    +           36,   36,   44,   44,   47,   47,   47,   46,   46,   62,
    +           37,   37,   37,   37,   63,   63,   64,   64,   65,   65,
    +           34,   34,   30,   30,   66,   32,   32,   67,   31,   31,
    +           33,   33,   43,   43,   43,   54,   54,   69,   69,   70,
    +           70,   72,   72,   72,   71,   71,   55,   55,   73,   73,
    +           74,   74,   75,   75,   75,   39,   39,   76,   40,   40,
    +           78,   78,   59,   59,   79,   79,   79,   79,   84,   84,
    +           85,   85,   86,   86,   86,   86,   86,   87,   88,   88,
    +           83,   83,   80,   80,   82,   82,   90,   90,   89,   89,
    +           89,   89,   89,   89,   81,   81,   91,   91,   41,   41,
    +           35,   35,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
    +           23,   23,   23,   23,   98,   92,   92,   97,   97,  100,
    +          100,  101,  102,  102,  102,  106,  106,   51,   51,   51,
    +           93,   93,  104,  104,   94,   94,   96,   96,   96,   99,
    +           99,  110,  110,  111,  111,  111,   95,   95,   95,   95,
    +           95,   95,   95,   95,   95,   95,   95,   95,   95,   95,
    +           95,   95,  113,  113,   38,   38,  108,  108,  108,  103,
    +          103,  103,  114,  114,  114,  114,  114,  114,   45,   45,
    +           45,   77,   77,   77,  116,  107,  107,  107,  107,  107,
    +          107,  105,  105,  105,  115,  115,  115,   68,  117,  117,
    +          118,  118,  118,  112,  112,  119,  119,  120,  120,  120,
    +          120,  109,  109,  109,  109,  122,  121,  121,  121,  121,
    +          121,  121,  121,  123,  123,  123
    +    );
    +
    +    protected $ruleToLength = array(
    +            1,    1,    2,    0,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    3,    1,    1,    1,    1,    1,    3,
    +            5,    4,    3,    4,    2,    3,    1,    1,    7,    8,
    +            6,    7,    3,    1,    3,    1,    3,    1,    1,    3,
    +            1,    2,    1,    2,    3,    1,    3,    3,    1,    3,
    +            2,    0,    1,    1,    1,    1,    1,    3,    7,   10,
    +            5,    7,    9,    5,    3,    3,    3,    3,    3,    3,
    +            1,    2,    5,    7,    9,    5,    6,    3,    3,    2,
    +            2,    1,    1,    1,    0,    2,    8,    0,    4,    1,
    +            3,    0,    1,    0,    1,   10,    7,    6,    5,    1,
    +            2,    2,    0,    2,    0,    2,    0,    2,    1,    3,
    +            1,    4,    1,    4,    1,    1,    4,    1,    3,    3,
    +            3,    4,    4,    5,    0,    2,    4,    3,    1,    1,
    +            1,    4,    0,    2,    5,    0,    2,    6,    0,    2,
    +            0,    3,    1,    2,    1,    1,    0,    1,    3,    4,
    +            6,    1,    1,    1,    0,    1,    0,    2,    2,    3,
    +            1,    3,    1,    2,    2,    3,    1,    1,    3,    1,
    +            1,    3,    2,    0,    3,    3,    9,    3,    1,    3,
    +            0,    2,    4,    5,    4,    4,    4,    3,    1,    1,
    +            1,    3,    1,    1,    0,    1,    1,    2,    1,    1,
    +            1,    1,    1,    1,    1,    3,    1,    3,    3,    1,
    +            0,    1,    1,    3,    3,    4,    1,    2,    3,    3,
    +            3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
    +            2,    2,    2,    2,    3,    3,    3,    3,    3,    3,
    +            3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
    +            3,    2,    2,    2,    2,    3,    3,    3,    3,    3,
    +            3,    3,    3,    3,    3,    3,    5,    4,    3,    4,
    +            4,    2,    2,    4,    2,    2,    2,    2,    2,    2,
    +            2,    2,    2,    2,    2,    1,    3,    2,    1,    2,
    +            4,    2,   10,   11,    7,    3,    2,    0,    4,    1,
    +            3,    2,    2,    2,    4,    1,    1,    1,    2,    3,
    +            1,    1,    1,    1,    0,    3,    0,    1,    1,    0,
    +            1,    1,    3,    4,    3,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    1,    1,    3,    2,
    +            3,    3,    0,    1,    0,    1,    1,    3,    1,    1,
    +            3,    1,    1,    4,    4,    4,    1,    4,    1,    1,
    +            3,    1,    4,    2,    3,    1,    4,    4,    3,    3,
    +            3,    1,    3,    1,    1,    3,    1,    4,    3,    1,
    +            1,    1,    0,    0,    2,    3,    1,    3,    1,    4,
    +            2,    2,    2,    1,    2,    1,    1,    4,    3,    3,
    +            3,    6,    3,    1,    1,    1
    +    );
    +
    +    protected function reduceRule0() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule1() {
    +         $this->semValue = $this->handleNamespaces($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule2() {
    +         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    +    }
    +
    +    protected function reduceRule3() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule4() {
    +         $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $nop = null; };
    +            if ($nop !== null) { $this->semStack[$this->stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule5() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule6() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule7() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule8() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule9() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule10() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule11() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule12() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule13() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule14() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule15() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule16() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule17() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule18() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule19() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule20() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule21() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule22() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule23() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule24() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule25() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule26() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule27() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule28() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule29() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule30() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule31() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule32() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule33() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule34() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule35() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule36() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule37() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule38() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule39() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule40() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule41() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule42() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule43() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule44() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule45() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule46() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule47() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule48() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule49() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule50() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule51() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule52() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule53() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule54() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule55() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule56() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule57() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule58() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule59() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule60() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule61() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule62() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule63() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule64() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule65() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule66() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule67() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule68() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule69() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule70() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule71() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule72() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule73() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule74() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule75() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule76() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule77() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule78() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule79() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule80() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule81() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule82() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule83() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule84() {
    +         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule85() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule86() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule87() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule88() {
    +         $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule89() {
    +         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule90() {
    +         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule91() {
    +         $this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule92() {
    +         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule93() {
    +         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule94() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule95() {
    +         $this->semValue = new Stmt\Const_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule96() {
    +         $this->semValue = Stmt\Use_::TYPE_FUNCTION;
    +    }
    +
    +    protected function reduceRule97() {
    +         $this->semValue = Stmt\Use_::TYPE_CONSTANT;
    +    }
    +
    +    protected function reduceRule98() {
    +         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], $this->semStack[$this->stackPos-(7-2)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule99() {
    +         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(8-4)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(8-7)], $this->semStack[$this->stackPos-(8-2)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule100() {
    +         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule101() {
    +         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule102() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule103() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule104() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule105() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule106() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule107() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule108() {
    +         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule109() {
    +         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule110() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule111() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule112() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL;
    +    }
    +
    +    protected function reduceRule113() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)]; $this->semValue->type = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule114() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule115() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule116() {
    +         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule117() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule118() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule119() {
    +         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule120() {
    +         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    +    }
    +
    +    protected function reduceRule121() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule122() {
    +         $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $nop = null; };
    +            if ($nop !== null) { $this->semStack[$this->stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule123() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule124() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule125() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule126() {
    +         throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule127() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)]; $attrs = $this->startAttributeStack[$this->stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments']) && isset($stmts[0])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); };
    +    }
    +
    +    protected function reduceRule128() {
    +         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(7-3)], ['stmts' => is_array($this->semStack[$this->stackPos-(7-5)]) ? $this->semStack[$this->stackPos-(7-5)] : array($this->semStack[$this->stackPos-(7-5)]), 'elseifs' => $this->semStack[$this->stackPos-(7-6)], 'else' => $this->semStack[$this->stackPos-(7-7)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule129() {
    +         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(10-3)], ['stmts' => $this->semStack[$this->stackPos-(10-6)], 'elseifs' => $this->semStack[$this->stackPos-(10-7)], 'else' => $this->semStack[$this->stackPos-(10-8)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule130() {
    +         $this->semValue = new Stmt\While_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule131() {
    +         $this->semValue = new Stmt\Do_($this->semStack[$this->stackPos-(7-5)], is_array($this->semStack[$this->stackPos-(7-2)]) ? $this->semStack[$this->stackPos-(7-2)] : array($this->semStack[$this->stackPos-(7-2)]), $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule132() {
    +         $this->semValue = new Stmt\For_(['init' => $this->semStack[$this->stackPos-(9-3)], 'cond' => $this->semStack[$this->stackPos-(9-5)], 'loop' => $this->semStack[$this->stackPos-(9-7)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule133() {
    +         $this->semValue = new Stmt\Switch_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule134() {
    +         $this->semValue = new Stmt\Break_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule135() {
    +         $this->semValue = new Stmt\Continue_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule136() {
    +         $this->semValue = new Stmt\Return_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule137() {
    +         $this->semValue = new Stmt\Global_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule138() {
    +         $this->semValue = new Stmt\Static_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule139() {
    +         $this->semValue = new Stmt\Echo_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule140() {
    +         $this->semValue = new Stmt\InlineHTML($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule141() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule142() {
    +         $this->semValue = new Stmt\Unset_($this->semStack[$this->stackPos-(5-3)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule143() {
    +         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(7-3)], $this->semStack[$this->stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$this->stackPos-(7-5)][1], 'stmts' => $this->semStack[$this->stackPos-(7-7)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule144() {
    +         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(9-3)], $this->semStack[$this->stackPos-(9-7)][0], ['keyVar' => $this->semStack[$this->stackPos-(9-5)], 'byRef' => $this->semStack[$this->stackPos-(9-7)][1], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule145() {
    +         $this->semValue = new Stmt\Declare_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule146() {
    +         $this->semValue = new Stmt\TryCatch($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-5)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule147() {
    +         $this->semValue = new Stmt\Throw_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule148() {
    +         $this->semValue = new Stmt\Goto_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule149() {
    +         $this->semValue = new Stmt\Label($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule150() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule151() {
    +         $this->semValue = array(); /* means: no statement */
    +    }
    +
    +    protected function reduceRule152() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule153() {
    +         $startAttributes = $this->startAttributeStack[$this->stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $this->semValue = null; };
    +            if ($this->semValue === null) $this->semValue = array(); /* means: no statement */
    +    }
    +
    +    protected function reduceRule154() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule155() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule156() {
    +         $this->semValue = new Stmt\Catch_($this->semStack[$this->stackPos-(8-3)], substr($this->semStack[$this->stackPos-(8-4)], 1), $this->semStack[$this->stackPos-(8-7)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule157() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule158() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    +    }
    +
    +    protected function reduceRule159() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule160() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule161() {
    +         $this->semValue = false;
    +    }
    +
    +    protected function reduceRule162() {
    +         $this->semValue = true;
    +    }
    +
    +    protected function reduceRule163() {
    +         $this->semValue = false;
    +    }
    +
    +    protected function reduceRule164() {
    +         $this->semValue = true;
    +    }
    +
    +    protected function reduceRule165() {
    +         $this->semValue = new Stmt\Function_($this->semStack[$this->stackPos-(10-3)], ['byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-5)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule166() {
    +         $this->semValue = new Stmt\Class_($this->semStack[$this->stackPos-(7-2)], ['type' => $this->semStack[$this->stackPos-(7-1)], 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule167() {
    +         $this->semValue = new Stmt\Interface_($this->semStack[$this->stackPos-(6-2)], ['extends' => $this->semStack[$this->stackPos-(6-3)], 'stmts' => $this->semStack[$this->stackPos-(6-5)]], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule168() {
    +         $this->semValue = new Stmt\Trait_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule169() {
    +         $this->semValue = 0;
    +    }
    +
    +    protected function reduceRule170() {
    +         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    +    }
    +
    +    protected function reduceRule171() {
    +         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    +    }
    +
    +    protected function reduceRule172() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule173() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule174() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule175() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule176() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule177() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule178() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule179() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule180() {
    +         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule181() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule182() {
    +         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule183() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule184() {
    +         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule185() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule186() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule187() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule188() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule189() {
    +         $this->semValue = new Stmt\DeclareDeclare($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule190() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule191() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    +    }
    +
    +    protected function reduceRule192() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule193() {
    +         $this->semValue = $this->semStack[$this->stackPos-(5-3)];
    +    }
    +
    +    protected function reduceRule194() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule195() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule196() {
    +         $this->semValue = new Stmt\Case_($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule197() {
    +         $this->semValue = new Stmt\Case_(null, $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule198() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule199() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule200() {
    +         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule201() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    +    }
    +
    +    protected function reduceRule202() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule203() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule204() {
    +         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(5-3)], is_array($this->semStack[$this->stackPos-(5-5)]) ? $this->semStack[$this->stackPos-(5-5)] : array($this->semStack[$this->stackPos-(5-5)]), $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule205() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule206() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule207() {
    +         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule208() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule209() {
    +         $this->semValue = new Stmt\Else_(is_array($this->semStack[$this->stackPos-(2-2)]) ? $this->semStack[$this->stackPos-(2-2)] : array($this->semStack[$this->stackPos-(2-2)]), $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule210() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule211() {
    +         $this->semValue = new Stmt\Else_($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule212() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    +    }
    +
    +    protected function reduceRule213() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(2-2)], true);
    +    }
    +
    +    protected function reduceRule214() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    +    }
    +
    +    protected function reduceRule215() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule216() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule217() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule218() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule219() {
    +         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(4-4)], 1), null, $this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule220() {
    +         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(6-4)], 1), $this->semStack[$this->stackPos-(6-6)], $this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-3)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule221() {
    +         $this->semValue = $this->handleScalarTypes($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule222() {
    +         $this->semValue = 'array';
    +    }
    +
    +    protected function reduceRule223() {
    +         $this->semValue = 'callable';
    +    }
    +
    +    protected function reduceRule224() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule225() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule226() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule227() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule228() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule229() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule230() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule231() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule232() {
    +         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(1-1)], false, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule233() {
    +         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], true, false, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule234() {
    +         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], false, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule235() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule236() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule237() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule238() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule239() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule240() {
    +         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule241() {
    +         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule242() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule243() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule244() {
    +         $this->semValue = new Stmt\Property($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule245() {
    +         $this->semValue = new Stmt\ClassConst($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule246() {
    +         $this->semValue = new Stmt\ClassMethod($this->semStack[$this->stackPos-(9-4)], ['type' => $this->semStack[$this->stackPos-(9-1)], 'byRef' => $this->semStack[$this->stackPos-(9-3)], 'params' => $this->semStack[$this->stackPos-(9-6)], 'returnType' => $this->semStack[$this->stackPos-(9-8)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule247() {
    +         $this->semValue = new Stmt\TraitUse($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule248() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule249() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule250() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule251() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule252() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule253() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(5-1)][0], $this->semStack[$this->stackPos-(5-1)][1], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule254() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], null, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule255() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule256() {
    +         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule257() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)]);
    +    }
    +
    +    protected function reduceRule258() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule259() {
    +         $this->semValue = array(null, $this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule260() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule261() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule262() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule263() {
    +         $this->semValue = 0;
    +    }
    +
    +    protected function reduceRule264() {
    +         $this->semValue = 0;
    +    }
    +
    +    protected function reduceRule265() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule266() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule267() {
    +         Stmt\Class_::verifyModifier($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); $this->semValue = $this->semStack[$this->stackPos-(2-1)] | $this->semStack[$this->stackPos-(2-2)];
    +    }
    +
    +    protected function reduceRule268() {
    +         $this->semValue = Stmt\Class_::MODIFIER_PUBLIC;
    +    }
    +
    +    protected function reduceRule269() {
    +         $this->semValue = Stmt\Class_::MODIFIER_PROTECTED;
    +    }
    +
    +    protected function reduceRule270() {
    +         $this->semValue = Stmt\Class_::MODIFIER_PRIVATE;
    +    }
    +
    +    protected function reduceRule271() {
    +         $this->semValue = Stmt\Class_::MODIFIER_STATIC;
    +    }
    +
    +    protected function reduceRule272() {
    +         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    +    }
    +
    +    protected function reduceRule273() {
    +         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    +    }
    +
    +    protected function reduceRule274() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule275() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule276() {
    +         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule277() {
    +         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule278() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule279() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule280() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule281() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule282() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule283() {
    +         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule284() {
    +         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule285() {
    +         $this->semValue = new Expr\AssignRef($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule286() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule287() {
    +         $this->semValue = new Expr\Clone_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule288() {
    +         $this->semValue = new Expr\AssignOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule289() {
    +         $this->semValue = new Expr\AssignOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule290() {
    +         $this->semValue = new Expr\AssignOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule291() {
    +         $this->semValue = new Expr\AssignOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule292() {
    +         $this->semValue = new Expr\AssignOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule293() {
    +         $this->semValue = new Expr\AssignOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule294() {
    +         $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule295() {
    +         $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule296() {
    +         $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule297() {
    +         $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule298() {
    +         $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule299() {
    +         $this->semValue = new Expr\AssignOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule300() {
    +         $this->semValue = new Expr\PostInc($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule301() {
    +         $this->semValue = new Expr\PreInc($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule302() {
    +         $this->semValue = new Expr\PostDec($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule303() {
    +         $this->semValue = new Expr\PreDec($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule304() {
    +         $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule305() {
    +         $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule306() {
    +         $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule307() {
    +         $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule308() {
    +         $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule309() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule310() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule311() {
    +         $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule312() {
    +         $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule313() {
    +         $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule314() {
    +         $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule315() {
    +         $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule316() {
    +         $this->semValue = new Expr\BinaryOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule317() {
    +         $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule318() {
    +         $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule319() {
    +         $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule320() {
    +         $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule321() {
    +         $this->semValue = new Expr\UnaryPlus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule322() {
    +         $this->semValue = new Expr\UnaryMinus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule323() {
    +         $this->semValue = new Expr\BooleanNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule324() {
    +         $this->semValue = new Expr\BitwiseNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule325() {
    +         $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule326() {
    +         $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule327() {
    +         $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule328() {
    +         $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule329() {
    +         $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule330() {
    +         $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule331() {
    +         $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule332() {
    +         $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule333() {
    +         $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule334() {
    +         $this->semValue = new Expr\Instanceof_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule335() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule336() {
    +         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(5-1)], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule337() {
    +         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(4-1)], null, $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule338() {
    +         $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule339() {
    +         $this->semValue = new Expr\Isset_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule340() {
    +         $this->semValue = new Expr\Empty_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule341() {
    +         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule342() {
    +         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule343() {
    +         $this->semValue = new Expr\Eval_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule344() {
    +         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule345() {
    +         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule346() {
    +         $this->semValue = new Expr\Cast\Int_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule347() {
    +         $this->semValue = new Expr\Cast\Double($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule348() {
    +         $this->semValue = new Expr\Cast\String_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule349() {
    +         $this->semValue = new Expr\Cast\Array_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule350() {
    +         $this->semValue = new Expr\Cast\Object_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule351() {
    +         $this->semValue = new Expr\Cast\Bool_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule352() {
    +         $this->semValue = new Expr\Cast\Unset_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule353() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes;
    +            $attrs['kind'] = strtolower($this->semStack[$this->stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
    +            $this->semValue = new Expr\Exit_($this->semStack[$this->stackPos-(2-2)], $attrs);
    +    }
    +
    +    protected function reduceRule354() {
    +         $this->semValue = new Expr\ErrorSuppress($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule355() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule356() {
    +         $this->semValue = new Expr\ShellExec($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule357() {
    +         $this->semValue = new Expr\Print_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule358() {
    +         $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule359() {
    +         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(2-2)], null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule360() {
    +         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule361() {
    +         $this->semValue = new Expr\YieldFrom($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule362() {
    +         $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-4)], 'uses' => $this->semStack[$this->stackPos-(10-6)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule363() {
    +         $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$this->stackPos-(11-3)], 'params' => $this->semStack[$this->stackPos-(11-5)], 'uses' => $this->semStack[$this->stackPos-(11-7)], 'returnType' => $this->semStack[$this->stackPos-(11-8)], 'stmts' => $this->semStack[$this->stackPos-(11-10)]], $this->startAttributeStack[$this->stackPos-(11-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule364() {
    +         $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-2)]);
    +    }
    +
    +    protected function reduceRule365() {
    +         $this->semValue = new Expr\New_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule366() {
    +         list($class, $ctorArgs) = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule367() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule368() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    +    }
    +
    +    protected function reduceRule369() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule370() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule371() {
    +         $this->semValue = new Expr\ClosureUse(substr($this->semStack[$this->stackPos-(2-2)], 1), $this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule372() {
    +         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule373() {
    +         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule374() {
    +         $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule375() {
    +         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule376() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule377() {
    +         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule378() {
    +         $this->semValue = new Name\FullyQualified($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule379() {
    +         $this->semValue = new Name\Relative($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule380() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule381() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule382() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule383() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule384() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule385() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule386() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule387() {
    +         $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$this->stackPos-(1-1)], '`'), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes));
    +    }
    +
    +    protected function reduceRule388() {
    +         foreach ($this->semStack[$this->stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', true); } }; $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule389() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule390() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule391() {
    +         $this->semValue = new Expr\ConstFetch($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule392() {
    +         $this->semValue = new Expr\ClassConstFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule393() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG;
    +            $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(4-3)], $attrs);
    +    }
    +
    +    protected function reduceRule394() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT;
    +            $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(3-2)], $attrs);
    +    }
    +
    +    protected function reduceRule395() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$this->stackPos-(1-1)][0] === "'" || ($this->semStack[$this->stackPos-(1-1)][1] === "'" && ($this->semStack[$this->stackPos-(1-1)][0] === 'b' || $this->semStack[$this->stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
    +            $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$this->stackPos-(1-1)]), $attrs);
    +    }
    +
    +    protected function reduceRule396() {
    +         $this->semValue = Scalar\LNumber::fromString($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule397() {
    +         $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$this->stackPos-(1-1)]), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule398() {
    +         $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule399() {
    +         $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule400() {
    +         $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule401() {
    +         $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule402() {
    +         $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule403() {
    +         $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule404() {
    +         $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule405() {
    +         $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule406() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule407() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule408() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
    +            $this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)]), $attrs);
    +    }
    +
    +    protected function reduceRule409() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(2-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(2-1)], $matches); $attrs['docLabel'] = $matches[1];;
    +            $this->semValue = new Scalar\String_('', $attrs);
    +    }
    +
    +    protected function reduceRule410() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
    +            foreach ($this->semStack[$this->stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $attrs);
    +    }
    +
    +    protected function reduceRule411() {
    +         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
    +            foreach ($this->semStack[$this->stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, true); } } $s->value = preg_replace('~(\r\n|\n|\r)\z~', '', $s->value); if ('' === $s->value) array_pop($this->semStack[$this->stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $attrs);
    +    }
    +
    +    protected function reduceRule412() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule413() {
    +        $this->semValue = $this->semStack[$this->stackPos];
    +    }
    +
    +    protected function reduceRule414() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule415() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule416() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule417() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule418() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule419() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule420() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule421() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule422() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule423() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule424() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule425() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule426() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule427() {
    +         $this->semValue = new Expr\MethodCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule428() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule429() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule430() {
    +         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule431() {
    +         $this->semValue = substr($this->semStack[$this->stackPos-(1-1)], 1);
    +    }
    +
    +    protected function reduceRule432() {
    +         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    +    }
    +
    +    protected function reduceRule433() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule434() {
    +         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule435() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule436() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule437() {
    +         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule438() {
    +         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule439() {
    +         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule440() {
    +         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule441() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule442() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule443() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule444() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule445() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule446() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule447() {
    +         $this->semValue = new Expr\List_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule448() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule449() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule450() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule451() {
    +         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    +    }
    +
    +    protected function reduceRule452() {
    +         $this->semValue = null;
    +    }
    +
    +    protected function reduceRule453() {
    +         $this->semValue = array();
    +    }
    +
    +    protected function reduceRule454() {
    +         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule455() {
    +         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    +    }
    +
    +    protected function reduceRule456() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule457() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(3-3)], $this->semStack[$this->stackPos-(3-1)], false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule458() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule459() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-1)], true, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule460() {
    +         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(2-2)], null, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule461() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule462() {
    +         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    +    }
    +
    +    protected function reduceRule463() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    +    }
    +
    +    protected function reduceRule464() {
    +         $this->semValue = array($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]);
    +    }
    +
    +    protected function reduceRule465() {
    +         $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule466() {
    +         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule467() {
    +         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(4-1)], 1), $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule468() {
    +         $this->semValue = new Expr\PropertyFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule469() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule470() {
    +         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule471() {
    +         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-4)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule472() {
    +         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    +    }
    +
    +    protected function reduceRule473() {
    +         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule474() {
    +         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +
    +    protected function reduceRule475() {
    +         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php b/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php
    new file mode 100644
    index 0000000..861663f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php
    @@ -0,0 +1,144 @@
    +lexer = $lexer;
    +        $this->errors = array();
    +        $this->throwOnError = isset($options['throwOnError']) ? $options['throwOnError'] : true;
    +    }
    +
    +    /**
    +     * Get array of errors that occurred during the last parse.
    +     *
    +     * This method may only return multiple errors if the 'throwOnError' option is disabled.
    +     *
    +     * @return Error[]
    +     */
    +    public function getErrors() {
    +        return $this->errors;
    +    }
    +
    +    /**
    +     * Parses PHP code into a node tree.
    +     *
    +     * @param string $code The source code to parse
    +     *
    +     * @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
    +     *                     unable to recover from an error).
    +     */
    +    public function parse($code) {
    +        $this->lexer->startLexing($code);
    +        $this->errors = array();
    +
    +        // We start off with no lookahead-token
    +        $symbol = self::SYMBOL_NONE;
    +
    +        // The attributes for a node are taken from the first and last token of the node.
    +        // From the first token only the startAttributes are taken and from the last only
    +        // the endAttributes. Both are merged using the array union operator (+).
    +        $startAttributes = '*POISON';
    +        $endAttributes = '*POISON';
    +        $this->endAttributes = $endAttributes;
    +
    +        // In order to figure out the attributes for the starting token, we have to keep
    +        // them in a stack
    +        $this->startAttributeStack = array();
    +
    +        // Start off in the initial state and keep a stack of previous states
    +        $state = 0;
    +        $stateStack = array($state);
    +
    +        // Semantic value stack (contains values of tokens and semantic action results)
    +        $this->semStack = array();
    +
    +        // Current position in the stack(s)
    +        $this->stackPos = 0;
    +
    +        $errorState = 0;
    +
    +        for (;;) {
    +            //$this->traceNewState($state, $symbol);
    +
    +            if ($this->actionBase[$state] == 0) {
    +                $rule = $this->actionDefault[$state];
    +            } else {
    +                if ($symbol === self::SYMBOL_NONE) {
    +                    // Fetch the next token id from the lexer and fetch additional info by-ref.
    +                    // The end attributes are fetched into a temporary variable and only set once the token is really
    +                    // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
    +                    // reduced after a token was read but not yet shifted.
    +                    $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);
    +
    +                    // map the lexer token id to the internally used symbols
    +                    $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
    +                        ? $this->tokenToSymbol[$tokenId]
    +                        : $this->invalidSymbol;
    +
    +                    if ($symbol === $this->invalidSymbol) {
    +                        throw new \RangeException(sprintf(
    +                            'The lexer returned an invalid token (id=%d, value=%s)',
    +                            $tokenId, $tokenValue
    +                        ));
    +                    }
    +
    +                    // This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
    +                    // the attributes of the next token, even though they don't contain it themselves.
    +                    $this->startAttributeStack[$this->stackPos+1] = $startAttributes;
    +                    $this->lookaheadStartAttributes = $startAttributes;
    +
    +                    //$this->traceRead($symbol);
    +                }
    +
    +                $idx = $this->actionBase[$state] + $symbol;
    +                if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol)
    +                     || ($state < $this->YY2TBLSTATE
    +                         && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
    +                         && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol))
    +                    && ($action = $this->action[$idx]) != $this->defaultAction) {
    +                    /*
    +                     * >= YYNLSTATES: shift and reduce
    +                     * > 0: shift
    +                     * = 0: accept
    +                     * < 0: reduce
    +                     * = -YYUNEXPECTED: error
    +                     */
    +                    if ($action > 0) {
    +                        /* shift */
    +                        //$this->traceShift($symbol);
    +
    +                        ++$this->stackPos;
    +                        $stateStack[$this->stackPos] = $state = $action;
    +                        $this->semStack[$this->stackPos] = $tokenValue;
    +                        $this->startAttributeStack[$this->stackPos] = $startAttributes;
    +                        $this->endAttributes = $endAttributes;
    +                        $symbol = self::SYMBOL_NONE;
    +
    +                        if ($errorState) {
    +                            --$errorState;
    +                        }
    +
    +                        if ($action < $this->YYNLSTATES) {
    +                            continue;
    +                        }
    +
    +                        /* $yyn >= YYNLSTATES means shift-and-reduce */
    +                        $rule = $action - $this->YYNLSTATES;
    +                    } else {
    +                        $rule = -$action;
    +                    }
    +                } else {
    +                    $rule = $this->actionDefault[$state];
    +                }
    +            }
    +
    +            for (;;) {
    +                if ($rule === 0) {
    +                    /* accept */
    +                    //$this->traceAccept();
    +                    return $this->semValue;
    +                } elseif ($rule !== $this->unexpectedTokenRule) {
    +                    /* reduce */
    +                    //$this->traceReduce($rule);
    +
    +                    try {
    +                        $this->{'reduceRule' . $rule}();
    +                    } catch (Error $e) {
    +                        if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
    +                            $e->setStartLine($startAttributes['startLine']);
    +                        }
    +
    +                        $this->errors[] = $e;
    +                        if ($this->throwOnError) {
    +                            throw $e;
    +                        } else {
    +                            // Currently can't recover from "special" errors
    +                            return null;
    +                        }
    +                    }
    +
    +                    /* Goto - shift nonterminal */
    +                    $this->stackPos -= $this->ruleToLength[$rule];
    +                    $nonTerminal = $this->ruleToNonTerminal[$rule];
    +                    $idx = $this->gotoBase[$nonTerminal] + $stateStack[$this->stackPos];
    +                    if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] == $nonTerminal) {
    +                        $state = $this->goto[$idx];
    +                    } else {
    +                        $state = $this->gotoDefault[$nonTerminal];
    +                    }
    +
    +                    ++$this->stackPos;
    +                    $stateStack[$this->stackPos]     = $state;
    +                    $this->semStack[$this->stackPos] = $this->semValue;
    +                } else {
    +                    /* error */
    +                    switch ($errorState) {
    +                        case 0:
    +                            $msg = $this->getErrorMessage($symbol, $state);
    +                            $error = new Error($msg, $startAttributes + $endAttributes);
    +                            $this->errors[] = $error;
    +                            if ($this->throwOnError) {
    +                                throw $error;
    +                            }
    +                            // Break missing intentionally
    +                        case 1:
    +                        case 2:
    +                            $errorState = 3;
    +
    +                            // Pop until error-expecting state uncovered
    +                            while (!(
    +                                (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
    +                                    && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
    +                                || ($state < $this->YY2TBLSTATE
    +                                    && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $this->errorSymbol) >= 0
    +                                    && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
    +                            ) || ($action = $this->action[$idx]) == $this->defaultAction) { // Not totally sure about this
    +                                if ($this->stackPos <= 0) {
    +                                    // Could not recover from error
    +                                    return null;
    +                                }
    +                                $state = $stateStack[--$this->stackPos];
    +                                //$this->tracePop($state);
    +                            }
    +
    +                            //$this->traceShift($this->errorSymbol);
    +                            $stateStack[++$this->stackPos] = $state = $action;
    +                            break;
    +
    +                        case 3:
    +                            if ($symbol === 0) {
    +                                // Reached EOF without recovering from error
    +                                return null;
    +                            }
    +
    +                            //$this->traceDiscard($symbol);
    +                            $symbol = self::SYMBOL_NONE;
    +                            break 2;
    +                    }
    +                }
    +
    +                if ($state < $this->YYNLSTATES) {
    +                    break;
    +                }
    +
    +                /* >= YYNLSTATES means shift-and-reduce */
    +                $rule = $state - $this->YYNLSTATES;
    +            }
    +        }
    +
    +        throw new \RuntimeException('Reached end of parser loop');
    +    }
    +
    +    protected function getErrorMessage($symbol, $state) {
    +        $expectedString = '';
    +        if ($expected = $this->getExpectedTokens($state)) {
    +            $expectedString = ', expecting ' . implode(' or ', $expected);
    +        }
    +
    +        return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
    +    }
    +
    +    protected function getExpectedTokens($state) {
    +        $expected = array();
    +
    +        $base = $this->actionBase[$state];
    +        foreach ($this->symbolToName as $symbol => $name) {
    +            $idx = $base + $symbol;
    +            if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
    +                || $state < $this->YY2TBLSTATE
    +                && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
    +                && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
    +            ) {
    +                if ($this->action[$idx] != $this->unexpectedTokenRule
    +                    && $this->action[$idx] != $this->defaultAction
    +                ) {
    +                    if (count($expected) == 4) {
    +                        /* Too many expected tokens */
    +                        return array();
    +                    }
    +
    +                    $expected[] = $name;
    +                }
    +            }
    +        }
    +
    +        return $expected;
    +    }
    +
    +    /*
    +     * Tracing functions used for debugging the parser.
    +     */
    +
    +    /*
    +    protected function traceNewState($state, $symbol) {
    +        echo '% State ' . $state
    +            . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
    +    }
    +
    +    protected function traceRead($symbol) {
    +        echo '% Reading ' . $this->symbolToName[$symbol] . "\n";
    +    }
    +
    +    protected function traceShift($symbol) {
    +        echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
    +    }
    +
    +    protected function traceAccept() {
    +        echo "% Accepted.\n";
    +    }
    +
    +    protected function traceReduce($n) {
    +        echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
    +    }
    +
    +    protected function tracePop($state) {
    +        echo '% Recovering, uncovered state ' . $state . "\n";
    +    }
    +
    +    protected function traceDiscard($symbol) {
    +        echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
    +    }
    +    */
    +
    +    /*
    +     * Helper functions invoked by semantic actions
    +     */
    +
    +    /**
    +     * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions.
    +     *
    +     * @param Node[] $stmts
    +     * @return Node[]
    +     */
    +    protected function handleNamespaces(array $stmts) {
    +        $style = $this->getNamespacingStyle($stmts);
    +        if (null === $style) {
    +            // not namespaced, nothing to do
    +            return $stmts;
    +        } elseif ('brace' === $style) {
    +            // For braced namespaces we only have to check that there are no invalid statements between the namespaces
    +            $afterFirstNamespace = false;
    +            foreach ($stmts as $stmt) {
    +                if ($stmt instanceof Node\Stmt\Namespace_) {
    +                    $afterFirstNamespace = true;
    +                } elseif (!$stmt instanceof Node\Stmt\HaltCompiler && $afterFirstNamespace) {
    +                    throw new Error('No code may exist outside of namespace {}', $stmt->getLine());
    +                }
    +            }
    +            return $stmts;
    +        } else {
    +            // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
    +            $resultStmts = array();
    +            $targetStmts =& $resultStmts;
    +            foreach ($stmts as $stmt) {
    +                if ($stmt instanceof Node\Stmt\Namespace_) {
    +                    $stmt->stmts = array();
    +                    $targetStmts =& $stmt->stmts;
    +                    $resultStmts[] = $stmt;
    +                } elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
    +                    // __halt_compiler() is not moved into the namespace
    +                    $resultStmts[] = $stmt;
    +                } else {
    +                    $targetStmts[] = $stmt;
    +                }
    +            }
    +            return $resultStmts;
    +        }
    +    }
    +
    +    private function getNamespacingStyle(array $stmts) {
    +        $style = null;
    +        $hasNotAllowedStmts = false;
    +        foreach ($stmts as $i => $stmt) {
    +            if ($stmt instanceof Node\Stmt\Namespace_) {
    +                $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace';
    +                if (null === $style) {
    +                    $style = $currentStyle;
    +                    if ($hasNotAllowedStmts) {
    +                        throw new Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine());
    +                    }
    +                } elseif ($style !== $currentStyle) {
    +                    throw new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine());
    +                }
    +                continue;
    +            }
    +
    +            /* declare(), __halt_compiler() and nops can be used before a namespace declaration */
    +            if ($stmt instanceof Node\Stmt\Declare_
    +                || $stmt instanceof Node\Stmt\HaltCompiler
    +                || $stmt instanceof Node\Stmt\Nop) {
    +                continue;
    +            }
    +
    +            /* There may be a hashbang line at the very start of the file */
    +            if ($i == 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) {
    +                continue;
    +            }
    +
    +            /* Everything else if forbidden before namespace declarations */
    +            $hasNotAllowedStmts = true;
    +        }
    +        return $style;
    +    }
    +
    +    protected function handleScalarTypes(Name $name) {
    +        $scalarTypes = [
    +            'bool'   => true,
    +            'int'    => true,
    +            'float'  => true,
    +            'string' => true,
    +        ];
    +
    +        if (!$name->isUnqualified()) {
    +            return $name;
    +        }
    +
    +        $lowerName = strtolower($name->toString());
    +        return isset($scalarTypes[$lowerName]) ? $lowerName : $name;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php b/core/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php
    new file mode 100644
    index 0000000..28b9070
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php
    @@ -0,0 +1,43 @@
    +type ? $this->pType($node->type) . ' ' : '')
    +             . ($node->byRef ? '&' : '')
    +             . ($node->variadic ? '...' : '')
    +             . '$' . $node->name
    +             . ($node->default ? ' = ' . $this->p($node->default) : '');
    +    }
    +
    +    public function pArg(Node\Arg $node) {
    +        return ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') . $this->p($node->value);
    +    }
    +
    +    public function pConst(Node\Const_ $node) {
    +        return $node->name . ' = ' . $this->p($node->value);
    +    }
    +
    +    // Names
    +
    +    public function pName(Name $node) {
    +        return implode('\\', $node->parts);
    +    }
    +
    +    public function pName_FullyQualified(Name\FullyQualified $node) {
    +        return '\\' . implode('\\', $node->parts);
    +    }
    +
    +    public function pName_Relative(Name\Relative $node) {
    +        return 'namespace\\' . implode('\\', $node->parts);
    +    }
    +
    +    // Magic Constants
    +
    +    public function pScalar_MagicConst_Class(MagicConst\Class_ $node) {
    +        return '__CLASS__';
    +    }
    +
    +    public function pScalar_MagicConst_Dir(MagicConst\Dir $node) {
    +        return '__DIR__';
    +    }
    +
    +    public function pScalar_MagicConst_File(MagicConst\File $node) {
    +        return '__FILE__';
    +    }
    +
    +    public function pScalar_MagicConst_Function(MagicConst\Function_ $node) {
    +        return '__FUNCTION__';
    +    }
    +
    +    public function pScalar_MagicConst_Line(MagicConst\Line $node) {
    +        return '__LINE__';
    +    }
    +
    +    public function pScalar_MagicConst_Method(MagicConst\Method $node) {
    +        return '__METHOD__';
    +    }
    +
    +    public function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node) {
    +        return '__NAMESPACE__';
    +    }
    +
    +    public function pScalar_MagicConst_Trait(MagicConst\Trait_ $node) {
    +        return '__TRAIT__';
    +    }
    +
    +    // Scalars
    +
    +    public function pScalar_String(Scalar\String_ $node) {
    +        $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED);
    +        switch ($kind) {
    +            case Scalar\String_::KIND_NOWDOC:
    +                $label = $node->getAttribute('docLabel');
    +                if ($label && !$this->containsEndLabel($node->value, $label)) {
    +                    if ($node->value === '') {
    +                        return $this->pNoIndent("<<<'$label'\n$label") . $this->docStringEndToken;
    +                    }
    +
    +                    return $this->pNoIndent("<<<'$label'\n$node->value\n$label")
    +                         . $this->docStringEndToken;
    +                }
    +                /* break missing intentionally */
    +            case Scalar\String_::KIND_SINGLE_QUOTED:
    +                return '\'' . $this->pNoIndent(addcslashes($node->value, '\'\\')) . '\'';
    +            case Scalar\String_::KIND_HEREDOC:
    +                $label = $node->getAttribute('docLabel');
    +                if ($label && !$this->containsEndLabel($node->value, $label)) {
    +                    if ($node->value === '') {
    +                        return $this->pNoIndent("<<<$label\n$label") . $this->docStringEndToken;
    +                    }
    +
    +                    $escaped = $this->escapeString($node->value, null);
    +                    return $this->pNoIndent("<<<$label\n" . $escaped ."\n$label")
    +                         . $this->docStringEndToken;
    +                }
    +            /* break missing intentionally */
    +            case Scalar\String_::KIND_DOUBLE_QUOTED:
    +                return '"' . $this->escapeString($node->value, '"') . '"';
    +        }
    +        throw new \Exception('Invalid string kind');
    +    }
    +
    +    public function pScalar_Encapsed(Scalar\Encapsed $node) {
    +        if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) {
    +            $label = $node->getAttribute('docLabel');
    +            if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) {
    +                if (count($node->parts) === 1
    +                    && $node->parts[0] instanceof Scalar\EncapsedStringPart
    +                    && $node->parts[0]->value === ''
    +                ) {
    +                    return $this->pNoIndent("<<<$label\n$label") . $this->docStringEndToken;
    +                }
    +
    +                return $this->pNoIndent(
    +                    "<<<$label\n" . $this->pEncapsList($node->parts, null) . "\n$label"
    +                ) . $this->docStringEndToken;
    +            }
    +        }
    +        return '"' . $this->pEncapsList($node->parts, '"') . '"';
    +    }
    +
    +    public function pScalar_LNumber(Scalar\LNumber $node) {
    +        $str = (string) $node->value;
    +        switch ($node->getAttribute('kind', Scalar\LNumber::KIND_DEC)) {
    +            case Scalar\LNumber::KIND_BIN:
    +                return '0b' . base_convert($str, 10, 2);
    +            case Scalar\LNumber::KIND_OCT:
    +                return '0' . base_convert($str, 10, 8);
    +            case Scalar\LNumber::KIND_DEC:
    +                return $str;
    +            case Scalar\LNumber::KIND_HEX:
    +                return '0x' . base_convert($str, 10, 16);
    +        }
    +        throw new \Exception('Invalid number kind');
    +    }
    +
    +    public function pScalar_DNumber(Scalar\DNumber $node) {
    +        $stringValue = sprintf('%.16G', $node->value);
    +        if ($node->value !== (double) $stringValue) {
    +            $stringValue = sprintf('%.17G', $node->value);
    +        }
    +
    +        // ensure that number is really printed as float
    +        return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue;
    +    }
    +
    +    // Assignments
    +
    +    public function pExpr_Assign(Expr\Assign $node) {
    +        return $this->pInfixOp('Expr_Assign', $node->var, ' = ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignRef(Expr\AssignRef $node) {
    +        return $this->pInfixOp('Expr_AssignRef', $node->var, ' =& ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_Plus(AssignOp\Plus $node) {
    +        return $this->pInfixOp('Expr_AssignOp_Plus', $node->var, ' += ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_Minus(AssignOp\Minus $node) {
    +        return $this->pInfixOp('Expr_AssignOp_Minus', $node->var, ' -= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_Mul(AssignOp\Mul $node) {
    +        return $this->pInfixOp('Expr_AssignOp_Mul', $node->var, ' *= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_Div(AssignOp\Div $node) {
    +        return $this->pInfixOp('Expr_AssignOp_Div', $node->var, ' /= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_Concat(AssignOp\Concat $node) {
    +        return $this->pInfixOp('Expr_AssignOp_Concat', $node->var, ' .= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_Mod(AssignOp\Mod $node) {
    +        return $this->pInfixOp('Expr_AssignOp_Mod', $node->var, ' %= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) {
    +        return $this->pInfixOp('Expr_AssignOp_BitwiseAnd', $node->var, ' &= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) {
    +        return $this->pInfixOp('Expr_AssignOp_BitwiseOr', $node->var, ' |= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) {
    +        return $this->pInfixOp('Expr_AssignOp_BitwiseXor', $node->var, ' ^= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) {
    +        return $this->pInfixOp('Expr_AssignOp_ShiftLeft', $node->var, ' <<= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) {
    +        return $this->pInfixOp('Expr_AssignOp_ShiftRight', $node->var, ' >>= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignOp_Pow(AssignOp\Pow $node) {
    +        return $this->pInfixOp('Expr_AssignOp_Pow', $node->var, ' **= ', $node->expr);
    +    }
    +
    +    // Binary expressions
    +
    +    public function pExpr_BinaryOp_Plus(BinaryOp\Plus $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Plus', $node->left, ' + ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Minus(BinaryOp\Minus $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Minus', $node->left, ' - ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Mul(BinaryOp\Mul $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Mul', $node->left, ' * ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Div(BinaryOp\Div $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Div', $node->left, ' / ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Concat', $node->left, ' . ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Mod(BinaryOp\Mod $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Mod', $node->left, ' % ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_BooleanAnd', $node->left, ' && ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_BooleanOr', $node->left, ' || ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_BitwiseAnd', $node->left, ' & ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_BitwiseOr', $node->left, ' | ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_BitwiseXor', $node->left, ' ^ ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_ShiftLeft', $node->left, ' << ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_ShiftRight', $node->left, ' >> ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Pow(BinaryOp\Pow $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Pow', $node->left, ' ** ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_LogicalAnd', $node->left, ' and ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_LogicalOr', $node->left, ' or ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_LogicalXor', $node->left, ' xor ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Equal(BinaryOp\Equal $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Equal', $node->left, ' == ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_NotEqual', $node->left, ' != ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Identical(BinaryOp\Identical $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Identical', $node->left, ' === ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_NotIdentical', $node->left, ' !== ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Spaceship', $node->left, ' <=> ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Greater(BinaryOp\Greater $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Greater', $node->left, ' > ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_GreaterOrEqual', $node->left, ' >= ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Smaller', $node->left, ' < ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_SmallerOrEqual', $node->left, ' <= ', $node->right);
    +    }
    +
    +    public function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) {
    +        return $this->pInfixOp('Expr_BinaryOp_Coalesce', $node->left, ' ?? ', $node->right);
    +    }
    +
    +    public function pExpr_Instanceof(Expr\Instanceof_ $node) {
    +        return $this->pInfixOp('Expr_Instanceof', $node->expr, ' instanceof ', $node->class);
    +    }
    +
    +    // Unary expressions
    +
    +    public function pExpr_BooleanNot(Expr\BooleanNot $node) {
    +        return $this->pPrefixOp('Expr_BooleanNot', '!', $node->expr);
    +    }
    +
    +    public function pExpr_BitwiseNot(Expr\BitwiseNot $node) {
    +        return $this->pPrefixOp('Expr_BitwiseNot', '~', $node->expr);
    +    }
    +
    +    public function pExpr_UnaryMinus(Expr\UnaryMinus $node) {
    +        return $this->pPrefixOp('Expr_UnaryMinus', '-', $node->expr);
    +    }
    +
    +    public function pExpr_UnaryPlus(Expr\UnaryPlus $node) {
    +        return $this->pPrefixOp('Expr_UnaryPlus', '+', $node->expr);
    +    }
    +
    +    public function pExpr_PreInc(Expr\PreInc $node) {
    +        return $this->pPrefixOp('Expr_PreInc', '++', $node->var);
    +    }
    +
    +    public function pExpr_PreDec(Expr\PreDec $node) {
    +        return $this->pPrefixOp('Expr_PreDec', '--', $node->var);
    +    }
    +
    +    public function pExpr_PostInc(Expr\PostInc $node) {
    +        return $this->pPostfixOp('Expr_PostInc', $node->var, '++');
    +    }
    +
    +    public function pExpr_PostDec(Expr\PostDec $node) {
    +        return $this->pPostfixOp('Expr_PostDec', $node->var, '--');
    +    }
    +
    +    public function pExpr_ErrorSuppress(Expr\ErrorSuppress $node) {
    +        return $this->pPrefixOp('Expr_ErrorSuppress', '@', $node->expr);
    +    }
    +
    +    public function pExpr_YieldFrom(Expr\YieldFrom $node) {
    +        return $this->pPrefixOp('Expr_YieldFrom', 'yield from ', $node->expr);
    +    }
    +
    +    public function pExpr_Print(Expr\Print_ $node) {
    +        return $this->pPrefixOp('Expr_Print', 'print ', $node->expr);
    +    }
    +
    +    // Casts
    +
    +    public function pExpr_Cast_Int(Cast\Int_ $node) {
    +        return $this->pPrefixOp('Expr_Cast_Int', '(int) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Double(Cast\Double $node) {
    +        return $this->pPrefixOp('Expr_Cast_Double', '(double) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_String(Cast\String_ $node) {
    +        return $this->pPrefixOp('Expr_Cast_String', '(string) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Array(Cast\Array_ $node) {
    +        return $this->pPrefixOp('Expr_Cast_Array', '(array) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Object(Cast\Object_ $node) {
    +        return $this->pPrefixOp('Expr_Cast_Object', '(object) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Bool(Cast\Bool_ $node) {
    +        return $this->pPrefixOp('Expr_Cast_Bool', '(bool) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Unset(Cast\Unset_ $node) {
    +        return $this->pPrefixOp('Expr_Cast_Unset', '(unset) ', $node->expr);
    +    }
    +
    +    // Function calls and similar constructs
    +
    +    public function pExpr_FuncCall(Expr\FuncCall $node) {
    +        return $this->pCallLhs($node->name)
    +             . '(' . $this->pCommaSeparated($node->args) . ')';
    +    }
    +
    +    public function pExpr_MethodCall(Expr\MethodCall $node) {
    +        return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name)
    +             . '(' . $this->pCommaSeparated($node->args) . ')';
    +    }
    +
    +    public function pExpr_StaticCall(Expr\StaticCall $node) {
    +        return $this->pDereferenceLhs($node->class) . '::'
    +             . ($node->name instanceof Expr
    +                ? ($node->name instanceof Expr\Variable
    +                   ? $this->p($node->name)
    +                   : '{' . $this->p($node->name) . '}')
    +                : $node->name)
    +             . '(' . $this->pCommaSeparated($node->args) . ')';
    +    }
    +
    +    public function pExpr_Empty(Expr\Empty_ $node) {
    +        return 'empty(' . $this->p($node->expr) . ')';
    +    }
    +
    +    public function pExpr_Isset(Expr\Isset_ $node) {
    +        return 'isset(' . $this->pCommaSeparated($node->vars) . ')';
    +    }
    +
    +    public function pExpr_Eval(Expr\Eval_ $node) {
    +        return 'eval(' . $this->p($node->expr) . ')';
    +    }
    +
    +    public function pExpr_Include(Expr\Include_ $node) {
    +        static $map = array(
    +            Expr\Include_::TYPE_INCLUDE      => 'include',
    +            Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once',
    +            Expr\Include_::TYPE_REQUIRE      => 'require',
    +            Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once',
    +        );
    +
    +        return $map[$node->type] . ' ' . $this->p($node->expr);
    +    }
    +
    +    public function pExpr_List(Expr\List_ $node) {
    +        $pList = array();
    +        foreach ($node->vars as $var) {
    +            if (null === $var) {
    +                $pList[] = '';
    +            } else {
    +                $pList[] = $this->p($var);
    +            }
    +        }
    +
    +        return 'list(' . implode(', ', $pList) . ')';
    +    }
    +
    +    // Other
    +
    +    public function pExpr_Variable(Expr\Variable $node) {
    +        if ($node->name instanceof Expr) {
    +            return '${' . $this->p($node->name) . '}';
    +        } else {
    +            return '$' . $node->name;
    +        }
    +    }
    +
    +    public function pExpr_Array(Expr\Array_ $node) {
    +        $syntax = $node->getAttribute('kind',
    +            $this->options['shortArraySyntax'] ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG);
    +        if ($syntax === Expr\Array_::KIND_SHORT) {
    +            return '[' . $this->pCommaSeparated($node->items) . ']';
    +        } else {
    +            return 'array(' . $this->pCommaSeparated($node->items) . ')';
    +        }
    +    }
    +
    +    public function pExpr_ArrayItem(Expr\ArrayItem $node) {
    +        return (null !== $node->key ? $this->p($node->key) . ' => ' : '')
    +             . ($node->byRef ? '&' : '') . $this->p($node->value);
    +    }
    +
    +    public function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node) {
    +        return $this->pDereferenceLhs($node->var)
    +             . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']';
    +    }
    +
    +    public function pExpr_ConstFetch(Expr\ConstFetch $node) {
    +        return $this->p($node->name);
    +    }
    +
    +    public function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) {
    +        return $this->p($node->class) . '::' . $node->name;
    +    }
    +
    +    public function pExpr_PropertyFetch(Expr\PropertyFetch $node) {
    +        return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name);
    +    }
    +
    +    public function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) {
    +        return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name);
    +    }
    +
    +    public function pExpr_ShellExec(Expr\ShellExec $node) {
    +        return '`' . $this->pEncapsList($node->parts, '`') . '`';
    +    }
    +
    +    public function pExpr_Closure(Expr\Closure $node) {
    +        return ($node->static ? 'static ' : '')
    +             . 'function ' . ($node->byRef ? '&' : '')
    +             . '(' . $this->pCommaSeparated($node->params) . ')'
    +             . (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')': '')
    +             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
    +             . ' {' . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pExpr_ClosureUse(Expr\ClosureUse $node) {
    +        return ($node->byRef ? '&' : '') . '$' . $node->var;
    +    }
    +
    +    public function pExpr_New(Expr\New_ $node) {
    +        if ($node->class instanceof Stmt\Class_) {
    +            $args = $node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : '';
    +            return 'new ' . $this->pClassCommon($node->class, $args);
    +        }
    +        return 'new ' . $this->p($node->class) . '(' . $this->pCommaSeparated($node->args) . ')';
    +    }
    +
    +    public function pExpr_Clone(Expr\Clone_ $node) {
    +        return 'clone ' . $this->p($node->expr);
    +    }
    +
    +    public function pExpr_Ternary(Expr\Ternary $node) {
    +        // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator.
    +        // this is okay because the part between ? and : never needs parentheses.
    +        return $this->pInfixOp('Expr_Ternary',
    +            $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else
    +        );
    +    }
    +
    +    public function pExpr_Exit(Expr\Exit_ $node) {
    +        $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE);
    +        return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die')
    +             . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : '');
    +    }
    +
    +    public function pExpr_Yield(Expr\Yield_ $node) {
    +        if ($node->value === null) {
    +            return 'yield';
    +        } else {
    +            // this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary
    +            return '(yield '
    +                 . ($node->key !== null ? $this->p($node->key) . ' => ' : '')
    +                 . $this->p($node->value)
    +                 . ')';
    +        }
    +    }
    +
    +    // Declarations
    +
    +    public function pStmt_Namespace(Stmt\Namespace_ $node) {
    +        if ($this->canUseSemicolonNamespaces) {
    +            return 'namespace ' . $this->p($node->name) . ';' . "\n" . $this->pStmts($node->stmts, false);
    +        } else {
    +            return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '')
    +                 . ' {' . $this->pStmts($node->stmts) . "\n" . '}';
    +        }
    +    }
    +
    +    public function pStmt_Use(Stmt\Use_ $node) {
    +        return 'use ' . $this->pUseType($node->type)
    +             . $this->pCommaSeparated($node->uses) . ';';
    +    }
    +
    +    public function pStmt_GroupUse(Stmt\GroupUse $node) {
    +        return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix)
    +             . '\{' . $this->pCommaSeparated($node->uses) . '};';
    +    }
    +
    +    public function pStmt_UseUse(Stmt\UseUse $node) {
    +        return $this->pUseType($node->type) . $this->p($node->name)
    +             . ($node->name->getLast() !== $node->alias ? ' as ' . $node->alias : '');
    +    }
    +
    +    private function pUseType($type) {
    +        return $type === Stmt\Use_::TYPE_FUNCTION ? 'function '
    +            : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : '');
    +    }
    +
    +    public function pStmt_Interface(Stmt\Interface_ $node) {
    +        return 'interface ' . $node->name
    +             . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '')
    +             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Class(Stmt\Class_ $node) {
    +        return $this->pClassCommon($node, ' ' . $node->name);
    +    }
    +
    +    public function pStmt_Trait(Stmt\Trait_ $node) {
    +        return 'trait ' . $node->name
    +             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_TraitUse(Stmt\TraitUse $node) {
    +        return 'use ' . $this->pCommaSeparated($node->traits)
    +             . (empty($node->adaptations)
    +                ? ';'
    +                : ' {' . $this->pStmts($node->adaptations) . "\n" . '}');
    +    }
    +
    +    public function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node) {
    +        return $this->p($node->trait) . '::' . $node->method
    +             . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';';
    +    }
    +
    +    public function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) {
    +        return (null !== $node->trait ? $this->p($node->trait) . '::' : '')
    +             . $node->method . ' as'
    +             . (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '')
    +             . (null !== $node->newName     ? ' ' . $node->newName                        : '')
    +             . ';';
    +    }
    +
    +    public function pStmt_Property(Stmt\Property $node) {
    +        return (0 === $node->type ? 'var ' : $this->pModifiers($node->type)) . $this->pCommaSeparated($node->props) . ';';
    +    }
    +
    +    public function pStmt_PropertyProperty(Stmt\PropertyProperty $node) {
    +        return '$' . $node->name
    +             . (null !== $node->default ? ' = ' . $this->p($node->default) : '');
    +    }
    +
    +    public function pStmt_ClassMethod(Stmt\ClassMethod $node) {
    +        return $this->pModifiers($node->type)
    +             . 'function ' . ($node->byRef ? '&' : '') . $node->name
    +             . '(' . $this->pCommaSeparated($node->params) . ')'
    +             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
    +             . (null !== $node->stmts
    +                ? "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}'
    +                : ';');
    +    }
    +
    +    public function pStmt_ClassConst(Stmt\ClassConst $node) {
    +        return 'const ' . $this->pCommaSeparated($node->consts) . ';';
    +    }
    +
    +    public function pStmt_Function(Stmt\Function_ $node) {
    +        return 'function ' . ($node->byRef ? '&' : '') . $node->name
    +             . '(' . $this->pCommaSeparated($node->params) . ')'
    +             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
    +             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Const(Stmt\Const_ $node) {
    +        return 'const ' . $this->pCommaSeparated($node->consts) . ';';
    +    }
    +
    +    public function pStmt_Declare(Stmt\Declare_ $node) {
    +        return 'declare (' . $this->pCommaSeparated($node->declares) . ')'
    +             . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . "\n" . '}' : ';');
    +    }
    +
    +    public function pStmt_DeclareDeclare(Stmt\DeclareDeclare $node) {
    +        return $node->key . '=' . $this->p($node->value);
    +    }
    +
    +    // Control flow
    +
    +    public function pStmt_If(Stmt\If_ $node) {
    +        return 'if (' . $this->p($node->cond) . ') {'
    +             . $this->pStmts($node->stmts) . "\n" . '}'
    +             . $this->pImplode($node->elseifs)
    +             . (null !== $node->else ? $this->p($node->else) : '');
    +    }
    +
    +    public function pStmt_ElseIf(Stmt\ElseIf_ $node) {
    +        return ' elseif (' . $this->p($node->cond) . ') {'
    +             . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Else(Stmt\Else_ $node) {
    +        return ' else {' . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_For(Stmt\For_ $node) {
    +        return 'for ('
    +             . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '')
    +             . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '')
    +             . $this->pCommaSeparated($node->loop)
    +             . ') {' . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Foreach(Stmt\Foreach_ $node) {
    +        return 'foreach (' . $this->p($node->expr) . ' as '
    +             . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '')
    +             . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {'
    +             . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_While(Stmt\While_ $node) {
    +        return 'while (' . $this->p($node->cond) . ') {'
    +             . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Do(Stmt\Do_ $node) {
    +        return 'do {' . $this->pStmts($node->stmts) . "\n"
    +             . '} while (' . $this->p($node->cond) . ');';
    +    }
    +
    +    public function pStmt_Switch(Stmt\Switch_ $node) {
    +        return 'switch (' . $this->p($node->cond) . ') {'
    +             . $this->pStmts($node->cases) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Case(Stmt\Case_ $node) {
    +        return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':'
    +             . $this->pStmts($node->stmts);
    +    }
    +
    +    public function pStmt_TryCatch(Stmt\TryCatch $node) {
    +        return 'try {' . $this->pStmts($node->stmts) . "\n" . '}'
    +             . $this->pImplode($node->catches)
    +             . ($node->finallyStmts !== null
    +                ? ' finally {' . $this->pStmts($node->finallyStmts) . "\n" . '}'
    +                : '');
    +    }
    +
    +    public function pStmt_Catch(Stmt\Catch_ $node) {
    +        return ' catch (' . $this->p($node->type) . ' $' . $node->var . ') {'
    +             . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Break(Stmt\Break_ $node) {
    +        return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
    +    }
    +
    +    public function pStmt_Continue(Stmt\Continue_ $node) {
    +        return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
    +    }
    +
    +    public function pStmt_Return(Stmt\Return_ $node) {
    +        return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';';
    +    }
    +
    +    public function pStmt_Throw(Stmt\Throw_ $node) {
    +        return 'throw ' . $this->p($node->expr) . ';';
    +    }
    +
    +    public function pStmt_Label(Stmt\Label $node) {
    +        return $node->name . ':';
    +    }
    +
    +    public function pStmt_Goto(Stmt\Goto_ $node) {
    +        return 'goto ' . $node->name . ';';
    +    }
    +
    +    // Other
    +
    +    public function pStmt_Echo(Stmt\Echo_ $node) {
    +        return 'echo ' . $this->pCommaSeparated($node->exprs) . ';';
    +    }
    +
    +    public function pStmt_Static(Stmt\Static_ $node) {
    +        return 'static ' . $this->pCommaSeparated($node->vars) . ';';
    +    }
    +
    +    public function pStmt_Global(Stmt\Global_ $node) {
    +        return 'global ' . $this->pCommaSeparated($node->vars) . ';';
    +    }
    +
    +    public function pStmt_StaticVar(Stmt\StaticVar $node) {
    +        return '$' . $node->name
    +             . (null !== $node->default ? ' = ' . $this->p($node->default) : '');
    +    }
    +
    +    public function pStmt_Unset(Stmt\Unset_ $node) {
    +        return 'unset(' . $this->pCommaSeparated($node->vars) . ');';
    +    }
    +
    +    public function pStmt_InlineHTML(Stmt\InlineHTML $node) {
    +        return '?>' . $this->pNoIndent("\n" . $node->value) . 'remaining;
    +    }
    +
    +    public function pStmt_Nop(Stmt\Nop $node) {
    +        return '';
    +    }
    +
    +    // Helpers
    +
    +    protected function pType($node) {
    +        return is_string($node) ? $node : $this->p($node);
    +    }
    +
    +    protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) {
    +        return $this->pModifiers($node->type)
    +        . 'class' . $afterClassToken
    +        . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
    +        . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
    +        . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    protected function pObjectProperty($node) {
    +        if ($node instanceof Expr) {
    +            return '{' . $this->p($node) . '}';
    +        } else {
    +            return $node;
    +        }
    +    }
    +
    +    protected function pModifiers($modifiers) {
    +        return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC    ? 'public '    : '')
    +             . ($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '')
    +             . ($modifiers & Stmt\Class_::MODIFIER_PRIVATE   ? 'private '   : '')
    +             . ($modifiers & Stmt\Class_::MODIFIER_STATIC    ? 'static '    : '')
    +             . ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT  ? 'abstract '  : '')
    +             . ($modifiers & Stmt\Class_::MODIFIER_FINAL     ? 'final '     : '');
    +    }
    +
    +    protected function pEncapsList(array $encapsList, $quote) {
    +        $return = '';
    +        foreach ($encapsList as $element) {
    +            if ($element instanceof Scalar\EncapsedStringPart) {
    +                $return .= $this->escapeString($element->value, $quote);
    +            } else {
    +                $return .= '{' . $this->p($element) . '}';
    +            }
    +        }
    +
    +        return $return;
    +    }
    +
    +    protected function escapeString($string, $quote) {
    +        if (null === $quote) {
    +            // For doc strings, don't escape newlines
    +            $escaped = addcslashes($string, "\t\f\v$\\");
    +        } else {
    +            $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\");
    +        }
    +
    +        // Escape other control characters
    +        return preg_replace_callback('/([\0-\10\16-\37])(?=([0-7]?))/', function ($matches) {
    +            $oct = decoct(ord($matches[1]));
    +            if ($matches[2] !== '') {
    +                // If there is a trailing digit, use the full three character form
    +                return '\\' . str_pad($oct, 3, '0', STR_PAD_LEFT);
    +            }
    +            return '\\' . $oct;
    +        }, $escaped);
    +    }
    +
    +    protected function containsEndLabel($string, $label, $atStart = true, $atEnd = true) {
    +        $start = $atStart ? '(?:^|[\r\n])' : '[\r\n]';
    +        $end = $atEnd ? '(?:$|[;\r\n])' : '[;\r\n]';
    +        return false !== strpos($string, $label)
    +            && preg_match('/' . $start . $label . $end . '/', $string);
    +    }
    +
    +    protected function encapsedContainsEndLabel(array $parts, $label) {
    +        foreach ($parts as $i => $part) {
    +            $atStart = $i === 0;
    +            $atEnd = $i === count($parts) - 1;
    +            if ($part instanceof Scalar\EncapsedStringPart
    +                && $this->containsEndLabel($part->value, $label, $atStart, $atEnd)
    +            ) {
    +                return true;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    protected function pDereferenceLhs(Node $node) {
    +        if ($node instanceof Expr\Variable
    +            || $node instanceof Name
    +            || $node instanceof Expr\ArrayDimFetch
    +            || $node instanceof Expr\PropertyFetch
    +            || $node instanceof Expr\StaticPropertyFetch
    +            || $node instanceof Expr\FuncCall
    +            || $node instanceof Expr\MethodCall
    +            || $node instanceof Expr\StaticCall
    +            || $node instanceof Expr\Array_
    +            || $node instanceof Scalar\String_
    +            || $node instanceof Expr\ConstFetch
    +            || $node instanceof Expr\ClassConstFetch
    +        ) {
    +            return $this->p($node);
    +        } else  {
    +            return '(' . $this->p($node) . ')';
    +        }
    +    }
    +
    +    protected function pCallLhs(Node $node) {
    +        if ($node instanceof Name
    +            || $node instanceof Expr\Variable
    +            || $node instanceof Expr\ArrayDimFetch
    +            || $node instanceof Expr\FuncCall
    +            || $node instanceof Expr\MethodCall
    +            || $node instanceof Expr\StaticCall
    +            || $node instanceof Expr\Array_
    +        ) {
    +            return $this->p($node);
    +        } else  {
    +            return '(' . $this->p($node) . ')';
    +        }
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php b/core/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php
    new file mode 100644
    index 0000000..d984d27
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php
    @@ -0,0 +1,312 @@
    + array(  0,  1),
    +        'Expr_BitwiseNot'              => array( 10,  1),
    +        'Expr_PreInc'                  => array( 10,  1),
    +        'Expr_PreDec'                  => array( 10,  1),
    +        'Expr_PostInc'                 => array( 10, -1),
    +        'Expr_PostDec'                 => array( 10, -1),
    +        'Expr_UnaryPlus'               => array( 10,  1),
    +        'Expr_UnaryMinus'              => array( 10,  1),
    +        'Expr_Cast_Int'                => array( 10,  1),
    +        'Expr_Cast_Double'             => array( 10,  1),
    +        'Expr_Cast_String'             => array( 10,  1),
    +        'Expr_Cast_Array'              => array( 10,  1),
    +        'Expr_Cast_Object'             => array( 10,  1),
    +        'Expr_Cast_Bool'               => array( 10,  1),
    +        'Expr_Cast_Unset'              => array( 10,  1),
    +        'Expr_ErrorSuppress'           => array( 10,  1),
    +        'Expr_Instanceof'              => array( 20,  0),
    +        'Expr_BooleanNot'              => array( 30,  1),
    +        'Expr_BinaryOp_Mul'            => array( 40, -1),
    +        'Expr_BinaryOp_Div'            => array( 40, -1),
    +        'Expr_BinaryOp_Mod'            => array( 40, -1),
    +        'Expr_BinaryOp_Plus'           => array( 50, -1),
    +        'Expr_BinaryOp_Minus'          => array( 50, -1),
    +        'Expr_BinaryOp_Concat'         => array( 50, -1),
    +        'Expr_BinaryOp_ShiftLeft'      => array( 60, -1),
    +        'Expr_BinaryOp_ShiftRight'     => array( 60, -1),
    +        'Expr_BinaryOp_Smaller'        => array( 70,  0),
    +        'Expr_BinaryOp_SmallerOrEqual' => array( 70,  0),
    +        'Expr_BinaryOp_Greater'        => array( 70,  0),
    +        'Expr_BinaryOp_GreaterOrEqual' => array( 70,  0),
    +        'Expr_BinaryOp_Equal'          => array( 80,  0),
    +        'Expr_BinaryOp_NotEqual'       => array( 80,  0),
    +        'Expr_BinaryOp_Identical'      => array( 80,  0),
    +        'Expr_BinaryOp_NotIdentical'   => array( 80,  0),
    +        'Expr_BinaryOp_Spaceship'      => array( 80,  0),
    +        'Expr_BinaryOp_BitwiseAnd'     => array( 90, -1),
    +        'Expr_BinaryOp_BitwiseXor'     => array(100, -1),
    +        'Expr_BinaryOp_BitwiseOr'      => array(110, -1),
    +        'Expr_BinaryOp_BooleanAnd'     => array(120, -1),
    +        'Expr_BinaryOp_BooleanOr'      => array(130, -1),
    +        'Expr_BinaryOp_Coalesce'       => array(140,  1),
    +        'Expr_Ternary'                 => array(150, -1),
    +        // parser uses %left for assignments, but they really behave as %right
    +        'Expr_Assign'                  => array(160,  1),
    +        'Expr_AssignRef'               => array(160,  1),
    +        'Expr_AssignOp_Plus'           => array(160,  1),
    +        'Expr_AssignOp_Minus'          => array(160,  1),
    +        'Expr_AssignOp_Mul'            => array(160,  1),
    +        'Expr_AssignOp_Div'            => array(160,  1),
    +        'Expr_AssignOp_Concat'         => array(160,  1),
    +        'Expr_AssignOp_Mod'            => array(160,  1),
    +        'Expr_AssignOp_BitwiseAnd'     => array(160,  1),
    +        'Expr_AssignOp_BitwiseOr'      => array(160,  1),
    +        'Expr_AssignOp_BitwiseXor'     => array(160,  1),
    +        'Expr_AssignOp_ShiftLeft'      => array(160,  1),
    +        'Expr_AssignOp_ShiftRight'     => array(160,  1),
    +        'Expr_AssignOp_Pow'            => array(160,  1),
    +        'Expr_YieldFrom'               => array(165,  1),
    +        'Expr_Print'                   => array(168,  1),
    +        'Expr_BinaryOp_LogicalAnd'     => array(170, -1),
    +        'Expr_BinaryOp_LogicalXor'     => array(180, -1),
    +        'Expr_BinaryOp_LogicalOr'      => array(190, -1),
    +        'Expr_Include'                 => array(200, -1),
    +    );
    +
    +    protected $noIndentToken;
    +    protected $docStringEndToken;
    +    protected $canUseSemicolonNamespaces;
    +    protected $options;
    +
    +    /**
    +     * Creates a pretty printer instance using the given options.
    +     *
    +     * Supported options:
    +     *  * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array
    +     *                                    syntax, if the node does not specify a format.
    +     *
    +     * @param array $options Dictionary of formatting options
    +     */
    +    public function __construct(array $options = []) {
    +        $this->noIndentToken = '_NO_INDENT_' . mt_rand();
    +        $this->docStringEndToken = '_DOC_STRING_END_' . mt_rand();
    +
    +        $defaultOptions = ['shortArraySyntax' => false];
    +        $this->options = $options + $defaultOptions;
    +    }
    +
    +    /**
    +     * Pretty prints an array of statements.
    +     *
    +     * @param Node[] $stmts Array of statements
    +     *
    +     * @return string Pretty printed statements
    +     */
    +    public function prettyPrint(array $stmts) {
    +        $this->preprocessNodes($stmts);
    +
    +        return ltrim($this->handleMagicTokens($this->pStmts($stmts, false)));
    +    }
    +
    +    /**
    +     * Pretty prints an expression.
    +     *
    +     * @param Expr $node Expression node
    +     *
    +     * @return string Pretty printed node
    +     */
    +    public function prettyPrintExpr(Expr $node) {
    +        return $this->handleMagicTokens($this->p($node));
    +    }
    +
    +    /**
    +     * Pretty prints a file of statements (includes the opening prettyPrint($stmts);
    +
    +        if ($stmts[0] instanceof Stmt\InlineHTML) {
    +            $p = preg_replace('/^<\?php\s+\?>\n?/', '', $p);
    +        }
    +        if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) {
    +            $p = preg_replace('/<\?php$/', '', rtrim($p));
    +        }
    +
    +        return $p;
    +    }
    +
    +    /**
    +     * Preprocesses the top-level nodes to initialize pretty printer state.
    +     *
    +     * @param Node[] $nodes Array of nodes
    +     */
    +    protected function preprocessNodes(array $nodes) {
    +        /* We can use semicolon-namespaces unless there is a global namespace declaration */
    +        $this->canUseSemicolonNamespaces = true;
    +        foreach ($nodes as $node) {
    +            if ($node instanceof Stmt\Namespace_ && null === $node->name) {
    +                $this->canUseSemicolonNamespaces = false;
    +            }
    +        }
    +    }
    +
    +    protected function handleMagicTokens($str) {
    +        // Drop no-indent tokens
    +        $str = str_replace($this->noIndentToken, '', $str);
    +
    +        // Replace doc-string-end tokens with nothing or a newline
    +        $str = str_replace($this->docStringEndToken . ";\n", ";\n", $str);
    +        $str = str_replace($this->docStringEndToken, "\n", $str);
    +
    +        return $str;
    +    }
    +
    +    /**
    +     * Pretty prints an array of nodes (statements) and indents them optionally.
    +     *
    +     * @param Node[] $nodes  Array of nodes
    +     * @param bool   $indent Whether to indent the printed nodes
    +     *
    +     * @return string Pretty printed statements
    +     */
    +    protected function pStmts(array $nodes, $indent = true) {
    +        $result = '';
    +        foreach ($nodes as $node) {
    +            $comments = $node->getAttribute('comments', array());
    +            if ($comments) {
    +                $result .= "\n" . $this->pComments($comments);
    +                if ($node instanceof Stmt\Nop) {
    +                    continue;
    +                }
    +            }
    +
    +            $result .= "\n" . $this->p($node) . ($node instanceof Expr ? ';' : '');
    +        }
    +
    +        if ($indent) {
    +            return preg_replace('~\n(?!$|' . $this->noIndentToken . ')~', "\n    ", $result);
    +        } else {
    +            return $result;
    +        }
    +    }
    +
    +    /**
    +     * Pretty prints a node.
    +     *
    +     * @param Node $node Node to be pretty printed
    +     *
    +     * @return string Pretty printed node
    +     */
    +    protected function p(Node $node) {
    +        return $this->{'p' . $node->getType()}($node);
    +    }
    +
    +    protected function pInfixOp($type, Node $leftNode, $operatorString, Node $rightNode) {
    +        list($precedence, $associativity) = $this->precedenceMap[$type];
    +
    +        return $this->pPrec($leftNode, $precedence, $associativity, -1)
    +             . $operatorString
    +             . $this->pPrec($rightNode, $precedence, $associativity, 1);
    +    }
    +
    +    protected function pPrefixOp($type, $operatorString, Node $node) {
    +        list($precedence, $associativity) = $this->precedenceMap[$type];
    +        return $operatorString . $this->pPrec($node, $precedence, $associativity, 1);
    +    }
    +
    +    protected function pPostfixOp($type, Node $node, $operatorString) {
    +        list($precedence, $associativity) = $this->precedenceMap[$type];
    +        return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString;
    +    }
    +
    +    /**
    +     * Prints an expression node with the least amount of parentheses necessary to preserve the meaning.
    +     *
    +     * @param Node $node                Node to pretty print
    +     * @param int  $parentPrecedence    Precedence of the parent operator
    +     * @param int  $parentAssociativity Associativity of parent operator
    +     *                                  (-1 is left, 0 is nonassoc, 1 is right)
    +     * @param int  $childPosition       Position of the node relative to the operator
    +     *                                  (-1 is left, 1 is right)
    +     *
    +     * @return string The pretty printed node
    +     */
    +    protected function pPrec(Node $node, $parentPrecedence, $parentAssociativity, $childPosition) {
    +        $type = $node->getType();
    +        if (isset($this->precedenceMap[$type])) {
    +            $childPrecedence = $this->precedenceMap[$type][0];
    +            if ($childPrecedence > $parentPrecedence
    +                || ($parentPrecedence == $childPrecedence && $parentAssociativity != $childPosition)
    +            ) {
    +                return '(' . $this->{'p' . $type}($node) . ')';
    +            }
    +        }
    +
    +        return $this->{'p' . $type}($node);
    +    }
    +
    +    /**
    +     * Pretty prints an array of nodes and implodes the printed values.
    +     *
    +     * @param Node[] $nodes Array of Nodes to be printed
    +     * @param string $glue  Character to implode with
    +     *
    +     * @return string Imploded pretty printed nodes
    +     */
    +    protected function pImplode(array $nodes, $glue = '') {
    +        $pNodes = array();
    +        foreach ($nodes as $node) {
    +            $pNodes[] = $this->p($node);
    +        }
    +
    +        return implode($glue, $pNodes);
    +    }
    +
    +    /**
    +     * Pretty prints an array of nodes and implodes the printed values with commas.
    +     *
    +     * @param Node[] $nodes Array of Nodes to be printed
    +     *
    +     * @return string Comma separated pretty printed nodes
    +     */
    +    protected function pCommaSeparated(array $nodes) {
    +        return $this->pImplode($nodes, ', ');
    +    }
    +
    +    /**
    +     * Signals the pretty printer that a string shall not be indented.
    +     *
    +     * @param string $string Not to be indented string
    +     *
    +     * @return string String marked with $this->noIndentToken's.
    +     */
    +    protected function pNoIndent($string) {
    +        return str_replace("\n", "\n" . $this->noIndentToken, $string);
    +    }
    +
    +    /**
    +     * Prints reformatted text of the passed comments.
    +     *
    +     * @param Comment[] $comments List of comments
    +     *
    +     * @return string Reformatted text of comments
    +     */
    +    protected function pComments(array $comments) {
    +        $formattedComments = [];
    +
    +        foreach ($comments as $comment) {
    +            $formattedComments[] = $comment->getReformattedText();
    +        }
    +
    +        return implode("\n", $formattedComments);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Serializer.php b/core/vendor/nikic/php-parser/lib/PhpParser/Serializer.php
    new file mode 100644
    index 0000000..7c173cd
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Serializer.php
    @@ -0,0 +1,15 @@
    +writer = new XMLWriter;
    +        $this->writer->openMemory();
    +        $this->writer->setIndent(true);
    +    }
    +
    +    public function serialize(array $nodes) {
    +        $this->writer->flush();
    +        $this->writer->startDocument('1.0', 'UTF-8');
    +
    +        $this->writer->startElement('AST');
    +        $this->writer->writeAttribute('xmlns:node',      'http://nikic.github.com/PHPParser/XML/node');
    +        $this->writer->writeAttribute('xmlns:subNode',   'http://nikic.github.com/PHPParser/XML/subNode');
    +        $this->writer->writeAttribute('xmlns:attribute', 'http://nikic.github.com/PHPParser/XML/attribute');
    +        $this->writer->writeAttribute('xmlns:scalar',    'http://nikic.github.com/PHPParser/XML/scalar');
    +
    +        $this->_serialize($nodes);
    +
    +        $this->writer->endElement();
    +
    +        return $this->writer->outputMemory();
    +    }
    +
    +    protected function _serialize($node) {
    +        if ($node instanceof Node) {
    +            $this->writer->startElement('node:' . $node->getType());
    +
    +            foreach ($node->getAttributes() as $name => $value) {
    +                $this->writer->startElement('attribute:' . $name);
    +                $this->_serialize($value);
    +                $this->writer->endElement();
    +            }
    +
    +            foreach ($node as $name => $subNode) {
    +                $this->writer->startElement('subNode:' . $name);
    +                $this->_serialize($subNode);
    +                $this->writer->endElement();
    +            }
    +
    +            $this->writer->endElement();
    +        } elseif ($node instanceof Comment) {
    +            $this->writer->startElement('comment');
    +            $this->writer->writeAttribute('isDocComment', $node instanceof Comment\Doc ? 'true' : 'false');
    +            $this->writer->writeAttribute('line', (string) $node->getLine());
    +            $this->writer->text($node->getText());
    +            $this->writer->endElement();
    +        } elseif (is_array($node)) {
    +            $this->writer->startElement('scalar:array');
    +            foreach ($node as $subNode) {
    +                $this->_serialize($subNode);
    +            }
    +            $this->writer->endElement();
    +        } elseif (is_string($node)) {
    +            $this->writer->writeElement('scalar:string', $node);
    +        } elseif (is_int($node)) {
    +            $this->writer->writeElement('scalar:int', (string) $node);
    +        } elseif (is_float($node)) {
    +            // TODO Higher precision conversion?
    +            $this->writer->writeElement('scalar:float', (string) $node);
    +        } elseif (true === $node) {
    +            $this->writer->writeElement('scalar:true');
    +        } elseif (false === $node) {
    +            $this->writer->writeElement('scalar:false');
    +        } elseif (null === $node) {
    +            $this->writer->writeElement('scalar:null');
    +        } else {
    +            throw new \InvalidArgumentException('Unexpected node type');
    +        }
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/PhpParser/Unserializer.php b/core/vendor/nikic/php-parser/lib/PhpParser/Unserializer.php
    new file mode 100644
    index 0000000..bfa6da0
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/PhpParser/Unserializer.php
    @@ -0,0 +1,15 @@
    +reader = new XMLReader;
    +    }
    +
    +    public function unserialize($string) {
    +        $this->reader->XML($string);
    +
    +        $this->reader->read();
    +        if ('AST' !== $this->reader->name) {
    +            throw new DomainException('AST root element not found');
    +        }
    +
    +        return $this->read($this->reader->depth);
    +    }
    +
    +    protected function read($depthLimit, $throw = true, &$nodeFound = null) {
    +        $nodeFound = true;
    +        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
    +            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
    +                continue;
    +            }
    +
    +            if ('node' === $this->reader->prefix) {
    +                return $this->readNode();
    +            } elseif ('scalar' === $this->reader->prefix) {
    +                return $this->readScalar();
    +            } elseif ('comment' === $this->reader->name) {
    +                return $this->readComment();
    +            } else {
    +                throw new DomainException(sprintf('Unexpected node of type "%s"', $this->reader->name));
    +            }
    +        }
    +
    +        $nodeFound = false;
    +        if ($throw) {
    +            throw new DomainException('Expected node or scalar');
    +        }
    +    }
    +
    +    protected function readNode() {
    +        $className = $this->getClassNameFromType($this->reader->localName);
    +
    +        // create the node without calling it's constructor
    +        $node = unserialize(
    +            sprintf(
    +                "O:%d:\"%s\":1:{s:13:\"\0*\0attributes\";a:0:{}}",
    +                strlen($className), $className
    +            )
    +        );
    +
    +        $depthLimit = $this->reader->depth;
    +        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
    +            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
    +                continue;
    +            }
    +
    +            $type = $this->reader->prefix;
    +            if ('subNode' !== $type && 'attribute' !== $type) {
    +                throw new DomainException(
    +                    sprintf('Expected sub node or attribute, got node of type "%s"', $this->reader->name)
    +                );
    +            }
    +
    +            $name = $this->reader->localName;
    +            $value = $this->read($this->reader->depth);
    +
    +            if ('subNode' === $type) {
    +                $node->$name = $value;
    +            } else {
    +                $node->setAttribute($name, $value);
    +            }
    +        }
    +
    +        return $node;
    +    }
    +
    +    protected function readScalar() {
    +        switch ($name = $this->reader->localName) {
    +            case 'array':
    +                $depth = $this->reader->depth;
    +                $array = array();
    +                while (true) {
    +                    $node = $this->read($depth, false, $nodeFound);
    +                    if (!$nodeFound) {
    +                        break;
    +                    }
    +                    $array[] = $node;
    +                }
    +                return $array;
    +            case 'string':
    +                return $this->reader->readString();
    +            case 'int':
    +                return $this->parseInt($this->reader->readString());
    +            case 'float':
    +                $text = $this->reader->readString();
    +                if (false === $float = filter_var($text, FILTER_VALIDATE_FLOAT)) {
    +                    throw new DomainException(sprintf('"%s" is not a valid float', $text));
    +                }
    +                return $float;
    +            case 'true':
    +            case 'false':
    +            case 'null':
    +                if (!$this->reader->isEmptyElement) {
    +                    throw new DomainException(sprintf('"%s" scalar must be empty', $name));
    +                }
    +                return constant($name);
    +            default:
    +                throw new DomainException(sprintf('Unknown scalar type "%s"', $name));
    +        }
    +    }
    +
    +    private function parseInt($text) {
    +        if (false === $int = filter_var($text, FILTER_VALIDATE_INT)) {
    +            throw new DomainException(sprintf('"%s" is not a valid integer', $text));
    +        }
    +        return $int;
    +    }
    +
    +    protected function readComment() {
    +        $className = $this->reader->getAttribute('isDocComment') === 'true'
    +            ? 'PhpParser\Comment\Doc'
    +            : 'PhpParser\Comment'
    +        ;
    +        return new $className(
    +            $this->reader->readString(),
    +            $this->parseInt($this->reader->getAttribute('line'))
    +        );
    +    }
    +
    +    protected function getClassNameFromType($type) {
    +        $className = 'PhpParser\\Node\\' . strtr($type, '_', '\\');
    +        if (!class_exists($className)) {
    +            $className .= '_';
    +        }
    +        if (!class_exists($className)) {
    +            throw new DomainException(sprintf('Unknown node type "%s"', $type));
    +        }
    +        return $className;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/lib/bootstrap.php b/core/vendor/nikic/php-parser/lib/bootstrap.php
    new file mode 100644
    index 0000000..b0f5178
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/lib/bootstrap.php
    @@ -0,0 +1,6 @@
    +
    +
    +
    +    
    +        
    +            ./test/
    +        
    +    
    +
    +    
    +        
    +            ./lib/PhpParser/
    +        
    +    
    +
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/AutoloaderTest.php b/core/vendor/nikic/php-parser/test/PhpParser/AutoloaderTest.php
    new file mode 100644
    index 0000000..e16b9f2
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/AutoloaderTest.php
    @@ -0,0 +1,18 @@
    +assertTrue(class_exists('PhpParser\NodeVisitorAbstract'));
    +        $this->assertFalse(class_exists('PHPParser_NodeVisitor_NameResolver'));
    +
    +        $this->assertFalse(class_exists('PhpParser\FooBar'));
    +        $this->assertFalse(class_exists('PHPParser_FooBar'));
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/ClassTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/ClassTest.php
    new file mode 100644
    index 0000000..94ce67a
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/ClassTest.php
    @@ -0,0 +1,161 @@
    +createClassBuilder('SomeLogger')
    +            ->extend('BaseLogger')
    +            ->implement('Namespaced\Logger', new Name('SomeInterface'))
    +            ->implement('\Fully\Qualified', 'namespace\NamespaceRelative')
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Class_('SomeLogger', array(
    +                'extends' => new Name('BaseLogger'),
    +                'implements' => array(
    +                    new Name('Namespaced\Logger'),
    +                    new Name('SomeInterface'),
    +                    new Name\FullyQualified('Fully\Qualified'),
    +                    new Name\Relative('NamespaceRelative'),
    +                ),
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testAbstract() {
    +        $node = $this->createClassBuilder('Test')
    +            ->makeAbstract()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Class_('Test', array(
    +                'type' => Stmt\Class_::MODIFIER_ABSTRACT
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testFinal() {
    +        $node = $this->createClassBuilder('Test')
    +            ->makeFinal()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Class_('Test', array(
    +                'type' => Stmt\Class_::MODIFIER_FINAL
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testStatementOrder() {
    +        $method = new Stmt\ClassMethod('testMethod');
    +        $property = new Stmt\Property(
    +            Stmt\Class_::MODIFIER_PUBLIC,
    +            array(new Stmt\PropertyProperty('testProperty'))
    +        );
    +        $const = new Stmt\ClassConst(array(
    +            new Node\Const_('TEST_CONST', new Node\Scalar\String_('ABC'))
    +        ));
    +        $use = new Stmt\TraitUse(array(new Name('SomeTrait')));
    +
    +        $node = $this->createClassBuilder('Test')
    +            ->addStmt($method)
    +            ->addStmt($property)
    +            ->addStmts(array($const, $use))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Class_('Test', array(
    +                'stmts' => array($use, $const, $property, $method)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testDocComment() {
    +        $docComment = <<<'DOC'
    +/**
    + * Test
    + */
    +DOC;
    +        $class = $this->createClassBuilder('Test')
    +            ->setDocComment($docComment)
    +            ->getNode();
    +
    +        $this->assertEquals(
    +            new Stmt\Class_('Test', array(), array(
    +                'comments' => array(
    +                    new Comment\Doc($docComment)
    +                )
    +            )),
    +            $class
    +        );
    +
    +        $class = $this->createClassBuilder('Test')
    +            ->setDocComment(new Comment\Doc($docComment))
    +            ->getNode();
    +
    +        $this->assertEquals(
    +            new Stmt\Class_('Test', array(), array(
    +                'comments' => array(
    +                    new Comment\Doc($docComment)
    +                )
    +            )),
    +            $class
    +        );
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Unexpected node of type "Stmt_Echo"
    +     */
    +    public function testInvalidStmtError() {
    +        $this->createClassBuilder('Test')
    +            ->addStmt(new Stmt\Echo_(array()))
    +        ;
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Doc comment must be a string or an instance of PhpParser\Comment\Doc
    +     */
    +    public function testInvalidDocComment() {
    +        $this->createClassBuilder('Test')
    +            ->setDocComment(new Comment('Test'));
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Name cannot be empty
    +     */
    +    public function testEmptyName() {
    +        $this->createClassBuilder('Test')
    +            ->extend('');
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Name must be a string or an instance of PhpParser\Node\Name
    +     */
    +    public function testInvalidName() {
    +        $this->createClassBuilder('Test')
    +            ->extend(array('Foo'));
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/FunctionTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/FunctionTest.php
    new file mode 100644
    index 0000000..7b397fe
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/FunctionTest.php
    @@ -0,0 +1,98 @@
    +createFunctionBuilder('test')
    +            ->makeReturnByRef()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Function_('test', array(
    +                'byRef' => true
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testParams() {
    +        $param1 = new Node\Param('test1');
    +        $param2 = new Node\Param('test2');
    +        $param3 = new Node\Param('test3');
    +
    +        $node = $this->createFunctionBuilder('test')
    +            ->addParam($param1)
    +            ->addParams(array($param2, $param3))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Function_('test', array(
    +                'params' => array($param1, $param2, $param3)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testStmts() {
    +        $stmt1 = new Print_(new String_('test1'));
    +        $stmt2 = new Print_(new String_('test2'));
    +        $stmt3 = new Print_(new String_('test3'));
    +
    +        $node = $this->createFunctionBuilder('test')
    +            ->addStmt($stmt1)
    +            ->addStmts(array($stmt2, $stmt3))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Function_('test', array(
    +                'stmts' => array($stmt1, $stmt2, $stmt3)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testDocComment() {
    +        $node = $this->createFunctionBuilder('test')
    +            ->setDocComment('/** Test */')
    +            ->getNode();
    +
    +        $this->assertEquals(new Stmt\Function_('test', array(), array(
    +            'comments' => array(new Comment\Doc('/** Test */'))
    +        )), $node);
    +    }
    +
    +    public function testReturnType() {
    +        $node = $this->createFunctionBuilder('test')
    +            ->setReturnType('bool')
    +            ->getNode();
    +
    +        $this->assertEquals(new Stmt\Function_('test', array(
    +            'returnType' => 'bool'
    +        ), array()), $node);
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Expected parameter node, got "Name"
    +     */
    +    public function testInvalidParamError() {
    +        $this->createFunctionBuilder('test')
    +            ->addParam(new Node\Name('foo'))
    +        ;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/InterfaceTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/InterfaceTest.php
    new file mode 100644
    index 0000000..c0d2fe3
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/InterfaceTest.php
    @@ -0,0 +1,105 @@
    +builder = new Interface_('Contract');
    +    }
    +
    +    private function dump($node) {
    +        $pp = new \PhpParser\PrettyPrinter\Standard;
    +        return $pp->prettyPrint(array($node));
    +    }
    +
    +    public function testEmpty() {
    +        $contract = $this->builder->getNode();
    +        $this->assertInstanceOf('PhpParser\Node\Stmt\Interface_', $contract);
    +        $this->assertSame('Contract', $contract->name);
    +    }
    +
    +    public function testExtending() {
    +        $contract = $this->builder->extend('Space\Root1', 'Root2')->getNode();
    +        $this->assertEquals(
    +            new Stmt\Interface_('Contract', array(
    +                'extends' => array(
    +                    new Node\Name('Space\Root1'),
    +                    new Node\Name('Root2')
    +                ),
    +            )), $contract
    +        );
    +    }
    +
    +    public function testAddMethod() {
    +        $method = new Stmt\ClassMethod('doSomething');
    +        $contract = $this->builder->addStmt($method)->getNode();
    +        $this->assertSame(array($method), $contract->stmts);
    +    }
    +
    +    public function testAddConst() {
    +        $const = new Stmt\ClassConst(array(
    +            new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458.0))
    +        ));
    +        $contract = $this->builder->addStmt($const)->getNode();
    +        $this->assertSame(299792458.0, $contract->stmts[0]->consts[0]->value->value);
    +    }
    +
    +    public function testOrder() {
    +        $const = new Stmt\ClassConst(array(
    +            new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458))
    +        ));
    +        $method = new Stmt\ClassMethod('doSomething');
    +        $contract = $this->builder
    +            ->addStmt($method)
    +            ->addStmt($const)
    +            ->getNode()
    +        ;
    +
    +        $this->assertInstanceOf('PhpParser\Node\Stmt\ClassConst', $contract->stmts[0]);
    +        $this->assertInstanceOf('PhpParser\Node\Stmt\ClassMethod', $contract->stmts[1]);
    +    }
    +
    +    public function testDocComment() {
    +        $node = $this->builder
    +            ->setDocComment('/** Test */')
    +            ->getNode();
    +
    +        $this->assertEquals(new Stmt\Interface_('Contract', array(), array(
    +            'comments' => array(new Comment\Doc('/** Test */'))
    +        )), $node);
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Unexpected node of type "Stmt_PropertyProperty"
    +     */
    +    public function testInvalidStmtError() {
    +        $this->builder->addStmt(new Stmt\PropertyProperty('invalid'));
    +    }
    +
    +    public function testFullFunctional() {
    +        $const = new Stmt\ClassConst(array(
    +            new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458))
    +        ));
    +        $method = new Stmt\ClassMethod('doSomething');
    +        $contract = $this->builder
    +            ->addStmt($method)
    +            ->addStmt($const)
    +            ->getNode()
    +        ;
    +
    +        eval($this->dump($contract));
    +
    +        $this->assertTrue(interface_exists('Contract', false));
    +    }
    +}
    +
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/MethodTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/MethodTest.php
    new file mode 100644
    index 0000000..aa8e976
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/MethodTest.php
    @@ -0,0 +1,163 @@
    +createMethodBuilder('test')
    +            ->makePublic()
    +            ->makeAbstract()
    +            ->makeStatic()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\ClassMethod('test', array(
    +                'type' => Stmt\Class_::MODIFIER_PUBLIC
    +                        | Stmt\Class_::MODIFIER_ABSTRACT
    +                        | Stmt\Class_::MODIFIER_STATIC,
    +                'stmts' => null,
    +            )),
    +            $node
    +        );
    +
    +        $node = $this->createMethodBuilder('test')
    +            ->makeProtected()
    +            ->makeFinal()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\ClassMethod('test', array(
    +                'type' => Stmt\Class_::MODIFIER_PROTECTED
    +                        | Stmt\Class_::MODIFIER_FINAL
    +            )),
    +            $node
    +        );
    +
    +        $node = $this->createMethodBuilder('test')
    +            ->makePrivate()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\ClassMethod('test', array(
    +                'type' => Stmt\Class_::MODIFIER_PRIVATE
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testReturnByRef() {
    +        $node = $this->createMethodBuilder('test')
    +            ->makeReturnByRef()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\ClassMethod('test', array(
    +                'byRef' => true
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testParams() {
    +        $param1 = new Node\Param('test1');
    +        $param2 = new Node\Param('test2');
    +        $param3 = new Node\Param('test3');
    +
    +        $node = $this->createMethodBuilder('test')
    +            ->addParam($param1)
    +            ->addParams(array($param2, $param3))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\ClassMethod('test', array(
    +                'params' => array($param1, $param2, $param3)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testStmts() {
    +        $stmt1 = new Print_(new String_('test1'));
    +        $stmt2 = new Print_(new String_('test2'));
    +        $stmt3 = new Print_(new String_('test3'));
    +
    +        $node = $this->createMethodBuilder('test')
    +            ->addStmt($stmt1)
    +            ->addStmts(array($stmt2, $stmt3))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\ClassMethod('test', array(
    +                'stmts' => array($stmt1, $stmt2, $stmt3)
    +            )),
    +            $node
    +        );
    +    }
    +    public function testDocComment() {
    +        $node = $this->createMethodBuilder('test')
    +            ->setDocComment('/** Test */')
    +            ->getNode();
    +
    +        $this->assertEquals(new Stmt\ClassMethod('test', array(), array(
    +            'comments' => array(new Comment\Doc('/** Test */'))
    +        )), $node);
    +    }
    +
    +    public function testReturnType() {
    +        $node = $this->createMethodBuilder('test')
    +            ->setReturnType('bool')
    +            ->getNode();
    +        $this->assertEquals(new Stmt\ClassMethod('test', array(
    +            'returnType' => 'bool'
    +        ), array()), $node);
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Cannot add statements to an abstract method
    +     */
    +    public function testAddStmtToAbstractMethodError() {
    +        $this->createMethodBuilder('test')
    +            ->makeAbstract()
    +            ->addStmt(new Print_(new String_('test')))
    +        ;
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Cannot make method with statements abstract
    +     */
    +    public function testMakeMethodWithStmtsAbstractError() {
    +        $this->createMethodBuilder('test')
    +            ->addStmt(new Print_(new String_('test')))
    +            ->makeAbstract()
    +        ;
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Expected parameter node, got "Name"
    +     */
    +    public function testInvalidParamError() {
    +        $this->createMethodBuilder('test')
    +            ->addParam(new Node\Name('foo'))
    +        ;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/NamespaceTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/NamespaceTest.php
    new file mode 100644
    index 0000000..54e8c93
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/NamespaceTest.php
    @@ -0,0 +1,41 @@
    +createNamespaceBuilder('Name\Space')
    +            ->addStmt($stmt1)
    +            ->addStmts(array($stmt2, $stmt3))
    +            ->getNode()
    +        ;
    +        $this->assertEquals($expected, $node);
    +
    +        $node = $this->createNamespaceBuilder(new Node\Name(array('Name', 'Space')))
    +            ->addStmts(array($stmt1, $stmt2))
    +            ->addStmt($stmt3)
    +            ->getNode()
    +        ;
    +        $this->assertEquals($expected, $node);
    +
    +        $node = $this->createNamespaceBuilder(null)->getNode();
    +        $this->assertNull($node->name);
    +        $this->assertEmpty($node->stmts);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/ParamTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/ParamTest.php
    new file mode 100644
    index 0000000..f71a10c
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/ParamTest.php
    @@ -0,0 +1,124 @@
    +createParamBuilder('test')
    +            ->setDefault($value)
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals($expectedValueNode, $node->default);
    +    }
    +
    +    public function provideTestDefaultValues() {
    +        return array(
    +            array(
    +                null,
    +                new Expr\ConstFetch(new Node\Name('null'))
    +            ),
    +            array(
    +                true,
    +                new Expr\ConstFetch(new Node\Name('true'))
    +            ),
    +            array(
    +                false,
    +                new Expr\ConstFetch(new Node\Name('false'))
    +            ),
    +            array(
    +                31415,
    +                new Scalar\LNumber(31415)
    +            ),
    +            array(
    +                3.1415,
    +                new Scalar\DNumber(3.1415)
    +            ),
    +            array(
    +                'Hallo World',
    +                new Scalar\String_('Hallo World')
    +            ),
    +            array(
    +                array(1, 2, 3),
    +                new Expr\Array_(array(
    +                    new Expr\ArrayItem(new Scalar\LNumber(1)),
    +                    new Expr\ArrayItem(new Scalar\LNumber(2)),
    +                    new Expr\ArrayItem(new Scalar\LNumber(3)),
    +                ))
    +            ),
    +            array(
    +                array('foo' => 'bar', 'bar' => 'foo'),
    +                new Expr\Array_(array(
    +                    new Expr\ArrayItem(
    +                        new Scalar\String_('bar'),
    +                        new Scalar\String_('foo')
    +                    ),
    +                    new Expr\ArrayItem(
    +                        new Scalar\String_('foo'),
    +                        new Scalar\String_('bar')
    +                    ),
    +                ))
    +            ),
    +            array(
    +                new Scalar\MagicConst\Dir,
    +                new Scalar\MagicConst\Dir
    +            )
    +        );
    +    }
    +
    +    public function testTypeHints() {
    +        $node = $this->createParamBuilder('test')
    +            ->setTypeHint('array')
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Node\Param('test', null, 'array'),
    +            $node
    +        );
    +
    +        $node = $this->createParamBuilder('test')
    +            ->setTypeHint('callable')
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Node\Param('test', null, 'callable'),
    +            $node
    +        );
    +
    +        $node = $this->createParamBuilder('test')
    +            ->setTypeHint('Some\Class')
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Node\Param('test', null, new Node\Name('Some\Class')),
    +            $node
    +        );
    +    }
    +
    +    public function testByRef() {
    +        $node = $this->createParamBuilder('test')
    +            ->makeByRef()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Node\Param('test', null, null, true),
    +            $node
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/PropertyTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/PropertyTest.php
    new file mode 100644
    index 0000000..1e62173
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/PropertyTest.php
    @@ -0,0 +1,147 @@
    +createPropertyBuilder('test')
    +            ->makePrivate()
    +            ->makeStatic()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Property(
    +                Stmt\Class_::MODIFIER_PRIVATE
    +              | Stmt\Class_::MODIFIER_STATIC,
    +                array(
    +                    new Stmt\PropertyProperty('test')
    +                )
    +            ),
    +            $node
    +        );
    +
    +        $node = $this->createPropertyBuilder('test')
    +            ->makeProtected()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Property(
    +                Stmt\Class_::MODIFIER_PROTECTED,
    +                array(
    +                    new Stmt\PropertyProperty('test')
    +                )
    +            ),
    +            $node
    +        );
    +
    +        $node = $this->createPropertyBuilder('test')
    +            ->makePublic()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new Stmt\Property(
    +                Stmt\Class_::MODIFIER_PUBLIC,
    +                array(
    +                    new Stmt\PropertyProperty('test')
    +                )
    +            ),
    +            $node
    +        );
    +    }
    +
    +    public function testDocComment() {
    +        $node = $this->createPropertyBuilder('test')
    +            ->setDocComment('/** Test */')
    +            ->getNode();
    +
    +        $this->assertEquals(new Stmt\Property(
    +            Stmt\Class_::MODIFIER_PUBLIC,
    +            array(
    +                new Stmt\PropertyProperty('test')
    +            ),
    +            array(
    +                'comments' => array(new Comment\Doc('/** Test */'))
    +            )
    +        ), $node);
    +    }
    +
    +    /**
    +     * @dataProvider provideTestDefaultValues
    +     */
    +    public function testDefaultValues($value, $expectedValueNode) {
    +        $node = $this->createPropertyBuilder('test')
    +            ->setDefault($value)
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals($expectedValueNode, $node->props[0]->default);
    +    }
    +
    +    public function provideTestDefaultValues() {
    +        return array(
    +            array(
    +                null,
    +                new Expr\ConstFetch(new Name('null'))
    +            ),
    +            array(
    +                true,
    +                new Expr\ConstFetch(new Name('true'))
    +            ),
    +            array(
    +                false,
    +                new Expr\ConstFetch(new Name('false'))
    +            ),
    +            array(
    +                31415,
    +                new Scalar\LNumber(31415)
    +            ),
    +            array(
    +                3.1415,
    +                new Scalar\DNumber(3.1415)
    +            ),
    +            array(
    +                'Hallo World',
    +                new Scalar\String_('Hallo World')
    +            ),
    +            array(
    +                array(1, 2, 3),
    +                new Expr\Array_(array(
    +                    new Expr\ArrayItem(new Scalar\LNumber(1)),
    +                    new Expr\ArrayItem(new Scalar\LNumber(2)),
    +                    new Expr\ArrayItem(new Scalar\LNumber(3)),
    +                ))
    +            ),
    +            array(
    +                array('foo' => 'bar', 'bar' => 'foo'),
    +                new Expr\Array_(array(
    +                    new Expr\ArrayItem(
    +                        new Scalar\String_('bar'),
    +                        new Scalar\String_('foo')
    +                    ),
    +                    new Expr\ArrayItem(
    +                        new Scalar\String_('foo'),
    +                        new Scalar\String_('bar')
    +                    ),
    +                ))
    +            ),
    +            array(
    +                new Scalar\MagicConst\Dir,
    +                new Scalar\MagicConst\Dir
    +            )
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/TraitTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/TraitTest.php
    new file mode 100644
    index 0000000..a6d69ad
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/TraitTest.php
    @@ -0,0 +1,47 @@
    +createTraitBuilder('TestTrait')
    +            ->setDocComment('/** Nice trait */')
    +            ->addStmt($method1)
    +            ->addStmts(array($method2, $method3))
    +            ->addStmt($prop)
    +            ->getNode();
    +        $this->assertEquals(new Stmt\Trait_('TestTrait', array(
    +            $prop, $method1, $method2, $method3
    +        ), array(
    +            'comments' => array(
    +                new Comment\Doc('/** Nice trait */')
    +            )
    +        )), $trait);
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage Unexpected node of type "Stmt_Echo"
    +     */
    +    public function testInvalidStmtError() {
    +        $this->createTraitBuilder('Test')
    +            ->addStmt(new Stmt\Echo_(array()))
    +        ;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Builder/UseTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Builder/UseTest.php
    new file mode 100644
    index 0000000..adaeb3a
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Builder/UseTest.php
    @@ -0,0 +1,35 @@
    +createUseBuilder('Foo\Bar')->getNode();
    +        $this->assertEquals(new Stmt\Use_(array(
    +            new Stmt\UseUse(new Name('Foo\Bar'), 'Bar')
    +        )), $node);
    +
    +        $node = $this->createUseBuilder(new Name('Foo\Bar'))->as('XYZ')->getNode();
    +        $this->assertEquals(new Stmt\Use_(array(
    +            new Stmt\UseUse(new Name('Foo\Bar'), 'XYZ')
    +        )), $node);
    +
    +        $node = $this->createUseBuilder('foo\bar', Stmt\Use_::TYPE_FUNCTION)->as('foo')->getNode();
    +        $this->assertEquals(new Stmt\Use_(array(
    +            new Stmt\UseUse(new Name('foo\bar'), 'foo')
    +        ), Stmt\Use_::TYPE_FUNCTION), $node);
    +    }
    +
    +    public function testNonExistingMethod() {
    +        $this->setExpectedException('LogicException', 'Method "foo" does not exist');
    +        $builder = $this->createUseBuilder('Test');
    +        $builder->foo();
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/BuilderFactoryTest.php b/core/vendor/nikic/php-parser/test/PhpParser/BuilderFactoryTest.php
    new file mode 100644
    index 0000000..1c3ef18
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/BuilderFactoryTest.php
    @@ -0,0 +1,108 @@
    +assertInstanceOf($className, $factory->$methodName('test'));
    +    }
    +
    +    public function provideTestFactory() {
    +        return array(
    +            array('namespace', 'PhpParser\Builder\Namespace_'),
    +            array('class',     'PhpParser\Builder\Class_'),
    +            array('interface', 'PhpParser\Builder\Interface_'),
    +            array('trait',     'PhpParser\Builder\Trait_'),
    +            array('method',    'PhpParser\Builder\Method'),
    +            array('function',  'PhpParser\Builder\Function_'),
    +            array('property',  'PhpParser\Builder\Property'),
    +            array('param',     'PhpParser\Builder\Param'),
    +            array('use',       'PhpParser\Builder\Use_'),
    +        );
    +    }
    +
    +    public function testNonExistingMethod() {
    +        $this->setExpectedException('LogicException', 'Method "foo" does not exist');
    +        $factory = new BuilderFactory();
    +        $factory->foo();
    +    }
    +
    +    public function testIntegration() {
    +        $factory = new BuilderFactory;
    +        $node = $factory->namespace('Name\Space')
    +            ->addStmt($factory->use('Foo\Bar\SomeOtherClass'))
    +            ->addStmt($factory->use('Foo\Bar')->as('A'))
    +            ->addStmt($factory
    +                ->class('SomeClass')
    +                ->extend('SomeOtherClass')
    +                ->implement('A\Few', '\Interfaces')
    +                ->makeAbstract()
    +
    +                ->addStmt($factory->method('firstMethod'))
    +
    +                ->addStmt($factory->method('someMethod')
    +                    ->makePublic()
    +                    ->makeAbstract()
    +                    ->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
    +                    ->setDocComment('/**
    +                                      * This method does something.
    +                                      *
    +                                      * @param SomeClass And takes a parameter
    +                                      */'))
    +
    +                ->addStmt($factory->method('anotherMethod')
    +                    ->makeProtected()
    +                    ->addParam($factory->param('someParam')->setDefault('test'))
    +                    ->addStmt(new Expr\Print_(new Expr\Variable('someParam'))))
    +
    +                ->addStmt($factory->property('someProperty')->makeProtected())
    +                ->addStmt($factory->property('anotherProperty')
    +                    ->makePrivate()
    +                    ->setDefault(array(1, 2, 3))))
    +            ->getNode()
    +        ;
    +
    +        $expected = <<<'EOC'
    +prettyPrintFile($stmts);
    +
    +        $this->assertEquals(
    +            str_replace("\r\n", "\n", $expected),
    +            str_replace("\r\n", "\n", $generated)
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/CodeParsingTest.php b/core/vendor/nikic/php-parser/test/PhpParser/CodeParsingTest.php
    new file mode 100644
    index 0000000..5eb695e
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/CodeParsingTest.php
    @@ -0,0 +1,69 @@
    + array(
    +            'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
    +        )));
    +        $parser5 = new Parser\Php5($lexer, array(
    +            'throwOnError' => false,
    +        ));
    +        $parser7 = new Parser\Php7($lexer, array(
    +            'throwOnError' => false,
    +        ));
    +
    +        $output5 = $this->getParseOutput($parser5, $code);
    +        $output7 = $this->getParseOutput($parser7, $code);
    +
    +        if ($mode === 'php5') {
    +            $this->assertSame($expected, $output5, $name);
    +            $this->assertNotSame($expected, $output7, $name);
    +        } else if ($mode === 'php7') {
    +            $this->assertNotSame($expected, $output5, $name);
    +            $this->assertSame($expected, $output7, $name);
    +        } else {
    +            $this->assertSame($expected, $output5, $name);
    +            $this->assertSame($expected, $output7, $name);
    +        }
    +    }
    +
    +    private function getParseOutput(Parser $parser, $code) {
    +        $stmts = $parser->parse($code);
    +        $errors = $parser->getErrors();
    +
    +        $output = '';
    +        foreach ($errors as $error) {
    +            $output .= $this->formatErrorMessage($error, $code) . "\n";
    +        }
    +
    +        if (null !== $stmts) {
    +            $dumper = new NodeDumper(['dumpComments' => true]);
    +            $output .= $dumper->dump($stmts);
    +        }
    +
    +        return canonicalize($output);
    +    }
    +
    +    public function provideTestParse() {
    +        return $this->getTests(__DIR__ . '/../code/parser', 'test');
    +    }
    +
    +    private function formatErrorMessage(Error $e, $code) {
    +        if ($e->hasColumnInfo()) {
    +            return $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
    +                . ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
    +        } else {
    +            return $e->getMessage();
    +        }
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/CodeTestAbstract.php b/core/vendor/nikic/php-parser/test/PhpParser/CodeTestAbstract.php
    new file mode 100644
    index 0000000..369ee41
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/CodeTestAbstract.php
    @@ -0,0 +1,61 @@
    +getPathname();
    +            $fileContents = file_get_contents($fileName);
    +            $fileContents = canonicalize($fileContents);
    +
    +            // evaluate @@{expr}@@ expressions
    +            $fileContents = preg_replace_callback(
    +                '/@@\{(.*?)\}@@/',
    +                function($matches) {
    +                    return eval('return ' . $matches[1] . ';');
    +                },
    +                $fileContents
    +            );
    +
    +            // parse sections
    +            $parts = preg_split("/\n-----(?:\n|$)/", $fileContents);
    +
    +            // first part is the name
    +            $name = array_shift($parts) . ' (' . $fileName . ')';
    +            $shortName = ltrim(str_replace($directory, '', $fileName), '/\\');
    +
    +            // multiple sections possible with always two forming a pair
    +            $chunks = array_chunk($parts, 2);
    +            foreach ($chunks as $i => $chunk) {
    +                $dataSetName = $shortName . (count($chunks) > 1 ? '#' . $i : '');
    +                list($expected, $mode) = $this->extractMode($chunk[1]);
    +                $tests[$dataSetName] = array($name, $chunk[0], $expected, $mode);
    +            }
    +        }
    +
    +        return $tests;
    +    }
    +
    +    private function extractMode($expected) {
    +        $firstNewLine = strpos($expected, "\n");
    +        if (false === $firstNewLine) {
    +            $firstNewLine = strlen($expected);
    +        }
    +
    +        $firstLine = substr($expected, 0, $firstNewLine);
    +        if (0 !== strpos($firstLine, '!!')) {
    +            return [$expected, null];
    +        }
    +
    +        $expected = (string) substr($expected, $firstNewLine + 1);
    +        return [$expected, substr($firstLine, 2)];
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/CommentTest.php b/core/vendor/nikic/php-parser/test/PhpParser/CommentTest.php
    new file mode 100644
    index 0000000..fb20557
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/CommentTest.php
    @@ -0,0 +1,80 @@
    +assertSame('/* Some comment */', $comment->getText());
    +        $this->assertSame('/* Some comment */', (string) $comment);
    +        $this->assertSame(1, $comment->getLine());
    +        $this->assertSame(10, $comment->getFilePos());
    +
    +        $comment->setText('/* Some other comment */');
    +        $comment->setLine(10);
    +
    +        $this->assertSame('/* Some other comment */', $comment->getText());
    +        $this->assertSame('/* Some other comment */', (string) $comment);
    +        $this->assertSame(10, $comment->getLine());
    +    }
    +
    +    /**
    +     * @dataProvider provideTestReformatting
    +     */
    +    public function testReformatting($commentText, $reformattedText) {
    +        $comment = new Comment($commentText);
    +        $this->assertSame($reformattedText, $comment->getReformattedText());
    +    }
    +
    +    public function provideTestReformatting() {
    +        return array(
    +            array('// Some text' . "\n", '// Some text'),
    +            array('/* Some text */', '/* Some text */'),
    +            array(
    +                '/**
    +     * Some text.
    +     * Some more text.
    +     */',
    +                '/**
    + * Some text.
    + * Some more text.
    + */'
    +            ),
    +            array(
    +                '/*
    +        Some text.
    +        Some more text.
    +    */',
    +                '/*
    +    Some text.
    +    Some more text.
    +*/'
    +            ),
    +            array(
    +                '/* Some text.
    +       More text.
    +       Even more text. */',
    +                '/* Some text.
    +   More text.
    +   Even more text. */'
    +            ),
    +            array(
    +                '/* Some text.
    +       More text.
    +         Indented text. */',
    +                '/* Some text.
    +   More text.
    +     Indented text. */',
    +            ),
    +            // invalid comment -> no reformatting
    +            array(
    +                'hallo
    +    world',
    +                'hallo
    +    world',
    +            ),
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/ErrorTest.php b/core/vendor/nikic/php-parser/test/PhpParser/ErrorTest.php
    new file mode 100644
    index 0000000..021a7f8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/ErrorTest.php
    @@ -0,0 +1,112 @@
    + 10,
    +            'endLine' => 11,
    +        );
    +        $error = new Error('Some error', $attributes);
    +
    +        $this->assertSame('Some error', $error->getRawMessage());
    +        $this->assertSame($attributes, $error->getAttributes());
    +        $this->assertSame(10, $error->getStartLine());
    +        $this->assertSame(11, $error->getEndLine());
    +        $this->assertSame(10, $error->getRawLine());
    +        $this->assertSame('Some error on line 10', $error->getMessage());
    +
    +        return $error;
    +    }
    +
    +    /**
    +     * @depends testConstruct
    +     */
    +    public function testSetMessageAndLine(Error $error) {
    +        $error->setRawMessage('Some other error');
    +        $this->assertSame('Some other error', $error->getRawMessage());
    +
    +        $error->setStartLine(15);
    +        $this->assertSame(15, $error->getStartLine());
    +        $this->assertSame('Some other error on line 15', $error->getMessage());
    +
    +        $error->setRawLine(17);
    +        $this->assertSame(17, $error->getRawLine());
    +        $this->assertSame('Some other error on line 17', $error->getMessage());
    +    }
    +
    +    public function testUnknownLine() {
    +        $error = new Error('Some error');
    +
    +        $this->assertSame(-1, $error->getStartLine());
    +        $this->assertSame(-1, $error->getEndLine());
    +        $this->assertSame(-1, $error->getRawLine());
    +        $this->assertSame('Some error on unknown line', $error->getMessage());
    +    }
    +
    +    /** @dataProvider provideTestColumnInfo */
    +    public function testColumnInfo($code, $startPos, $endPos, $startColumn, $endColumn) {
    +        $error = new Error('Some error', array(
    +            'startFilePos' => $startPos,
    +            'endFilePos' => $endPos,
    +        ));
    +
    +        $this->assertSame(true, $error->hasColumnInfo());
    +        $this->assertSame($startColumn, $error->getStartColumn($code));
    +        $this->assertSame($endColumn, $error->getEndColumn($code));
    +
    +    }
    +
    +    public function provideTestColumnInfo() {
    +        return array(
    +            // Error at "bar"
    +            array("assertSame(false, $error->hasColumnInfo());
    +        try {
    +            $error->getStartColumn('');
    +            $this->fail('Expected RuntimeException');
    +        } catch (\RuntimeException $e) {
    +            $this->assertSame('Error does not have column information', $e->getMessage());
    +        }
    +        try {
    +            $error->getEndColumn('');
    +            $this->fail('Expected RuntimeException');
    +        } catch (\RuntimeException $e) {
    +            $this->assertSame('Error does not have column information', $e->getMessage());
    +        }
    +    }
    +
    +    /**
    +     * @expectedException \RuntimeException
    +     * @expectedExceptionMessage Invalid position information
    +     */
    +    public function testInvalidPosInfo() {
    +        $error = new Error('Some error', array(
    +            'startFilePos' => 10,
    +            'endFilePos' => 11,
    +        ));
    +        $error->getStartColumn('code');
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Lexer/EmulativeTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Lexer/EmulativeTest.php
    new file mode 100644
    index 0000000..f1fe618
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Lexer/EmulativeTest.php
    @@ -0,0 +1,133 @@
    +getLexer();
    +        $lexer->startLexing('assertSame($expectedToken, $lexer->getNextToken());
    +        $this->assertSame(0, $lexer->getNextToken());
    +    }
    +
    +    /**
    +     * @dataProvider provideTestReplaceKeywords
    +     */
    +    public function testNoReplaceKeywordsAfterObjectOperator($keyword) {
    +        $lexer = $this->getLexer();
    +        $lexer->startLexing('' . $keyword);
    +
    +        $this->assertSame(Tokens::T_OBJECT_OPERATOR, $lexer->getNextToken());
    +        $this->assertSame(Tokens::T_STRING, $lexer->getNextToken());
    +        $this->assertSame(0, $lexer->getNextToken());
    +    }
    +
    +    public function provideTestReplaceKeywords() {
    +        return array(
    +            // PHP 5.5
    +            array('finally',       Tokens::T_FINALLY),
    +            array('yield',         Tokens::T_YIELD),
    +
    +            // PHP 5.4
    +            array('callable',      Tokens::T_CALLABLE),
    +            array('insteadof',     Tokens::T_INSTEADOF),
    +            array('trait',         Tokens::T_TRAIT),
    +            array('__TRAIT__',     Tokens::T_TRAIT_C),
    +
    +            // PHP 5.3
    +            array('__DIR__',       Tokens::T_DIR),
    +            array('goto',          Tokens::T_GOTO),
    +            array('namespace',     Tokens::T_NAMESPACE),
    +            array('__NAMESPACE__', Tokens::T_NS_C),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideTestLexNewFeatures
    +     */
    +    public function testLexNewFeatures($code, array $expectedTokens) {
    +        $lexer = $this->getLexer();
    +        $lexer->startLexing('assertSame($expectedTokenType, $lexer->getNextToken($text));
    +            $this->assertSame($expectedTokenText, $text);
    +        }
    +        $this->assertSame(0, $lexer->getNextToken());
    +    }
    +
    +    /**
    +     * @dataProvider provideTestLexNewFeatures
    +     */
    +    public function testLeaveStuffAloneInStrings($code) {
    +        $stringifiedToken = '"' . addcslashes($code, '"\\') . '"';
    +
    +        $lexer = $this->getLexer();
    +        $lexer->startLexing('assertSame(Tokens::T_CONSTANT_ENCAPSED_STRING, $lexer->getNextToken($text));
    +        $this->assertSame($stringifiedToken, $text);
    +        $this->assertSame(0, $lexer->getNextToken());
    +    }
    +
    +    public function provideTestLexNewFeatures() {
    +        return array(
    +            array('yield from', array(
    +                array(Tokens::T_YIELD_FROM, 'yield from'),
    +            )),
    +            array("yield\r\nfrom", array(
    +                array(Tokens::T_YIELD_FROM, "yield\r\nfrom"),
    +            )),
    +            array('...', array(
    +                array(Tokens::T_ELLIPSIS, '...'),
    +            )),
    +            array('**', array(
    +                array(Tokens::T_POW, '**'),
    +            )),
    +            array('**=', array(
    +                array(Tokens::T_POW_EQUAL, '**='),
    +            )),
    +            array('??', array(
    +                array(Tokens::T_COALESCE, '??'),
    +            )),
    +            array('<=>', array(
    +                array(Tokens::T_SPACESHIP, '<=>'),
    +            )),
    +            array('0b1010110', array(
    +                array(Tokens::T_LNUMBER, '0b1010110'),
    +            )),
    +            array('0b1011010101001010110101010010101011010101010101101011001110111100', array(
    +                array(Tokens::T_DNUMBER, '0b1011010101001010110101010010101011010101010101101011001110111100'),
    +            )),
    +            array('\\', array(
    +                array(Tokens::T_NS_SEPARATOR, '\\'),
    +            )),
    +            array("<<<'NOWDOC'\nNOWDOC;\n", array(
    +                array(Tokens::T_START_HEREDOC, "<<<'NOWDOC'\n"),
    +                array(Tokens::T_END_HEREDOC, 'NOWDOC'),
    +                array(ord(';'), ';'),
    +            )),
    +            array("<<<'NOWDOC'\nFoobar\nNOWDOC;\n", array(
    +                array(Tokens::T_START_HEREDOC, "<<<'NOWDOC'\n"),
    +                array(Tokens::T_ENCAPSED_AND_WHITESPACE, "Foobar\n"),
    +                array(Tokens::T_END_HEREDOC, 'NOWDOC'),
    +                array(ord(';'), ';'),
    +            )),
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/LexerTest.php b/core/vendor/nikic/php-parser/test/PhpParser/LexerTest.php
    new file mode 100644
    index 0000000..d180e76
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/LexerTest.php
    @@ -0,0 +1,250 @@
    +markTestSkipped('HHVM does not throw warnings from token_get_all()');
    +        }
    +
    +        $lexer = $this->getLexer();
    +        try {
    +            $lexer->startLexing($code);
    +        } catch (Error $e) {
    +            $this->assertSame($message, $e->getMessage());
    +
    +            return;
    +        }
    +
    +        $this->fail('Expected PhpParser\Error');
    +    }
    +
    +    public function provideTestError() {
    +        return array(
    +            array('getLexer($options);
    +        $lexer->startLexing($code);
    +        while ($id = $lexer->getNextToken($value, $startAttributes, $endAttributes)) {
    +            $token = array_shift($tokens);
    +
    +            $this->assertSame($token[0], $id);
    +            $this->assertSame($token[1], $value);
    +            $this->assertEquals($token[2], $startAttributes);
    +            $this->assertEquals($token[3], $endAttributes);
    +        }
    +    }
    +
    +    public function provideTestLex() {
    +        return array(
    +            // tests conversion of closing PHP tag and drop of whitespace and opening tags
    +            array(
    +                'plaintext',
    +                array(),
    +                array(
    +                    array(
    +                        Tokens::T_STRING, 'tokens',
    +                        array('startLine' => 1), array('endLine' => 1)
    +                    ),
    +                    array(
    +                        ord(';'), '?>',
    +                        array('startLine' => 1), array('endLine' => 1)
    +                    ),
    +                    array(
    +                        Tokens::T_INLINE_HTML, 'plaintext',
    +                        array('startLine' => 1), array('endLine' => 1)
    +                    ),
    +                )
    +            ),
    +            // tests line numbers
    +            array(
    +                ' 2), array('endLine' => 2)
    +                    ),
    +                    array(
    +                        Tokens::T_STRING, 'token',
    +                        array('startLine' => 2), array('endLine' => 2)
    +                    ),
    +                    array(
    +                        ord('$'), '$',
    +                        array(
    +                            'startLine' => 3,
    +                            'comments' => array(
    +                                new Comment\Doc('/** doc' . "\n" . 'comment */', 2, 14),
    +                            )
    +                        ),
    +                        array('endLine' => 3)
    +                    ),
    +                )
    +            ),
    +            // tests comment extraction
    +            array(
    +                ' 2,
    +                            'comments' => array(
    +                                new Comment('/* comment */', 1, 6),
    +                                new Comment('// comment' . "\n", 1, 20),
    +                                new Comment\Doc('/** docComment 1 */', 2, 31),
    +                                new Comment\Doc('/** docComment 2 */', 2, 50),
    +                            ),
    +                        ),
    +                        array('endLine' => 2)
    +                    ),
    +                )
    +            ),
    +            // tests differing start and end line
    +            array(
    +                ' 1), array('endLine' => 2)
    +                    ),
    +                )
    +            ),
    +            // tests exact file offsets
    +            array(
    +                ' array('startFilePos', 'endFilePos')),
    +                array(
    +                    array(
    +                        Tokens::T_CONSTANT_ENCAPSED_STRING, '"a"',
    +                        array('startFilePos' => 6), array('endFilePos' => 8)
    +                    ),
    +                    array(
    +                        ord(';'), ';',
    +                        array('startFilePos' => 9), array('endFilePos' => 9)
    +                    ),
    +                    array(
    +                        Tokens::T_CONSTANT_ENCAPSED_STRING, '"b"',
    +                        array('startFilePos' => 18), array('endFilePos' => 20)
    +                    ),
    +                    array(
    +                        ord(';'), ';',
    +                        array('startFilePos' => 21), array('endFilePos' => 21)
    +                    ),
    +                )
    +            ),
    +            // tests token offsets
    +            array(
    +                ' array('startTokenPos', 'endTokenPos')),
    +                array(
    +                    array(
    +                        Tokens::T_CONSTANT_ENCAPSED_STRING, '"a"',
    +                        array('startTokenPos' => 1), array('endTokenPos' => 1)
    +                    ),
    +                    array(
    +                        ord(';'), ';',
    +                        array('startTokenPos' => 2), array('endTokenPos' => 2)
    +                    ),
    +                    array(
    +                        Tokens::T_CONSTANT_ENCAPSED_STRING, '"b"',
    +                        array('startTokenPos' => 5), array('endTokenPos' => 5)
    +                    ),
    +                    array(
    +                        ord(';'), ';',
    +                        array('startTokenPos' => 6), array('endTokenPos' => 6)
    +                    ),
    +                )
    +            ),
    +            // tests all attributes being disabled
    +            array(
    +                ' array()),
    +                array(
    +                    array(
    +                        Tokens::T_VARIABLE, '$bar',
    +                        array(), array()
    +                    ),
    +                    array(
    +                        ord(';'), ';',
    +                        array(), array()
    +                    )
    +                )
    +            )
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideTestHaltCompiler
    +     */
    +    public function testHandleHaltCompiler($code, $remaining) {
    +        $lexer = $this->getLexer();
    +        $lexer->startLexing($code);
    +
    +        while (Tokens::T_HALT_COMPILER !== $lexer->getNextToken());
    +
    +        $this->assertSame($remaining, $lexer->handleHaltCompiler());
    +        $this->assertSame(0, $lexer->getNextToken());
    +    }
    +
    +    public function provideTestHaltCompiler() {
    +        return array(
    +            array('Remaining Text', 'Remaining Text'),
    +            //array('getLexer();
    +        $lexer->startLexing('getNextToken());
    +        $lexer->handleHaltCompiler();
    +    }
    +
    +    public function testGetTokens() {
    +        $code = 'getLexer();
    +        $lexer->startLexing($code);
    +        $this->assertSame($expectedTokens, $lexer->getTokens());
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Node/NameTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Node/NameTest.php
    new file mode 100644
    index 0000000..3378551
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Node/NameTest.php
    @@ -0,0 +1,164 @@
    +assertSame(array('foo', 'bar'), $name->parts);
    +
    +        $name = new Name('foo\bar');
    +        $this->assertSame(array('foo', 'bar'), $name->parts);
    +    }
    +
    +    public function testGet() {
    +        $name = new Name('foo');
    +        $this->assertSame('foo', $name->getFirst());
    +        $this->assertSame('foo', $name->getLast());
    +
    +        $name = new Name('foo\bar');
    +        $this->assertSame('foo', $name->getFirst());
    +        $this->assertSame('bar', $name->getLast());
    +    }
    +
    +    public function testToString() {
    +        $name = new Name('foo\bar');
    +
    +        $this->assertSame('foo\bar', (string) $name);
    +        $this->assertSame('foo\bar', $name->toString());
    +        $this->assertSame('foo_bar', $name->toString('_'));
    +    }
    +
    +    public function testSet() {
    +        $name = new Name('foo');
    +
    +        $name->set('foo\bar');
    +        $this->assertSame('foo\bar', $name->toString());
    +
    +        $name->set(array('foo', 'bar'));
    +        $this->assertSame('foo\bar', $name->toString());
    +
    +        $name->set(new Name('foo\bar'));
    +        $this->assertSame('foo\bar', $name->toString());
    +    }
    +
    +    public function testSetFirst() {
    +        $name = new Name('foo');
    +
    +        $name->setFirst('bar');
    +        $this->assertSame('bar', $name->toString());
    +
    +        $name->setFirst('A\B');
    +        $this->assertSame('A\B', $name->toString());
    +
    +        $name->setFirst('C');
    +        $this->assertSame('C\B', $name->toString());
    +
    +        $name->setFirst('D\E');
    +        $this->assertSame('D\E\B', $name->toString());
    +    }
    +
    +    public function testSetLast() {
    +        $name = new Name('foo');
    +
    +        $name->setLast('bar');
    +        $this->assertSame('bar', $name->toString());
    +
    +        $name->setLast('A\B');
    +        $this->assertSame('A\B', $name->toString());
    +
    +        $name->setLast('C');
    +        $this->assertSame('A\C', $name->toString());
    +
    +        $name->setLast('D\E');
    +        $this->assertSame('A\D\E', $name->toString());
    +    }
    +
    +    public function testAppend() {
    +        $name = new Name('foo');
    +
    +        $name->append('bar');
    +        $this->assertSame('foo\bar', $name->toString());
    +
    +        $name->append('bar\foo');
    +        $this->assertSame('foo\bar\bar\foo', $name->toString());
    +    }
    +
    +    public function testPrepend() {
    +        $name = new Name('foo');
    +
    +        $name->prepend('bar');
    +        $this->assertSame('bar\foo', $name->toString());
    +
    +        $name->prepend('foo\bar');
    +        $this->assertSame('foo\bar\bar\foo', $name->toString());
    +    }
    +
    +    public function testSlice() {
    +        $name = new Name('foo\bar');
    +        $this->assertEquals(new Name('foo\bar'), $name->slice(0));
    +        $this->assertEquals(new Name('bar'), $name->slice(1));
    +        $this->assertEquals(new Name([]), $name->slice(2));
    +    }
    +
    +    /**
    +     * @expectedException \OutOfBoundsException
    +     * @expectedExceptionMessage Offset 4 is out of bounds
    +     */
    +    public function testSliceException() {
    +        (new Name('foo\bar\baz'))->slice(4);
    +    }
    +
    +    public function testConcat() {
    +        $this->assertEquals(new Name('foo\bar\baz'), Name::concat('foo', 'bar\baz'));
    +        $this->assertEquals(
    +            new Name\FullyQualified('foo\bar'),
    +            Name\FullyQualified::concat(['foo'], new Name('bar'))
    +        );
    +
    +        $attributes = ['foo' => 'bar'];
    +        $this->assertEquals(
    +            new Name\Relative('foo\bar\baz', $attributes),
    +            Name\Relative::concat(new Name\FullyQualified('foo\bar'), 'baz', $attributes)
    +        );
    +
    +        $this->assertEquals(new Name('foo'), Name::concat([], 'foo'));
    +        $this->assertEquals(new Name([]), Name::concat([], []));
    +    }
    +
    +    public function testIs() {
    +        $name = new Name('foo');
    +        $this->assertTrue ($name->isUnqualified());
    +        $this->assertFalse($name->isQualified());
    +        $this->assertFalse($name->isFullyQualified());
    +        $this->assertFalse($name->isRelative());
    +
    +        $name = new Name('foo\bar');
    +        $this->assertFalse($name->isUnqualified());
    +        $this->assertTrue ($name->isQualified());
    +        $this->assertFalse($name->isFullyQualified());
    +        $this->assertFalse($name->isRelative());
    +
    +        $name = new Name\FullyQualified('foo');
    +        $this->assertFalse($name->isUnqualified());
    +        $this->assertFalse($name->isQualified());
    +        $this->assertTrue ($name->isFullyQualified());
    +        $this->assertFalse($name->isRelative());
    +
    +        $name = new Name\Relative('foo');
    +        $this->assertFalse($name->isUnqualified());
    +        $this->assertFalse($name->isQualified());
    +        $this->assertFalse($name->isFullyQualified());
    +        $this->assertTrue ($name->isRelative());
    +    }
    +
    +    /**
    +     * @expectedException        \InvalidArgumentException
    +     * @expectedExceptionMessage When changing a name you need to pass either a string, an array or a Name node
    +     */
    +    public function testInvalidArg() {
    +        $name = new Name('foo');
    +        $name->set(new \stdClass);
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Node/Scalar/MagicConstTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Node/Scalar/MagicConstTest.php
    new file mode 100644
    index 0000000..3141f56
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Node/Scalar/MagicConstTest.php
    @@ -0,0 +1,25 @@
    +assertSame($name, $magicConst->getName());
    +    }
    +
    +    public function provideTestGetName() {
    +        return array(
    +            array(new MagicConst\Class_, '__CLASS__'),
    +            array(new MagicConst\Dir, '__DIR__'),
    +            array(new MagicConst\File, '__FILE__'),
    +            array(new MagicConst\Function_, '__FUNCTION__'),
    +            array(new MagicConst\Line, '__LINE__'),
    +            array(new MagicConst\Method, '__METHOD__'),
    +            array(new MagicConst\Namespace_, '__NAMESPACE__'),
    +            array(new MagicConst\Trait_, '__TRAIT__'),
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Node/Scalar/StringTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Node/Scalar/StringTest.php
    new file mode 100644
    index 0000000..be39035
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Node/Scalar/StringTest.php
    @@ -0,0 +1,61 @@
    +assertSame(
    +            $expected,
    +            String_::parseEscapeSequences($string, $quote)
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideTestParse
    +     */
    +    public function testCreate($expected, $string) {
    +        $this->assertSame(
    +            $expected,
    +            String_::parse($string)
    +        );
    +    }
    +
    +    public function provideTestParseEscapeSequences() {
    +        return array(
    +            array('"',              '\\"',              '"'),
    +            array('\\"',            '\\"',              '`'),
    +            array('\\"\\`',         '\\"\\`',           null),
    +            array("\\\$\n\r\t\f\v", '\\\\\$\n\r\t\f\v', null),
    +            array("\x1B",           '\e',               null),
    +            array(chr(255),         '\xFF',             null),
    +            array(chr(255),         '\377',             null),
    +            array(chr(0),           '\400',             null),
    +            array("\0",             '\0',               null),
    +            array('\xFF',           '\\\\xFF',          null),
    +        );
    +    }
    +
    +    public function provideTestParse() {
    +        $tests = array(
    +            array('A', '\'A\''),
    +            array('A', 'b\'A\''),
    +            array('A', '"A"'),
    +            array('A', 'b"A"'),
    +            array('\\', '\'\\\\\''),
    +            array('\'', '\'\\\'\''),
    +        );
    +
    +        foreach ($this->provideTestParseEscapeSequences() as $i => $test) {
    +            // skip second and third tests, they aren't for double quotes
    +            if ($i != 1 && $i != 2) {
    +                $tests[] = array($test[0], '"' . $test[1] . '"');
    +            }
    +        }
    +
    +        return $tests;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/ClassMethodTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/ClassMethodTest.php
    new file mode 100644
    index 0000000..fa8aed8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/ClassMethodTest.php
    @@ -0,0 +1,63 @@
    + constant('PhpParser\Node\Stmt\Class_::MODIFIER_' . strtoupper($modifier))
    +        ));
    +
    +        $this->assertTrue($node->{'is' . $modifier}());
    +    }
    +
    +    public function testNoModifiers() {
    +        $node = new ClassMethod('foo', array('type' => 0));
    +
    +        $this->assertTrue($node->isPublic());
    +        $this->assertFalse($node->isProtected());
    +        $this->assertFalse($node->isPrivate());
    +        $this->assertFalse($node->isAbstract());
    +        $this->assertFalse($node->isFinal());
    +        $this->assertFalse($node->isStatic());
    +    }
    +
    +    public function provideModifiers() {
    +        return array(
    +            array('public'),
    +            array('protected'),
    +            array('private'),
    +            array('abstract'),
    +            array('final'),
    +            array('static'),
    +        );
    +    }
    +
    +    /**
    +     * Checks that implicit public modifier detection for method is working
    +     *
    +     * @dataProvider implicitPublicModifiers
    +     *
    +     * @param integer $modifier Node type modifier
    +     */
    +    public function testImplicitPublic($modifier)
    +    {
    +        $node = new ClassMethod('foo', array(
    +            'type' => constant('PhpParser\Node\Stmt\Class_::MODIFIER_' . strtoupper($modifier))
    +        ));
    +
    +        $this->assertTrue($node->isPublic(), 'Node should be implicitly public');
    +    }
    +
    +    public function implicitPublicModifiers() {
    +        return array(
    +            array('abstract'),
    +            array('final'),
    +            array('static'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/ClassTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/ClassTest.php
    new file mode 100644
    index 0000000..643b15c
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/ClassTest.php
    @@ -0,0 +1,59 @@
    + Class_::MODIFIER_ABSTRACT));
    +        $this->assertTrue($class->isAbstract());
    +
    +        $class = new Class_('Foo');
    +        $this->assertFalse($class->isAbstract());
    +    }
    +
    +    public function testIsFinal() {
    +        $class = new Class_('Foo', array('type' => Class_::MODIFIER_FINAL));
    +        $this->assertTrue($class->isFinal());
    +
    +        $class = new Class_('Foo');
    +        $this->assertFalse($class->isFinal());
    +    }
    +
    +    public function testGetMethods() {
    +        $methods = array(
    +            new ClassMethod('foo'),
    +            new ClassMethod('bar'),
    +            new ClassMethod('fooBar'),
    +        );
    +        $class = new Class_('Foo', array(
    +            'stmts' => array(
    +                new TraitUse(array()),
    +                $methods[0],
    +                new ClassConst(array()),
    +                $methods[1],
    +                new Property(0, array()),
    +                $methods[2],
    +            )
    +        ));
    +
    +        $this->assertSame($methods, $class->getMethods());
    +    }
    +
    +    public function testGetMethod() {
    +        $methodConstruct = new ClassMethod('__CONSTRUCT');
    +        $methodTest = new ClassMethod('test');
    +        $class = new Class_('Foo', array(
    +            'stmts' => array(
    +                new ClassConst(array()),
    +                $methodConstruct,
    +                new Property(0, array()),
    +                $methodTest,
    +            )
    +        ));
    +
    +        $this->assertSame($methodConstruct, $class->getMethod('__construct'));
    +        $this->assertSame($methodTest, $class->getMethod('test'));
    +        $this->assertNull($class->getMethod('nonExisting'));
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/InterfaceTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/InterfaceTest.php
    new file mode 100644
    index 0000000..c499058
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/InterfaceTest.php
    @@ -0,0 +1,26 @@
    + array(
    +                new Node\Stmt\ClassConst(array(new Node\Const_('C1', new Node\Scalar\String_('C1')))),
    +                $methods[0],
    +                new Node\Stmt\ClassConst(array(new Node\Const_('C2', new Node\Scalar\String_('C2')))),
    +                $methods[1],
    +                new Node\Stmt\ClassConst(array(new Node\Const_('C3', new Node\Scalar\String_('C3')))),
    +            )
    +        ));
    +
    +        $this->assertSame($methods, $interface->getMethods());
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/PropertyTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/PropertyTest.php
    new file mode 100644
    index 0000000..bcfc0c6
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Node/Stmt/PropertyTest.php
    @@ -0,0 +1,44 @@
    +assertTrue($node->{'is' . $modifier}());
    +    }
    +
    +    public function testNoModifiers() {
    +        $node = new Property(0, array());
    +
    +        $this->assertTrue($node->isPublic());
    +        $this->assertFalse($node->isProtected());
    +        $this->assertFalse($node->isPrivate());
    +        $this->assertFalse($node->isStatic());
    +    }
    +
    +    public function testStaticImplicitlyPublic() {
    +        $node = new Property(Class_::MODIFIER_STATIC, array());
    +        $this->assertTrue($node->isPublic());
    +        $this->assertFalse($node->isProtected());
    +        $this->assertFalse($node->isPrivate());
    +        $this->assertTrue($node->isStatic());
    +    }
    +
    +    public function provideModifiers() {
    +        return array(
    +            array('public'),
    +            array('protected'),
    +            array('private'),
    +            array('static'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/NodeAbstractTest.php b/core/vendor/nikic/php-parser/test/PhpParser/NodeAbstractTest.php
    new file mode 100644
    index 0000000..40e0bb8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/NodeAbstractTest.php
    @@ -0,0 +1,147 @@
    +subNode1 = $subNode1;
    +        $this->subNode2 = $subNode2;
    +    }
    +
    +    public function getSubNodeNames() {
    +        return array('subNode1', 'subNode2');
    +    }
    +
    +    // This method is only overwritten because the node is located in an unusual namespace
    +    public function getType() {
    +        return 'Dummy';
    +    }
    +}
    +
    +class NodeAbstractTest extends \PHPUnit_Framework_TestCase
    +{
    +    public function provideNodes() {
    +        $attributes = array(
    +            'startLine' => 10,
    +            'comments'  => array(
    +                new Comment('// Comment' . "\n"),
    +                new Comment\Doc('/** doc comment */'),
    +            ),
    +        );
    +
    +        $node = new DummyNode('value1', 'value2', $attributes);
    +        $node->notSubNode = 'value3';
    +
    +        return array(
    +            array($attributes, $node),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideNodes
    +     */
    +    public function testConstruct(array $attributes, Node $node) {
    +        $this->assertSame('Dummy', $node->getType());
    +        $this->assertSame(array('subNode1', 'subNode2'), $node->getSubNodeNames());
    +        $this->assertSame(10, $node->getLine());
    +        $this->assertSame('/** doc comment */', $node->getDocComment()->getText());
    +        $this->assertSame('value1', $node->subNode1);
    +        $this->assertSame('value2', $node->subNode2);
    +        $this->assertTrue(isset($node->subNode1));
    +        $this->assertTrue(isset($node->subNode2));
    +        $this->assertFalse(isset($node->subNode3));
    +        $this->assertSame($attributes, $node->getAttributes());
    +
    +        return $node;
    +    }
    +
    +    /**
    +     * @dataProvider provideNodes
    +     */
    +    public function testGetDocComment(array $attributes, Node $node) {
    +        $this->assertSame('/** doc comment */', $node->getDocComment()->getText());
    +        array_pop($node->getAttribute('comments')); // remove doc comment
    +        $this->assertNull($node->getDocComment());
    +        array_pop($node->getAttribute('comments')); // remove comment
    +        $this->assertNull($node->getDocComment());
    +    }
    +
    +    /**
    +     * @dataProvider provideNodes
    +     */
    +    public function testChange(array $attributes, Node $node) {
    +        // change of line
    +        $node->setLine(15);
    +        $this->assertSame(15, $node->getLine());
    +
    +        // direct modification
    +        $node->subNode = 'newValue';
    +        $this->assertSame('newValue', $node->subNode);
    +
    +        // indirect modification
    +        $subNode =& $node->subNode;
    +        $subNode = 'newNewValue';
    +        $this->assertSame('newNewValue', $node->subNode);
    +
    +        // removal
    +        unset($node->subNode);
    +        $this->assertFalse(isset($node->subNode));
    +    }
    +
    +    /**
    +     * @dataProvider provideNodes
    +     */
    +    public function testIteration(array $attributes, Node $node) {
    +        // Iteration is simple object iteration over properties,
    +        // not over subnodes
    +        $i = 0;
    +        foreach ($node as $key => $value) {
    +            if ($i === 0) {
    +                $this->assertSame('subNode1', $key);
    +                $this->assertSame('value1', $value);
    +            } else if ($i === 1) {
    +                $this->assertSame('subNode2', $key);
    +                $this->assertSame('value2', $value);
    +            } else if ($i === 2) {
    +                $this->assertSame('notSubNode', $key);
    +                $this->assertSame('value3', $value);
    +            } else {
    +                throw new \Exception;
    +            }
    +            $i++;
    +        }
    +        $this->assertSame(3, $i);
    +    }
    +
    +    public function testAttributes() {
    +        /** @var $node Node */
    +        $node = $this->getMockForAbstractClass('PhpParser\NodeAbstract');
    +
    +        $this->assertEmpty($node->getAttributes());
    +
    +        $node->setAttribute('key', 'value');
    +        $this->assertTrue($node->hasAttribute('key'));
    +        $this->assertSame('value', $node->getAttribute('key'));
    +
    +        $this->assertFalse($node->hasAttribute('doesNotExist'));
    +        $this->assertNull($node->getAttribute('doesNotExist'));
    +        $this->assertSame('default', $node->getAttribute('doesNotExist', 'default'));
    +
    +        $node->setAttribute('null', null);
    +        $this->assertTrue($node->hasAttribute('null'));
    +        $this->assertNull($node->getAttribute('null'));
    +        $this->assertNull($node->getAttribute('null', 'default'));
    +
    +        $this->assertSame(
    +            array(
    +                'key'  => 'value',
    +                'null' => null,
    +            ),
    +            $node->getAttributes()
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/NodeDumperTest.php b/core/vendor/nikic/php-parser/test/PhpParser/NodeDumperTest.php
    new file mode 100644
    index 0000000..306bc20
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/NodeDumperTest.php
    @@ -0,0 +1,72 @@
    +assertSame($this->canonicalize($dump), $this->canonicalize($dumper->dump($node)));
    +    }
    +
    +    public function provideTestDump() {
    +        return array(
    +            array(
    +                array(),
    +'array(
    +)'
    +            ),
    +            array(
    +                array('Foo', 'Bar', 'Key' => 'FooBar'),
    +'array(
    +    0: Foo
    +    1: Bar
    +    Key: FooBar
    +)'
    +            ),
    +            array(
    +                new Node\Name(array('Hallo', 'World')),
    +'Name(
    +    parts: array(
    +        0: Hallo
    +        1: World
    +    )
    +)'
    +            ),
    +            array(
    +                new Node\Expr\Array_(array(
    +                    new Node\Expr\ArrayItem(new Node\Scalar\String_('Foo'))
    +                )),
    +'Expr_Array(
    +    items: array(
    +        0: Expr_ArrayItem(
    +            key: null
    +            value: Scalar_String(
    +                value: Foo
    +            )
    +            byRef: false
    +        )
    +    )
    +)'
    +            ),
    +        );
    +    }
    +
    +    /**
    +     * @expectedException        \InvalidArgumentException
    +     * @expectedExceptionMessage Can only dump nodes and arrays.
    +     */
    +    public function testError() {
    +        $dumper = new NodeDumper;
    +        $dumper->dump(new \stdClass);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/NodeTraverserTest.php b/core/vendor/nikic/php-parser/test/PhpParser/NodeTraverserTest.php
    new file mode 100644
    index 0000000..d750e14
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/NodeTraverserTest.php
    @@ -0,0 +1,218 @@
    +getMock('PhpParser\NodeVisitor');
    +
    +        $visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts);
    +        $visitor->expects($this->at(1))->method('enterNode')->with($echoNode);
    +        $visitor->expects($this->at(2))->method('enterNode')->with($str1Node);
    +        $visitor->expects($this->at(3))->method('leaveNode')->with($str1Node);
    +        $visitor->expects($this->at(4))->method('enterNode')->with($str2Node);
    +        $visitor->expects($this->at(5))->method('leaveNode')->with($str2Node);
    +        $visitor->expects($this->at(6))->method('leaveNode')->with($echoNode);
    +        $visitor->expects($this->at(7))->method('afterTraverse')->with($stmts);
    +
    +        $traverser = new NodeTraverser;
    +        $traverser->addVisitor($visitor);
    +
    +        $this->assertEquals($stmts, $traverser->traverse($stmts));
    +    }
    +
    +    public function testModifying() {
    +        $str1Node  = new String_('Foo');
    +        $str2Node  = new String_('Bar');
    +        $printNode = new Expr\Print_($str1Node);
    +
    +        // first visitor changes the node, second verifies the change
    +        $visitor1 = $this->getMock('PhpParser\NodeVisitor');
    +        $visitor2 = $this->getMock('PhpParser\NodeVisitor');
    +
    +        // replace empty statements with string1 node
    +        $visitor1->expects($this->at(0))->method('beforeTraverse')->with(array())
    +                 ->will($this->returnValue(array($str1Node)));
    +        $visitor2->expects($this->at(0))->method('beforeTraverse')->with(array($str1Node));
    +
    +        // replace string1 node with print node
    +        $visitor1->expects($this->at(1))->method('enterNode')->with($str1Node)
    +                 ->will($this->returnValue($printNode));
    +        $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
    +
    +        // replace string1 node with string2 node
    +        $visitor1->expects($this->at(2))->method('enterNode')->with($str1Node)
    +                 ->will($this->returnValue($str2Node));
    +        $visitor2->expects($this->at(2))->method('enterNode')->with($str2Node);
    +
    +        // replace string2 node with string1 node again
    +        $visitor1->expects($this->at(3))->method('leaveNode')->with($str2Node)
    +                 ->will($this->returnValue($str1Node));
    +        $visitor2->expects($this->at(3))->method('leaveNode')->with($str1Node);
    +
    +        // replace print node with string1 node again
    +        $visitor1->expects($this->at(4))->method('leaveNode')->with($printNode)
    +                 ->will($this->returnValue($str1Node));
    +        $visitor2->expects($this->at(4))->method('leaveNode')->with($str1Node);
    +
    +        // replace string1 node with empty statements again
    +        $visitor1->expects($this->at(5))->method('afterTraverse')->with(array($str1Node))
    +                 ->will($this->returnValue(array()));
    +        $visitor2->expects($this->at(5))->method('afterTraverse')->with(array());
    +
    +        $traverser = new NodeTraverser;
    +        $traverser->addVisitor($visitor1);
    +        $traverser->addVisitor($visitor2);
    +
    +        // as all operations are reversed we end where we start
    +        $this->assertEquals(array(), $traverser->traverse(array()));
    +    }
    +
    +    public function testRemove() {
    +        $str1Node = new String_('Foo');
    +        $str2Node = new String_('Bar');
    +
    +        $visitor = $this->getMock('PhpParser\NodeVisitor');
    +
    +        // remove the string1 node, leave the string2 node
    +        $visitor->expects($this->at(2))->method('leaveNode')->with($str1Node)
    +                ->will($this->returnValue(false));
    +
    +        $traverser = new NodeTraverser;
    +        $traverser->addVisitor($visitor);
    +
    +        $this->assertEquals(array($str2Node), $traverser->traverse(array($str1Node, $str2Node)));
    +    }
    +
    +    public function testMerge() {
    +        $strStart  = new String_('Start');
    +        $strMiddle = new String_('End');
    +        $strEnd    = new String_('Middle');
    +        $strR1     = new String_('Replacement 1');
    +        $strR2     = new String_('Replacement 2');
    +
    +        $visitor = $this->getMock('PhpParser\NodeVisitor');
    +
    +        // replace strMiddle with strR1 and strR2 by merge
    +        $visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle)
    +                ->will($this->returnValue(array($strR1, $strR2)));
    +
    +        $traverser = new NodeTraverser;
    +        $traverser->addVisitor($visitor);
    +
    +        $this->assertEquals(
    +            array($strStart, $strR1, $strR2, $strEnd),
    +            $traverser->traverse(array($strStart, $strMiddle, $strEnd))
    +        );
    +    }
    +
    +    public function testDeepArray() {
    +        $strNode = new String_('Foo');
    +        $stmts = array(array(array($strNode)));
    +
    +        $visitor = $this->getMock('PhpParser\NodeVisitor');
    +        $visitor->expects($this->at(1))->method('enterNode')->with($strNode);
    +
    +        $traverser = new NodeTraverser;
    +        $traverser->addVisitor($visitor);
    +
    +        $this->assertEquals($stmts, $traverser->traverse($stmts));
    +    }
    +
    +    public function testDontTraverseChildren() {
    +        $strNode = new String_('str');
    +        $printNode = new Expr\Print_($strNode);
    +        $varNode = new Expr\Variable('foo');
    +        $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode);
    +        $negNode = new Expr\UnaryMinus($mulNode);
    +        $stmts = array($printNode, $negNode);
    +
    +        $visitor1 = $this->getMock('PhpParser\NodeVisitor');
    +        $visitor2 = $this->getMock('PhpParser\NodeVisitor');
    +
    +        $visitor1->expects($this->at(1))->method('enterNode')->with($printNode)
    +            ->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
    +        $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
    +
    +        $visitor1->expects($this->at(2))->method('leaveNode')->with($printNode);
    +        $visitor2->expects($this->at(2))->method('leaveNode')->with($printNode);
    +
    +        $visitor1->expects($this->at(3))->method('enterNode')->with($negNode);
    +        $visitor2->expects($this->at(3))->method('enterNode')->with($negNode);
    +
    +        $visitor1->expects($this->at(4))->method('enterNode')->with($mulNode);
    +        $visitor2->expects($this->at(4))->method('enterNode')->with($mulNode)
    +            ->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
    +
    +        $visitor1->expects($this->at(5))->method('leaveNode')->with($mulNode);
    +        $visitor2->expects($this->at(5))->method('leaveNode')->with($mulNode);
    +
    +        $visitor1->expects($this->at(6))->method('leaveNode')->with($negNode);
    +        $visitor2->expects($this->at(6))->method('leaveNode')->with($negNode);
    +
    +        $traverser = new NodeTraverser;
    +        $traverser->addVisitor($visitor1);
    +        $traverser->addVisitor($visitor2);
    +
    +        $this->assertEquals($stmts, $traverser->traverse($stmts));
    +    }
    +
    +    public function testRemovingVisitor() {
    +        $visitor1 = $this->getMock('PhpParser\NodeVisitor');
    +        $visitor2 = $this->getMock('PhpParser\NodeVisitor');
    +        $visitor3 = $this->getMock('PhpParser\NodeVisitor');
    +
    +        $traverser = new NodeTraverser;
    +        $traverser->addVisitor($visitor1);
    +        $traverser->addVisitor($visitor2);
    +        $traverser->addVisitor($visitor3);
    +
    +        $preExpected = array($visitor1, $visitor2, $visitor3);
    +        $this->assertAttributeSame($preExpected, 'visitors', $traverser, 'The appropriate visitors have not been added');
    +
    +        $traverser->removeVisitor($visitor2);
    +
    +        $postExpected = array(0 => $visitor1, 2 => $visitor3);
    +        $this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal');
    +    }
    +
    +    public function testCloneNodes() {
    +        $stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar'))));
    +
    +        $traverser = new NodeTraverser(true);
    +
    +        $this->assertNotSame($stmts, $traverser->traverse($stmts));
    +    }
    +
    +    public function testNoCloneNodesByDefault() {
    +        $stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar'))));
    +
    +        $traverser = new NodeTraverser;
    +
    +        $this->assertSame($stmts, $traverser->traverse($stmts));
    +    }
    +
    +    /**
    +     * @expectedException \LogicException
    +     * @expectedExceptionMessage leaveNode() may only return an array if the parent structure is an array
    +     */
    +    public function testReplaceByArrayOnlyAllowedIfParentIsArray() {
    +        $stmts = array(new Node\Expr\UnaryMinus(new Node\Scalar\LNumber(42)));
    +
    +        $visitor = $this->getMock('PhpParser\NodeVisitor');
    +        $visitor->method('leaveNode')->willReturn(array(new Node\Scalar\DNumber(42.0)));
    +
    +        $traverser = new NodeTraverser();
    +        $traverser->addVisitor($visitor);
    +        $traverser->traverse($stmts);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/NodeVisitor/NameResolverTest.php b/core/vendor/nikic/php-parser/test/PhpParser/NodeVisitor/NameResolverTest.php
    new file mode 100644
    index 0000000..ecc443f
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/NodeVisitor/NameResolverTest.php
    @@ -0,0 +1,417 @@
    +addVisitor(new NameResolver);
    +
    +        $stmts = $parser->parse($code);
    +        $stmts = $traverser->traverse($stmts);
    +
    +        $this->assertSame(
    +            $this->canonicalize($expectedCode),
    +            $prettyPrinter->prettyPrint($stmts)
    +        );
    +    }
    +
    +    /**
    +     * @covers PhpParser\NodeVisitor\NameResolver
    +     */
    +    public function testResolveLocations() {
    +        $code = <<<'EOC'
    +addVisitor(new NameResolver);
    +
    +        $stmts = $parser->parse($code);
    +        $stmts = $traverser->traverse($stmts);
    +
    +        $this->assertSame(
    +            $this->canonicalize($expectedCode),
    +            $prettyPrinter->prettyPrint($stmts)
    +        );
    +    }
    +
    +    public function testNoResolveSpecialName() {
    +        $stmts = array(new Node\Expr\New_(new Name('self')));
    +
    +        $traverser = new PhpParser\NodeTraverser;
    +        $traverser->addVisitor(new NameResolver);
    +
    +        $this->assertEquals($stmts, $traverser->traverse($stmts));
    +    }
    +
    +    public function testAddNamespacedName() {
    +        $nsStmts = array(
    +            new Stmt\Class_('A'),
    +            new Stmt\Interface_('B'),
    +            new Stmt\Function_('C'),
    +            new Stmt\Const_(array(
    +                new Node\Const_('D', new Node\Scalar\LNumber(42))
    +            )),
    +            new Stmt\Trait_('E'),
    +            new Expr\New_(new Stmt\Class_(null)),
    +        );
    +
    +        $traverser = new PhpParser\NodeTraverser;
    +        $traverser->addVisitor(new NameResolver);
    +
    +        $stmts = $traverser->traverse([new Stmt\Namespace_(new Name('NS'), $nsStmts)]);
    +        $this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
    +        $this->assertSame('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
    +        $this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
    +        $this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
    +        $this->assertSame('NS\\E', (string) $stmts[0]->stmts[4]->namespacedName);
    +        $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
    +
    +        $stmts = $traverser->traverse([new Stmt\Namespace_(null, $nsStmts)]);
    +        $this->assertSame('A',     (string) $stmts[0]->stmts[0]->namespacedName);
    +        $this->assertSame('B',     (string) $stmts[0]->stmts[1]->namespacedName);
    +        $this->assertSame('C',     (string) $stmts[0]->stmts[2]->namespacedName);
    +        $this->assertSame('D',     (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
    +        $this->assertSame('E',     (string) $stmts[0]->stmts[4]->namespacedName);
    +        $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
    +    }
    +
    +    /**
    +     * @dataProvider provideTestError
    +     */
    +    public function testError(Node $stmt, $errorMsg) {
    +        $this->setExpectedException('PhpParser\Error', $errorMsg);
    +
    +        $traverser = new PhpParser\NodeTraverser;
    +        $traverser->addVisitor(new NameResolver);
    +        $traverser->traverse(array($stmt));
    +    }
    +
    +    public function provideTestError() {
    +        return array(
    +            array(
    +                new Stmt\Use_(array(
    +                    new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
    +                    new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
    +                ), Stmt\Use_::TYPE_NORMAL),
    +                'Cannot use C\D as B because the name is already in use on line 2'
    +            ),
    +            array(
    +                new Stmt\Use_(array(
    +                    new Stmt\UseUse(new Name('a\b'), 'b', 0, array('startLine' => 1)),
    +                    new Stmt\UseUse(new Name('c\d'), 'B', 0, array('startLine' => 2)),
    +                ), Stmt\Use_::TYPE_FUNCTION),
    +                'Cannot use function c\d as B because the name is already in use on line 2'
    +            ),
    +            array(
    +                new Stmt\Use_(array(
    +                    new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
    +                    new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
    +                ), Stmt\Use_::TYPE_CONSTANT),
    +                'Cannot use const C\D as B because the name is already in use on line 2'
    +            ),
    +            array(
    +                new Expr\New_(new Name\FullyQualified('self', array('startLine' => 3))),
    +                "'\\self' is an invalid class name on line 3"
    +            ),
    +            array(
    +                new Expr\New_(new Name\Relative('self', array('startLine' => 3))),
    +                "'\\self' is an invalid class name on line 3"
    +            ),
    +            array(
    +                new Expr\New_(new Name\FullyQualified('PARENT', array('startLine' => 3))),
    +                "'\\PARENT' is an invalid class name on line 3"
    +            ),
    +            array(
    +                new Expr\New_(new Name\Relative('STATIC', array('startLine' => 3))),
    +                "'\\STATIC' is an invalid class name on line 3"
    +            ),
    +        );
    +    }
    +
    +    public function testClassNameIsCaseInsensitive()
    +    {
    +        $source = <<<'EOC'
    +parse($source);
    +
    +        $traverser = new PhpParser\NodeTraverser;
    +        $traverser->addVisitor(new NameResolver);
    +
    +        $stmts = $traverser->traverse($stmts);
    +        $stmt = $stmts[0];
    +
    +        $this->assertSame(array('Bar', 'Baz'), $stmt->stmts[1]->expr->class->parts);
    +    }
    +
    +    public function testSpecialClassNamesAreCaseInsensitive() {
    +        $source = <<<'EOC'
    +parse($source);
    +
    +        $traverser = new PhpParser\NodeTraverser;
    +        $traverser->addVisitor(new NameResolver);
    +
    +        $stmts = $traverser->traverse($stmts);
    +        $classStmt = $stmts[0];
    +        $methodStmt = $classStmt->stmts[0]->stmts[0];
    +
    +        $this->assertSame('SELF', (string)$methodStmt->stmts[0]->class);
    +        $this->assertSame('PARENT', (string)$methodStmt->stmts[1]->class);
    +        $this->assertSame('STATIC', (string)$methodStmt->stmts[2]->class);
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Parser/MultipleTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Parser/MultipleTest.php
    new file mode 100644
    index 0000000..e2722c3
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Parser/MultipleTest.php
    @@ -0,0 +1,113 @@
    + []]);
    +        return new Multiple([new Php7($lexer), new Php5($lexer)]);
    +    }
    +
    +    private function getPrefer5() {
    +        $lexer = new Lexer(['usedAttributes' => []]);
    +        return new Multiple([new Php5($lexer), new Php7($lexer)]);
    +    }
    +
    +    /** @dataProvider provideTestParse */
    +    public function testParse($code, Multiple $parser, $expected) {
    +        $this->assertEquals($expected, $parser->parse($code));
    +        $this->assertSame([], $parser->getErrors());
    +    }
    +
    +    public function provideTestParse() {
    +        return [
    +            [
    +                // PHP 7 only code
    +                'getPrefer5(),
    +                [
    +                    new Stmt\Class_('Test', ['stmts' => [
    +                        new Stmt\ClassMethod('function')
    +                    ]]),
    +                ]
    +            ],
    +            [
    +                // PHP 5 only code
    +                'b;',
    +                $this->getPrefer7(),
    +                [
    +                    new Stmt\Global_([
    +                        new Expr\Variable(new Expr\PropertyFetch(new Expr\Variable('a'), 'b'))
    +                    ])
    +                ]
    +            ],
    +            [
    +                // Different meaning (PHP 5)
    +                'getPrefer5(),
    +                [
    +                    new Expr\Variable(
    +                        new Expr\ArrayDimFetch(new Expr\Variable('a'), LNumber::fromString('0'))
    +                    )
    +                ]
    +            ],
    +            [
    +                // Different meaning (PHP 7)
    +                'getPrefer7(),
    +                [
    +                    new Expr\ArrayDimFetch(
    +                        new Expr\Variable(new Expr\Variable('a')), LNumber::fromString('0')
    +                    )
    +                ]
    +            ],
    +        ];
    +    }
    +
    +    public function testThrownError() {
    +        $this->setExpectedException('PhpParser\Error', 'FAIL A');
    +
    +        $parserA = $this->getMockBuilder('PhpParser\Parser')->getMock();
    +        $parserA->expects($this->at(0))
    +            ->method('parse')->will($this->throwException(new Error('FAIL A')));
    +
    +        $parserB = $this->getMockBuilder('PhpParser\Parser')->getMock();
    +        $parserB->expects($this->at(0))
    +            ->method('parse')->will($this->throwException(new Error('FAIL B')));
    +
    +        $parser = new Multiple([$parserA, $parserB]);
    +        $parser->parse('dummy');
    +    }
    +
    +    public function testGetErrors() {
    +        $errorsA = [new Error('A1'), new Error('A2')];
    +        $parserA = $this->getMockBuilder('PhpParser\Parser')->getMock();
    +        $parserA->expects($this->at(0))->method('parse');
    +        $parserA->expects($this->at(1))
    +            ->method('getErrors')->will($this->returnValue($errorsA));
    +
    +        $errorsB = [new Error('B1'), new Error('B2')];
    +        $parserB = $this->getMockBuilder('PhpParser\Parser')->getMock();
    +        $parserB->expects($this->at(0))->method('parse');
    +        $parserB->expects($this->at(1))
    +            ->method('getErrors')->will($this->returnValue($errorsB));
    +
    +        $parser = new Multiple([$parserA, $parserB]);
    +        $parser->parse('dummy');
    +        $this->assertSame($errorsA, $parser->getErrors());
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Parser/Php5Test.php b/core/vendor/nikic/php-parser/test/PhpParser/Parser/Php5Test.php
    new file mode 100644
    index 0000000..58c4e61
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Parser/Php5Test.php
    @@ -0,0 +1,14 @@
    +assertInstanceOf($expected, (new ParserFactory)->create($kind, $lexer));
    +    }
    +
    +    public function provideTestCreate() {
    +        $lexer = new Lexer();
    +        return [
    +            [
    +                ParserFactory::PREFER_PHP7, $lexer,
    +                'PhpParser\Parser\Multiple'
    +            ],
    +            [
    +                ParserFactory::PREFER_PHP5, null,
    +                'PhpParser\Parser\Multiple'
    +            ],
    +            [
    +                ParserFactory::ONLY_PHP7, null,
    +                'PhpParser\Parser\Php7'
    +            ],
    +            [
    +                ParserFactory::ONLY_PHP5, $lexer,
    +                'PhpParser\Parser\Php5'
    +            ]
    +        ];
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/ParserTest.php b/core/vendor/nikic/php-parser/test/PhpParser/ParserTest.php
    new file mode 100644
    index 0000000..1318d8b
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/ParserTest.php
    @@ -0,0 +1,170 @@
    +getParser(new Lexer());
    +        $parser->parse('getParser(new Lexer());
    +        $parser->parse(' array(
    +                'comments', 'startLine', 'endLine',
    +                'startTokenPos', 'endTokenPos',
    +            )
    +        ));
    +
    +        $code = <<<'EOC'
    +getParser($lexer);
    +        $stmts = $parser->parse($code);
    +
    +        /** @var \PhpParser\Node\Stmt\Function_ $fn */
    +        $fn = $stmts[0];
    +        $this->assertInstanceOf('PhpParser\Node\Stmt\Function_', $fn);
    +        $this->assertEquals(array(
    +            'comments' => array(
    +                new Comment\Doc('/** Doc comment */', 2, 6),
    +            ),
    +            'startLine' => 3,
    +            'endLine' => 7,
    +            'startTokenPos' => 3,
    +            'endTokenPos' => 21,
    +        ), $fn->getAttributes());
    +
    +        $param = $fn->params[0];
    +        $this->assertInstanceOf('PhpParser\Node\Param', $param);
    +        $this->assertEquals(array(
    +            'startLine' => 3,
    +            'endLine' => 3,
    +            'startTokenPos' => 7,
    +            'endTokenPos' => 7,
    +        ), $param->getAttributes());
    +
    +        /** @var \PhpParser\Node\Stmt\Echo_ $echo */
    +        $echo = $fn->stmts[0];
    +        $this->assertInstanceOf('PhpParser\Node\Stmt\Echo_', $echo);
    +        $this->assertEquals(array(
    +            'comments' => array(
    +                new Comment("// Line\n", 4, 49),
    +                new Comment("// Comments\n", 5, 61),
    +            ),
    +            'startLine' => 6,
    +            'endLine' => 6,
    +            'startTokenPos' => 16,
    +            'endTokenPos' => 19,
    +        ), $echo->getAttributes());
    +
    +        /** @var \PhpParser\Node\Expr\Variable $var */
    +        $var = $echo->exprs[0];
    +        $this->assertInstanceOf('PhpParser\Node\Expr\Variable', $var);
    +        $this->assertEquals(array(
    +            'startLine' => 6,
    +            'endLine' => 6,
    +            'startTokenPos' => 18,
    +            'endTokenPos' => 18,
    +        ), $var->getAttributes());
    +    }
    +
    +    /**
    +     * @expectedException \RangeException
    +     * @expectedExceptionMessage The lexer returned an invalid token (id=999, value=foobar)
    +     */
    +    public function testInvalidToken() {
    +        $lexer = new InvalidTokenLexer;
    +        $parser = $this->getParser($lexer);
    +        $parser->parse('dummy');
    +    }
    +
    +    /**
    +     * @dataProvider provideTestKindAttributes
    +     */
    +    public function testKindAttributes($code, $expectedAttributes) {
    +        $parser = $this->getParser(new Lexer);
    +        $stmts = $parser->parse("getAttributes();
    +        foreach ($expectedAttributes as $name => $value) {
    +            $this->assertSame($value, $attributes[$name]);
    +        }
    +    }
    +
    +    public function provideTestKindAttributes() {
    +        return array(
    +            array('0', ['kind' => Scalar\LNumber::KIND_DEC]),
    +            array('9', ['kind' => Scalar\LNumber::KIND_DEC]),
    +            array('07', ['kind' => Scalar\LNumber::KIND_OCT]),
    +            array('0xf', ['kind' => Scalar\LNumber::KIND_HEX]),
    +            array('0XF', ['kind' => Scalar\LNumber::KIND_HEX]),
    +            array('0b1', ['kind' => Scalar\LNumber::KIND_BIN]),
    +            array('0B1', ['kind' => Scalar\LNumber::KIND_BIN]),
    +            array('[]', ['kind' => Expr\Array_::KIND_SHORT]),
    +            array('array()', ['kind' => Expr\Array_::KIND_LONG]),
    +            array("'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]),
    +            array("b'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]),
    +            array("B'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]),
    +            array('"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
    +            array('b"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
    +            array('B"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
    +            array('"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
    +            array('b"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
    +            array('B"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
    +            array("<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']),
    +            array("<< String_::KIND_HEREDOC, 'docLabel' => 'STR']),
    +            array("<<<\"STR\"\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
    +            array("b<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']),
    +            array("B<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']),
    +            array("<<< \t 'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']),
    +            // HHVM doesn't support this due to a lexer bug
    +            // (https://github.com/facebook/hhvm/issues/6970)
    +            // array("<<<'\xff'\n\xff\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => "\xff"]),
    +            array("<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
    +            array("b<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
    +            array("B<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
    +            array("<<< \t \"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
    +            array("die", ['kind' => Expr\Exit_::KIND_DIE]),
    +            array("die('done')", ['kind' => Expr\Exit_::KIND_DIE]),
    +            array("exit", ['kind' => Expr\Exit_::KIND_EXIT]),
    +            array("exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]),
    +        );
    +    }
    +}
    +
    +class InvalidTokenLexer extends Lexer {
    +    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
    +        $value = 'foobar';
    +        return 999;
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/PrettyPrinterTest.php b/core/vendor/nikic/php-parser/test/PhpParser/PrettyPrinterTest.php
    new file mode 100644
    index 0000000..9dbd3d3
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/PrettyPrinterTest.php
    @@ -0,0 +1,164 @@
    +parseModeLine($modeLine);
    +        $prettyPrinter = new Standard($options);
    +
    +        try {
    +            $output5 = canonicalize($prettyPrinter->$method($parser5->parse($code)));
    +        } catch (Error $e) {
    +            $output5 = null;
    +            if ('php7' !== $version) {
    +                throw $e;
    +            }
    +        }
    +
    +        try {
    +            $output7 = canonicalize($prettyPrinter->$method($parser7->parse($code)));
    +        } catch (Error $e) {
    +            $output7 = null;
    +            if ('php5' !== $version) {
    +                throw $e;
    +            }
    +        }
    +
    +        if ('php5' === $version) {
    +            $this->assertSame($expected, $output5, $name);
    +            $this->assertNotSame($expected, $output7, $name);
    +        } else if ('php7' === $version) {
    +            $this->assertSame($expected, $output7, $name);
    +            $this->assertNotSame($expected, $output5, $name);
    +        } else {
    +            $this->assertSame($expected, $output5, $name);
    +            $this->assertSame($expected, $output7, $name);
    +        }
    +    }
    +
    +    /**
    +     * @dataProvider provideTestPrettyPrint
    +     * @covers PhpParser\PrettyPrinter\Standard
    +     */
    +    public function testPrettyPrint($name, $code, $expected, $mode) {
    +        $this->doTestPrettyPrintMethod('prettyPrint', $name, $code, $expected, $mode);
    +    }
    +
    +    /**
    +     * @dataProvider provideTestPrettyPrintFile
    +     * @covers PhpParser\PrettyPrinter\Standard
    +     */
    +    public function testPrettyPrintFile($name, $code, $expected, $mode) {
    +        $this->doTestPrettyPrintMethod('prettyPrintFile', $name, $code, $expected, $mode);
    +    }
    +
    +    public function provideTestPrettyPrint() {
    +        return $this->getTests(__DIR__ . '/../code/prettyPrinter', 'test');
    +    }
    +
    +    public function provideTestPrettyPrintFile() {
    +        return $this->getTests(__DIR__ . '/../code/prettyPrinter', 'file-test');
    +    }
    +
    +    public function testPrettyPrintExpr() {
    +        $prettyPrinter = new Standard;
    +        $expr = new Expr\BinaryOp\Mul(
    +            new Expr\BinaryOp\Plus(new Expr\Variable('a'), new Expr\Variable('b')),
    +            new Expr\Variable('c')
    +        );
    +        $this->assertEquals('($a + $b) * $c', $prettyPrinter->prettyPrintExpr($expr));
    +
    +        $expr = new Expr\Closure(array(
    +            'stmts' => array(new Stmt\Return_(new String_("a\nb")))
    +        ));
    +        $this->assertEquals("function () {\n    return 'a\nb';\n}", $prettyPrinter->prettyPrintExpr($expr));
    +    }
    +
    +    public function testCommentBeforeInlineHTML() {
    +        $prettyPrinter = new PrettyPrinter\Standard;
    +        $comment = new Comment\Doc("/**\n * This is a comment\n */");
    +        $stmts = [new Stmt\InlineHTML('Hello World!', ['comments' => [$comment]])];
    +        $expected = "\nHello World!";
    +        $this->assertSame($expected, $prettyPrinter->prettyPrintFile($stmts));
    +    }
    +
    +    private function parseModeLine($modeLine) {
    +        $parts = explode(' ', $modeLine, 2);
    +        $version = isset($parts[0]) ? $parts[0] : 'both';
    +        $options = isset($parts[1]) ? json_decode($parts[1], true) : [];
    +        return [$version, $options];
    +    }
    +
    +    public function testArraySyntaxDefault() {
    +        $prettyPrinter = new Standard(['shortArraySyntax' => true]);
    +        $expr = new Expr\Array_([
    +            new Expr\ArrayItem(new String_('val'), new String_('key'))
    +        ]);
    +        $expected = "['key' => 'val']";
    +        $this->assertSame($expected, $prettyPrinter->prettyPrintExpr($expr));
    +    }
    +
    +    /**
    +     * @dataProvider provideTestKindAttributes
    +     */
    +    public function testKindAttributes($node, $expected) {
    +        $prttyPrinter = new PrettyPrinter\Standard;
    +        $result = $prttyPrinter->prettyPrintExpr($node);
    +        $this->assertSame($expected, $result);
    +    }
    +
    +    public function provideTestKindAttributes() {
    +        $nowdoc = ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR'];
    +        $heredoc = ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR'];
    +        return [
    +            // Defaults to single quoted
    +            [new String_('foo'), "'foo'"],
    +            // Explicit single/double quoted
    +            [new String_('foo', ['kind' => String_::KIND_SINGLE_QUOTED]), "'foo'"],
    +            [new String_('foo', ['kind' => String_::KIND_DOUBLE_QUOTED]), '"foo"'],
    +            // Fallback from doc string if no label
    +            [new String_('foo', ['kind' => String_::KIND_NOWDOC]), "'foo'"],
    +            [new String_('foo', ['kind' => String_::KIND_HEREDOC]), '"foo"'],
    +            // Fallback if string contains label
    +            [new String_("A\nB\nC", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'A']), "'A\nB\nC'"],
    +            [new String_("A\nB\nC", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'B']), "'A\nB\nC'"],
    +            [new String_("A\nB\nC", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'C']), "'A\nB\nC'"],
    +            [new String_("STR;", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']), "'STR;'"],
    +            // Doc string if label not contained (or not in ending position)
    +            [new String_("foo", $nowdoc), "<<<'STR'\nfoo\nSTR\n"],
    +            [new String_("foo", $heredoc), "<<
    +     */
    +    public function testSerialize() {
    +        $code = <<
    +
    + 
    +  
    +   
    +    4
    +   
    +   
    +    
    +     // comment
    +
    +     /** doc comment */
    +    
    +   
    +   
    +    6
    +   
    +   
    +    
    +   
    +   
    +    functionName
    +   
    +   
    +    
    +     
    +      
    +       4
    +      
    +      
    +       4
    +      
    +      
    +       
    +      
    +      
    +       
    +      
    +      
    +       
    +      
    +      
    +       a
    +      
    +      
    +       
    +        
    +         4
    +        
    +        
    +         4
    +        
    +        
    +         10
    +        
    +        
    +         0
    +        
    +       
    +      
    +     
    +     
    +      
    +       4
    +      
    +      
    +       4
    +      
    +      
    +       
    +      
    +      
    +       
    +      
    +      
    +       
    +      
    +      
    +       b
    +      
    +      
    +       
    +        
    +         4
    +        
    +        
    +         4
    +        
    +        
    +         1
    +        
    +       
    +      
    +     
    +    
    +   
    +   
    +    
    +   
    +   
    +    
    +     
    +      
    +       5
    +      
    +      
    +       5
    +      
    +      
    +       
    +        
    +         
    +          5
    +         
    +         
    +          5
    +         
    +         
    +          1
    +         
    +         
    +          Foo
    +         
    +        
    +       
    +      
    +     
    +    
    +   
    +  
    + 
    +
    +XML;
    +
    +        $parser     = new PhpParser\Parser\Php7(new PhpParser\Lexer);
    +        $serializer = new XML;
    +
    +        $code = str_replace("\r\n", "\n", $code);
    +        $stmts = $parser->parse($code);
    +        $this->assertXmlStringEqualsXmlString($xml, $serializer->serialize($stmts));
    +    }
    +
    +    /**
    +     * @expectedException        \InvalidArgumentException
    +     * @expectedExceptionMessage Unexpected node type
    +     */
    +    public function testError() {
    +        $serializer = new XML;
    +        $serializer->serialize(array(new \stdClass));
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/PhpParser/Unserializer/XMLTest.php b/core/vendor/nikic/php-parser/test/PhpParser/Unserializer/XMLTest.php
    new file mode 100644
    index 0000000..8ee5d7b
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/PhpParser/Unserializer/XMLTest.php
    @@ -0,0 +1,150 @@
    +
    +
    + 
    +  
    +   1
    +  
    +  
    +   
    +    // comment
    +
    +    /** doc comment */
    +   
    +  
    +  
    +   Test
    +  
    + 
    +
    +XML;
    +
    +        $unserializer  = new XML;
    +        $this->assertEquals(
    +            new Scalar\String_('Test', array(
    +                'startLine' => 1,
    +                'comments'  => array(
    +                    new Comment('// comment' . "\n", 2),
    +                    new Comment\Doc('/** doc comment */', 3),
    +                ),
    +            )),
    +            $unserializer->unserialize($xml)
    +        );
    +    }
    +
    +    public function testEmptyNode() {
    +        $xml = <<
    +
    + 
    +
    +XML;
    +
    +        $unserializer  = new XML;
    +
    +        $this->assertEquals(
    +            new Scalar\MagicConst\Class_,
    +            $unserializer->unserialize($xml)
    +        );
    +    }
    +
    +    public function testScalars() {
    +        $xml = <<
    +
    + 
    +  
    +  
    +  test
    +  
    +  
    +  1
    +  1
    +  1.5
    +  
    +  
    +  
    + 
    +
    +XML;
    +        $result = array(
    +            array(), array(),
    +            'test', '', '',
    +            1,
    +            1, 1.5,
    +            true, false, null
    +        );
    +
    +        $unserializer  = new XML;
    +        $this->assertEquals($result, $unserializer->unserialize($xml));
    +    }
    +
    +    /**
    +     * @expectedException        \DomainException
    +     * @expectedExceptionMessage AST root element not found
    +     */
    +    public function testWrongRootElementError() {
    +        $xml = <<
    +
    +XML;
    +
    +        $unserializer = new XML;
    +        $unserializer->unserialize($xml);
    +    }
    +
    +    /**
    +     * @dataProvider             provideTestErrors
    +     */
    +    public function testErrors($xml, $errorMsg) {
    +        $this->setExpectedException('DomainException', $errorMsg);
    +
    +        $xml = <<
    +
    + $xml
    +
    +XML;
    +
    +        $unserializer = new XML;
    +        $unserializer->unserialize($xml);
    +    }
    +
    +    public function provideTestErrors() {
    +        return array(
    +            array('test',   '"true" scalar must be empty'),
    +            array('test', '"false" scalar must be empty'),
    +            array('test',   '"null" scalar must be empty'),
    +            array('bar',      'Unknown scalar type "foo"'),
    +            array('x',        '"x" is not a valid int'),
    +            array('x',    '"x" is not a valid float'),
    +            array('',                                  'Expected node or scalar'),
    +            array('test',           'Unexpected node of type "foo:bar"'),
    +            array(
    +                'test',
    +                'Expected sub node or attribute, got node of type "foo:bar"'
    +            ),
    +            array(
    +                '',
    +                'Expected node or scalar'
    +            ),
    +            array(
    +                '',
    +                'Unknown node type "Foo"'
    +            ),
    +        );
    +    }
    +}
    diff --git a/core/vendor/nikic/php-parser/test/bootstrap.php b/core/vendor/nikic/php-parser/test/bootstrap.php
    new file mode 100644
    index 0000000..9526b64
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/bootstrap.php
    @@ -0,0 +1,20 @@
    +
    +-----
    +array(
    +    0: Stmt_Nop(
    +        comments: array(
    +            0: /** doc */
    +            1: /* foobar */
    +            2: // foo
    +            3: // bar
    +        )
    +    )
    +)
    +-----
    + 'd', 'e' => &$f);
    +
    +// short array syntax
    +[];
    +[1, 2, 3];
    +['a' => 'b'];
    +-----
    +array(
    +    0: Expr_Array(
    +        items: array(
    +        )
    +    )
    +    1: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: a
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    2: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: a
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    3: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: a
    +                )
    +                byRef: false
    +            )
    +            1: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: b
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    4: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: a
    +                )
    +                byRef: false
    +            )
    +            1: Expr_ArrayItem(
    +                key: null
    +                value: Expr_Variable(
    +                    name: b
    +                )
    +                byRef: true
    +            )
    +            2: Expr_ArrayItem(
    +                key: Scalar_String(
    +                    value: c
    +                )
    +                value: Scalar_String(
    +                    value: d
    +                )
    +                byRef: false
    +            )
    +            3: Expr_ArrayItem(
    +                key: Scalar_String(
    +                    value: e
    +                )
    +                value: Expr_Variable(
    +                    name: f
    +                )
    +                byRef: true
    +            )
    +        )
    +    )
    +    5: Expr_Array(
    +        items: array(
    +        )
    +        comments: array(
    +            0: // short array syntax
    +        )
    +    )
    +    6: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_LNumber(
    +                    value: 1
    +                )
    +                byRef: false
    +            )
    +            1: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_LNumber(
    +                    value: 2
    +                )
    +                byRef: false
    +            )
    +            2: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_LNumber(
    +                    value: 3
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    7: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: Scalar_String(
    +                    value: a
    +                )
    +                value: Scalar_String(
    +                    value: b
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/assign.test b/core/vendor/nikic/php-parser/test/code/parser/expr/assign.test
    new file mode 100644
    index 0000000..d55e2d8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/assign.test
    @@ -0,0 +1,273 @@
    +Assignments
    +-----
    +>= $b;
    +$a **= $b;
    +
    +// chained assign
    +$a = $b *= $c **= $d;
    +
    +// by ref assign
    +$a =& $b;
    +
    +// list() assign
    +list($a) = $b;
    +list($a, , $b) = $c;
    +list($a, list(, $c), $d) = $e;
    +
    +// inc/dec
    +++$a;
    +$a++;
    +--$a;
    +$a--;
    +-----
    +array(
    +    0: Expr_Assign(
    +        var: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // simple assign
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +        comments: array(
    +            0: // simple assign
    +        )
    +    )
    +    1: Expr_AssignOp_BitwiseAnd(
    +        var: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // combined assign
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +        comments: array(
    +            0: // combined assign
    +        )
    +    )
    +    2: Expr_AssignOp_BitwiseOr(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    3: Expr_AssignOp_BitwiseXor(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    4: Expr_AssignOp_Concat(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    5: Expr_AssignOp_Div(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    6: Expr_AssignOp_Minus(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    7: Expr_AssignOp_Mod(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    8: Expr_AssignOp_Mul(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    9: Expr_AssignOp_Plus(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    10: Expr_AssignOp_ShiftLeft(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    11: Expr_AssignOp_ShiftRight(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    12: Expr_AssignOp_Pow(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    13: Expr_Assign(
    +        var: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // chained assign
    +            )
    +        )
    +        expr: Expr_AssignOp_Mul(
    +            var: Expr_Variable(
    +                name: b
    +            )
    +            expr: Expr_AssignOp_Pow(
    +                var: Expr_Variable(
    +                    name: c
    +                )
    +                expr: Expr_Variable(
    +                    name: d
    +                )
    +            )
    +        )
    +        comments: array(
    +            0: // chained assign
    +        )
    +    )
    +    14: Expr_AssignRef(
    +        var: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // by ref assign
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +        comments: array(
    +            0: // by ref assign
    +        )
    +    )
    +    15: Expr_Assign(
    +        var: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: a
    +                )
    +            )
    +            comments: array(
    +                0: // list() assign
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +        comments: array(
    +            0: // list() assign
    +        )
    +    )
    +    16: Expr_Assign(
    +        var: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: a
    +                )
    +                1: null
    +                2: Expr_Variable(
    +                    name: b
    +                )
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: c
    +        )
    +    )
    +    17: Expr_Assign(
    +        var: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: a
    +                )
    +                1: Expr_List(
    +                    vars: array(
    +                        0: null
    +                        1: Expr_Variable(
    +                            name: c
    +                        )
    +                    )
    +                )
    +                2: Expr_Variable(
    +                    name: d
    +                )
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: e
    +        )
    +    )
    +    18: Expr_PreInc(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        comments: array(
    +            0: // inc/dec
    +        )
    +    )
    +    19: Expr_PostInc(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    20: Expr_PreDec(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    21: Expr_PostDec(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/assignNewByRef.test b/core/vendor/nikic/php-parser/test/code/parser/expr/assignNewByRef.test
    new file mode 100644
    index 0000000..10e1317
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/assignNewByRef.test
    @@ -0,0 +1,39 @@
    +Assigning new by reference (PHP 5 only)
    +-----
    + $b;
    +$a >= $b;
    +$a == $b;
    +$a === $b;
    +$a != $b;
    +$a !== $b;
    +$a <=> $b;
    +$a instanceof B;
    +$a instanceof $b;
    +-----
    +array(
    +    0: Expr_BinaryOp_Smaller(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    1: Expr_BinaryOp_SmallerOrEqual(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    2: Expr_BinaryOp_Greater(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    3: Expr_BinaryOp_GreaterOrEqual(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    4: Expr_BinaryOp_Equal(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    5: Expr_BinaryOp_Identical(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    6: Expr_BinaryOp_NotEqual(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    7: Expr_BinaryOp_NotIdentical(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    8: Expr_BinaryOp_Spaceship(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    9: Expr_Instanceof(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        class: Name(
    +            parts: array(
    +                0: B
    +            )
    +        )
    +    )
    +    10: Expr_Instanceof(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        class: Expr_Variable(
    +            name: b
    +        )
    +    )
    +)
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/constant_expr.test b/core/vendor/nikic/php-parser/test/code/parser/expr/constant_expr.test
    new file mode 100644
    index 0000000..3e7a23e
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/constant_expr.test
    @@ -0,0 +1,621 @@
    +Expressions in static scalar context
    +-----
    + 0;
    +const T_20 = 1 >= 0;
    +const T_21 = 1 === 1;
    +const T_22 = 1 !== 1;
    +const T_23 = 0 != "0";
    +const T_24 = 1 == "1";
    +const T_25 = 1 + 2 * 3;
    +const T_26 = "1" + 2 + "3";
    +const T_27 = 2 ** 3;
    +const T_28 = [1, 2, 3][1];
    +const T_29 = 12 - 13;
    +const T_30 = 12 ^ 13;
    +const T_31 = 12 & 13;
    +const T_32 = 12 | 13;
    +const T_33 = 12 % 3;
    +const T_34 = 100 >> 4;
    +const T_35 = !false;
    +-----
    +array(
    +    0: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_1
    +                value: Expr_BinaryOp_ShiftLeft(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    1: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_2
    +                value: Expr_BinaryOp_Div(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 2
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    2: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_3
    +                value: Expr_BinaryOp_Plus(
    +                    left: Scalar_DNumber(
    +                        value: 1.5
    +                    )
    +                    right: Scalar_DNumber(
    +                        value: 1.5
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    3: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_4
    +                value: Expr_BinaryOp_Concat(
    +                    left: Scalar_String(
    +                        value: foo
    +                    )
    +                    right: Scalar_String(
    +                        value: bar
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    4: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_5
    +                value: Expr_BinaryOp_Mul(
    +                    left: Expr_BinaryOp_Plus(
    +                        left: Scalar_DNumber(
    +                            value: 1.5
    +                        )
    +                        right: Scalar_DNumber(
    +                            value: 1.5
    +                        )
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 2
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    5: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_6
    +                value: Expr_BinaryOp_Concat(
    +                    left: Expr_BinaryOp_Concat(
    +                        left: Expr_BinaryOp_Concat(
    +                            left: Scalar_String(
    +                                value: foo
    +                            )
    +                            right: Scalar_LNumber(
    +                                value: 2
    +                            )
    +                        )
    +                        right: Scalar_LNumber(
    +                            value: 3
    +                        )
    +                    )
    +                    right: Scalar_DNumber(
    +                        value: 4
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    6: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_7
    +                value: Scalar_MagicConst_Line(
    +                )
    +            )
    +        )
    +    )
    +    7: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_8
    +                value: Scalar_String(
    +                    value: This is a test string
    +                )
    +            )
    +        )
    +    )
    +    8: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_9
    +                value: Expr_BitwiseNot(
    +                    expr: Expr_UnaryMinus(
    +                        expr: Scalar_LNumber(
    +                            value: 1
    +                        )
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    9: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_10
    +                value: Expr_BinaryOp_Plus(
    +                    left: Expr_Ternary(
    +                        cond: Expr_UnaryMinus(
    +                            expr: Scalar_LNumber(
    +                                value: 1
    +                            )
    +                        )
    +                        if: null
    +                        else: Scalar_LNumber(
    +                            value: 1
    +                        )
    +                    )
    +                    right: Expr_Ternary(
    +                        cond: Scalar_LNumber(
    +                            value: 0
    +                        )
    +                        if: Scalar_LNumber(
    +                            value: 2
    +                        )
    +                        else: Scalar_LNumber(
    +                            value: 3
    +                        )
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    10: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_11
    +                value: Expr_BinaryOp_BooleanAnd(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    11: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_12
    +                value: Expr_BinaryOp_LogicalAnd(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    12: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_13
    +                value: Expr_BinaryOp_BooleanOr(
    +                    left: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    13: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_14
    +                value: Expr_BinaryOp_LogicalOr(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    14: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_15
    +                value: Expr_BinaryOp_LogicalXor(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    15: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_16
    +                value: Expr_BinaryOp_LogicalXor(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    16: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_17
    +                value: Expr_BinaryOp_Smaller(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    17: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_18
    +                value: Expr_BinaryOp_SmallerOrEqual(
    +                    left: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    18: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_19
    +                value: Expr_BinaryOp_Greater(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    19: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_20
    +                value: Expr_BinaryOp_GreaterOrEqual(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    20: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_21
    +                value: Expr_BinaryOp_Identical(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    21: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_22
    +                value: Expr_BinaryOp_NotIdentical(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    22: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_23
    +                value: Expr_BinaryOp_NotEqual(
    +                    left: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                    right: Scalar_String(
    +                        value: 0
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    23: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_24
    +                value: Expr_BinaryOp_Equal(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Scalar_String(
    +                        value: 1
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    24: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_25
    +                value: Expr_BinaryOp_Plus(
    +                    left: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                    right: Expr_BinaryOp_Mul(
    +                        left: Scalar_LNumber(
    +                            value: 2
    +                        )
    +                        right: Scalar_LNumber(
    +                            value: 3
    +                        )
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    25: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_26
    +                value: Expr_BinaryOp_Plus(
    +                    left: Expr_BinaryOp_Plus(
    +                        left: Scalar_String(
    +                            value: 1
    +                        )
    +                        right: Scalar_LNumber(
    +                            value: 2
    +                        )
    +                    )
    +                    right: Scalar_String(
    +                        value: 3
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    26: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_27
    +                value: Expr_BinaryOp_Pow(
    +                    left: Scalar_LNumber(
    +                        value: 2
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 3
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    27: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_28
    +                value: Expr_ArrayDimFetch(
    +                    var: Expr_Array(
    +                        items: array(
    +                            0: Expr_ArrayItem(
    +                                key: null
    +                                value: Scalar_LNumber(
    +                                    value: 1
    +                                )
    +                                byRef: false
    +                            )
    +                            1: Expr_ArrayItem(
    +                                key: null
    +                                value: Scalar_LNumber(
    +                                    value: 2
    +                                )
    +                                byRef: false
    +                            )
    +                            2: Expr_ArrayItem(
    +                                key: null
    +                                value: Scalar_LNumber(
    +                                    value: 3
    +                                )
    +                                byRef: false
    +                            )
    +                        )
    +                    )
    +                    dim: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    28: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_29
    +                value: Expr_BinaryOp_Minus(
    +                    left: Scalar_LNumber(
    +                        value: 12
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 13
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    29: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_30
    +                value: Expr_BinaryOp_BitwiseXor(
    +                    left: Scalar_LNumber(
    +                        value: 12
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 13
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    30: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_31
    +                value: Expr_BinaryOp_BitwiseAnd(
    +                    left: Scalar_LNumber(
    +                        value: 12
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 13
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    31: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_32
    +                value: Expr_BinaryOp_BitwiseOr(
    +                    left: Scalar_LNumber(
    +                        value: 12
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 13
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    32: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_33
    +                value: Expr_BinaryOp_Mod(
    +                    left: Scalar_LNumber(
    +                        value: 12
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 3
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    33: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_34
    +                value: Expr_BinaryOp_ShiftRight(
    +                    left: Scalar_LNumber(
    +                        value: 100
    +                    )
    +                    right: Scalar_LNumber(
    +                        value: 4
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    34: Stmt_Const(
    +        consts: array(
    +            0: Const(
    +                name: T_35
    +                value: Expr_BooleanNot(
    +                    expr: Expr_ConstFetch(
    +                        name: Name(
    +                            parts: array(
    +                                0: false
    +                            )
    +                        )
    +                    )
    +                )
    +            )
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/errorSuppress.test b/core/vendor/nikic/php-parser/test/code/parser/expr/errorSuppress.test
    new file mode 100644
    index 0000000..ce3fce9
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/errorSuppress.test
    @@ -0,0 +1,12 @@
    +Error suppression
    +-----
    +b['c']();
    +
    +// array dereferencing
    +a()['b'];
    +-----
    +array(
    +    0: Expr_FuncCall(
    +        name: Name(
    +            parts: array(
    +                0: a
    +            )
    +            comments: array(
    +                0: // function name variations
    +            )
    +        )
    +        args: array(
    +        )
    +        comments: array(
    +            0: // function name variations
    +        )
    +    )
    +    1: Expr_FuncCall(
    +        name: Expr_Variable(
    +            name: a
    +        )
    +        args: array(
    +        )
    +    )
    +    2: Expr_FuncCall(
    +        name: Expr_Variable(
    +            name: Scalar_String(
    +                value: a
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    3: Expr_FuncCall(
    +        name: Expr_Variable(
    +            name: Expr_Variable(
    +                name: a
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    4: Expr_FuncCall(
    +        name: Expr_Variable(
    +            name: Expr_Variable(
    +                name: Expr_Variable(
    +                    name: a
    +                )
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    5: Expr_FuncCall(
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            dim: Scalar_String(
    +                value: b
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    6: Expr_FuncCall(
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            dim: Scalar_String(
    +                value: b
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    7: Expr_FuncCall(
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: a
    +                )
    +                name: b
    +            )
    +            dim: Scalar_String(
    +                value: c
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    8: Expr_ArrayDimFetch(
    +        var: Expr_FuncCall(
    +            name: Name(
    +                parts: array(
    +                    0: a
    +                )
    +                comments: array(
    +                    0: // array dereferencing
    +                )
    +            )
    +            args: array(
    +            )
    +            comments: array(
    +                0: // array dereferencing
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: b
    +        )
    +        comments: array(
    +            0: // array dereferencing
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/newDeref.test b/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/newDeref.test
    new file mode 100644
    index 0000000..5e36ff8
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/newDeref.test
    @@ -0,0 +1,70 @@
    +New expression dereferencing
    +-----
    +b;
    +(new A)->b();
    +(new A)['b'];
    +(new A)['b']['c'];
    +-----
    +array(
    +    0: Expr_PropertyFetch(
    +        var: Expr_New(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +        name: b
    +    )
    +    1: Expr_MethodCall(
    +        var: Expr_New(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +        name: b
    +        args: array(
    +        )
    +    )
    +    2: Expr_ArrayDimFetch(
    +        var: Expr_New(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: b
    +        )
    +    )
    +    3: Expr_ArrayDimFetch(
    +        var: Expr_ArrayDimFetch(
    +            var: Expr_New(
    +                class: Name(
    +                    parts: array(
    +                        0: A
    +                    )
    +                )
    +                args: array(
    +                )
    +            )
    +            dim: Scalar_String(
    +                value: b
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/objectAccess.test b/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/objectAccess.test
    new file mode 100644
    index 0000000..584461b
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/objectAccess.test
    @@ -0,0 +1,145 @@
    +Object access
    +-----
    +b;
    +$a->b['c'];
    +$a->b{'c'};
    +
    +// method call variations
    +$a->b();
    +$a->{'b'}();
    +$a->$b();
    +$a->$b['c']();
    +
    +// array dereferencing
    +$a->b()['c'];
    +$a->b(){'c'}; // invalid PHP: drop Support?
    +-----
    +!!php5
    +array(
    +    0: Expr_PropertyFetch(
    +        var: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // property fetch variations
    +            )
    +        )
    +        name: b
    +        comments: array(
    +            0: // property fetch variations
    +        )
    +    )
    +    1: Expr_ArrayDimFetch(
    +        var: Expr_PropertyFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +    2: Expr_ArrayDimFetch(
    +        var: Expr_PropertyFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +    3: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // method call variations
    +            )
    +        )
    +        name: b
    +        args: array(
    +        )
    +        comments: array(
    +            0: // method call variations
    +        )
    +    )
    +    4: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        name: Scalar_String(
    +            value: b
    +        )
    +        args: array(
    +        )
    +    )
    +    5: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        name: Expr_Variable(
    +            name: b
    +        )
    +        args: array(
    +        )
    +    )
    +    6: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: b
    +            )
    +            dim: Scalar_String(
    +                value: c
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    7: Expr_ArrayDimFetch(
    +        var: Expr_MethodCall(
    +            var: Expr_Variable(
    +                name: a
    +                comments: array(
    +                    0: // array dereferencing
    +                )
    +            )
    +            name: b
    +            args: array(
    +            )
    +            comments: array(
    +                0: // array dereferencing
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +        comments: array(
    +            0: // array dereferencing
    +        )
    +    )
    +    8: Expr_ArrayDimFetch(
    +        var: Expr_MethodCall(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +            args: array(
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +    9: Stmt_Nop(
    +        comments: array(
    +            0: // invalid PHP: drop Support?
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/simpleArrayAccess.test b/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/simpleArrayAccess.test
    new file mode 100644
    index 0000000..ea3f9ef
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/simpleArrayAccess.test
    @@ -0,0 +1,62 @@
    +Simple array access
    +-----
    +> $b;
    +$a ** $b;
    +
    +// associativity
    +$a * $b * $c;
    +$a * ($b * $c);
    +
    +// precedence
    +$a + $b * $c;
    +($a + $b) * $c;
    +
    +// pow is special
    +$a ** $b ** $c;
    +($a ** $b) ** $c;
    +-----
    +array(
    +    0: Expr_BitwiseNot(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        comments: array(
    +            0: // unary ops
    +        )
    +    )
    +    1: Expr_UnaryPlus(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    2: Expr_UnaryMinus(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    3: Expr_BinaryOp_BitwiseAnd(
    +        left: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // binary ops
    +            )
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +        comments: array(
    +            0: // binary ops
    +        )
    +    )
    +    4: Expr_BinaryOp_BitwiseOr(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    5: Expr_BinaryOp_BitwiseXor(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    6: Expr_BinaryOp_Concat(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    7: Expr_BinaryOp_Div(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    8: Expr_BinaryOp_Minus(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    9: Expr_BinaryOp_Mod(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    10: Expr_BinaryOp_Mul(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    11: Expr_BinaryOp_Plus(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    12: Expr_BinaryOp_ShiftLeft(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    13: Expr_BinaryOp_ShiftRight(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    14: Expr_BinaryOp_Pow(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    15: Expr_BinaryOp_Mul(
    +        left: Expr_BinaryOp_Mul(
    +            left: Expr_Variable(
    +                name: a
    +                comments: array(
    +                    0: // associativity
    +                )
    +            )
    +            right: Expr_Variable(
    +                name: b
    +            )
    +            comments: array(
    +                0: // associativity
    +            )
    +        )
    +        right: Expr_Variable(
    +            name: c
    +        )
    +        comments: array(
    +            0: // associativity
    +        )
    +    )
    +    16: Expr_BinaryOp_Mul(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_BinaryOp_Mul(
    +            left: Expr_Variable(
    +                name: b
    +            )
    +            right: Expr_Variable(
    +                name: c
    +            )
    +        )
    +    )
    +    17: Expr_BinaryOp_Plus(
    +        left: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // precedence
    +            )
    +        )
    +        right: Expr_BinaryOp_Mul(
    +            left: Expr_Variable(
    +                name: b
    +            )
    +            right: Expr_Variable(
    +                name: c
    +            )
    +        )
    +        comments: array(
    +            0: // precedence
    +        )
    +    )
    +    18: Expr_BinaryOp_Mul(
    +        left: Expr_BinaryOp_Plus(
    +            left: Expr_Variable(
    +                name: a
    +            )
    +            right: Expr_Variable(
    +                name: b
    +            )
    +        )
    +        right: Expr_Variable(
    +            name: c
    +        )
    +    )
    +    19: Expr_BinaryOp_Pow(
    +        left: Expr_Variable(
    +            name: a
    +            comments: array(
    +                0: // pow is special
    +            )
    +        )
    +        right: Expr_BinaryOp_Pow(
    +            left: Expr_Variable(
    +                name: b
    +            )
    +            right: Expr_Variable(
    +                name: c
    +            )
    +        )
    +        comments: array(
    +            0: // pow is special
    +        )
    +    )
    +    20: Expr_BinaryOp_Pow(
    +        left: Expr_BinaryOp_Pow(
    +            left: Expr_Variable(
    +                name: a
    +            )
    +            right: Expr_Variable(
    +                name: b
    +            )
    +        )
    +        right: Expr_Variable(
    +            name: c
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/new.test b/core/vendor/nikic/php-parser/test/code/parser/expr/new.test
    new file mode 100644
    index 0000000..a132bbb
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/new.test
    @@ -0,0 +1,146 @@
    +New
    +-----
    +b();
    +new $a->b->c();
    +new $a->b['c']();
    +new $a->b{'c'}();
    +
    +// test regression introduces by new dereferencing syntax
    +(new A);
    +-----
    +array(
    +    0: Expr_New(
    +        class: Name(
    +            parts: array(
    +                0: A
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    1: Expr_New(
    +        class: Name(
    +            parts: array(
    +                0: A
    +            )
    +        )
    +        args: array(
    +            0: Arg(
    +                value: Expr_Variable(
    +                    name: b
    +                )
    +                byRef: false
    +                unpack: false
    +            )
    +        )
    +    )
    +    2: Expr_New(
    +        class: Expr_Variable(
    +            name: a
    +        )
    +        args: array(
    +        )
    +        comments: array(
    +            0: // class name variations
    +        )
    +    )
    +    3: Expr_New(
    +        class: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            dim: Scalar_String(
    +                value: b
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    4: Expr_New(
    +        class: Expr_StaticPropertyFetch(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            name: b
    +        )
    +        args: array(
    +        )
    +    )
    +    5: Expr_New(
    +        class: Expr_PropertyFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +        )
    +        args: array(
    +        )
    +        comments: array(
    +            0: // DNCR object access
    +        )
    +    )
    +    6: Expr_New(
    +        class: Expr_PropertyFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: a
    +                )
    +                name: b
    +            )
    +            name: c
    +        )
    +        args: array(
    +        )
    +    )
    +    7: Expr_New(
    +        class: Expr_ArrayDimFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: a
    +                )
    +                name: b
    +            )
    +            dim: Scalar_String(
    +                value: c
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    8: Expr_New(
    +        class: Expr_ArrayDimFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: a
    +                )
    +                name: b
    +            )
    +            dim: Scalar_String(
    +                value: c
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    9: Expr_New(
    +        class: Name(
    +            parts: array(
    +                0: A
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/newWithoutClass.test b/core/vendor/nikic/php-parser/test/code/parser/expr/newWithoutClass.test
    new file mode 100644
    index 0000000..84ec7b1
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/newWithoutClass.test
    @@ -0,0 +1,8 @@
    +New without a class
    +-----
    +bar;
    +-----
    +!!php7
    +Syntax error, unexpected T_OBJECT_OPERATOR, expecting ',' or ';' from 2:13 to 2:14
    +array(
    +    0: Expr_ConstFetch(
    +        name: Name(
    +            parts: array(
    +                0: bar
    +            )
    +        )
    +    )
    +)
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/indirectCall.test b/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/indirectCall.test
    new file mode 100644
    index 0000000..bb3e7fb
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/indirectCall.test
    @@ -0,0 +1,481 @@
    +UVS indirect calls
    +-----
    + 'b']->a);
    +isset("str"->a);
    +-----
    +!!php7
    +array(
    +    0: Expr_Isset(
    +        vars: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_BinaryOp_Plus(
    +                    left: Expr_Array(
    +                        items: array(
    +                            0: Expr_ArrayItem(
    +                                key: null
    +                                value: Scalar_LNumber(
    +                                    value: 0
    +                                )
    +                                byRef: false
    +                            )
    +                            1: Expr_ArrayItem(
    +                                key: null
    +                                value: Scalar_LNumber(
    +                                    value: 1
    +                                )
    +                                byRef: false
    +                            )
    +                        )
    +                    )
    +                    right: Expr_Array(
    +                        items: array(
    +                        )
    +                    )
    +                )
    +                dim: Scalar_LNumber(
    +                    value: 0
    +                )
    +            )
    +        )
    +    )
    +    1: Expr_Isset(
    +        vars: array(
    +            0: Expr_PropertyFetch(
    +                var: Expr_Array(
    +                    items: array(
    +                        0: Expr_ArrayItem(
    +                            key: Scalar_String(
    +                                value: a
    +                            )
    +                            value: Scalar_String(
    +                                value: b
    +                            )
    +                            byRef: false
    +                        )
    +                    )
    +                )
    +                name: a
    +            )
    +        )
    +    )
    +    2: Expr_Isset(
    +        vars: array(
    +            0: Expr_PropertyFetch(
    +                var: Scalar_String(
    +                    value: str
    +                )
    +                name: a
    +            )
    +        )
    +    )
    +)
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/misc.test b/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/misc.test
    new file mode 100644
    index 0000000..2c5ba90
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/misc.test
    @@ -0,0 +1,109 @@
    +Uniform variable syntax in PHP 7 (misc)
    +-----
    +length();
    +(clone $obj)->b[0](1);
    +[0, 1][0] = 1;
    +-----
    +!!php7
    +array(
    +    0: Expr_ArrayDimFetch(
    +        var: Expr_ClassConstFetch(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            name: A
    +        )
    +        dim: Scalar_LNumber(
    +            value: 0
    +        )
    +    )
    +    1: Expr_ArrayDimFetch(
    +        var: Expr_ArrayDimFetch(
    +            var: Expr_ArrayDimFetch(
    +                var: Expr_ClassConstFetch(
    +                    class: Name(
    +                        parts: array(
    +                            0: A
    +                        )
    +                    )
    +                    name: A
    +                )
    +                dim: Scalar_LNumber(
    +                    value: 0
    +                )
    +            )
    +            dim: Scalar_LNumber(
    +                value: 1
    +            )
    +        )
    +        dim: Scalar_LNumber(
    +            value: 2
    +        )
    +    )
    +    2: Expr_MethodCall(
    +        var: Scalar_String(
    +            value: string
    +        )
    +        name: length
    +        args: array(
    +        )
    +    )
    +    3: Expr_FuncCall(
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Clone(
    +                    expr: Expr_Variable(
    +                        name: obj
    +                    )
    +                )
    +                name: b
    +            )
    +            dim: Scalar_LNumber(
    +                value: 0
    +            )
    +        )
    +        args: array(
    +            0: Arg(
    +                value: Scalar_LNumber(
    +                    value: 1
    +                )
    +                byRef: false
    +                unpack: false
    +            )
    +        )
    +    )
    +    4: Expr_Assign(
    +        var: Expr_ArrayDimFetch(
    +            var: Expr_Array(
    +                items: array(
    +                    0: Expr_ArrayItem(
    +                        key: null
    +                        value: Scalar_LNumber(
    +                            value: 0
    +                        )
    +                        byRef: false
    +                    )
    +                    1: Expr_ArrayItem(
    +                        key: null
    +                        value: Scalar_LNumber(
    +                            value: 1
    +                        )
    +                        byRef: false
    +                    )
    +                )
    +            )
    +            dim: Scalar_LNumber(
    +                value: 0
    +            )
    +        )
    +        expr: Scalar_LNumber(
    +            value: 1
    +        )
    +    )
    +)
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/new.test b/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/new.test
    new file mode 100644
    index 0000000..e5f92f9
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/new.test
    @@ -0,0 +1,95 @@
    +UVS new expressions
    +-----
    +className;
    +new Test::$className;
    +new $test::$className;
    +new $weird[0]->foo::$className;
    +-----
    +!!php7
    +array(
    +    0: Expr_New(
    +        class: Expr_Variable(
    +            name: className
    +        )
    +        args: array(
    +        )
    +    )
    +    1: Expr_New(
    +        class: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: array
    +            )
    +            dim: Scalar_String(
    +                value: className
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    2: Expr_New(
    +        class: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: array
    +            )
    +            dim: Scalar_String(
    +                value: className
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    3: Expr_New(
    +        class: Expr_PropertyFetch(
    +            var: Expr_Variable(
    +                name: obj
    +            )
    +            name: className
    +        )
    +        args: array(
    +        )
    +    )
    +    4: Expr_New(
    +        class: Expr_StaticPropertyFetch(
    +            class: Name(
    +                parts: array(
    +                    0: Test
    +                )
    +            )
    +            name: className
    +        )
    +        args: array(
    +        )
    +    )
    +    5: Expr_New(
    +        class: Expr_StaticPropertyFetch(
    +            class: Expr_Variable(
    +                name: test
    +            )
    +            name: className
    +        )
    +        args: array(
    +        )
    +    )
    +    6: Expr_New(
    +        class: Expr_StaticPropertyFetch(
    +            class: Expr_PropertyFetch(
    +                var: Expr_ArrayDimFetch(
    +                    var: Expr_Variable(
    +                        name: weird
    +                    )
    +                    dim: Scalar_LNumber(
    +                        value: 0
    +                    )
    +                )
    +                name: foo
    +            )
    +            name: className
    +        )
    +        args: array(
    +        )
    +    )
    +)
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/staticProperty.test b/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/staticProperty.test
    new file mode 100644
    index 0000000..5fadfc4
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/expr/uvs/staticProperty.test
    @@ -0,0 +1,93 @@
    +UVS static access
    +-----
    +c test
    +EOS;
    +
    +b<<B";
    +"$A[B]";
    +"$A[0]";
    +"$A[0x0]";
    +"$A[$B]";
    +"{$A}";
    +"{$A['B']}";
    +"${A}";
    +"${A['B']}";
    +"${$A}";
    +"\{$A}";
    +"\{ $A }";
    +"\\{$A}";
    +"\\{ $A }";
    +"{$$A}[B]";
    +"$$A[B]";
    +"A $B C";
    +b"$A";
    +B"$A";
    +-----
    +array(
    +    0: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +    1: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                name: B
    +            )
    +        )
    +    )
    +    2: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +    )
    +    3: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: 0
    +                )
    +            )
    +        )
    +    )
    +    4: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: 0x0
    +                )
    +            )
    +        )
    +    )
    +    5: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Expr_Variable(
    +                    name: B
    +                )
    +            )
    +        )
    +    )
    +    6: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +    7: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +    )
    +    8: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +    9: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +    )
    +    10: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: Expr_Variable(
    +                    name: A
    +                )
    +            )
    +        )
    +    )
    +    11: Scalar_Encapsed(
    +        parts: array(
    +            0: Scalar_EncapsedStringPart(
    +                value: \{
    +            )
    +            1: Expr_Variable(
    +                name: A
    +            )
    +            2: Scalar_EncapsedStringPart(
    +                value: }
    +            )
    +        )
    +    )
    +    12: Scalar_Encapsed(
    +        parts: array(
    +            0: Scalar_EncapsedStringPart(
    +                value: \{
    +            )
    +            1: Expr_Variable(
    +                name: A
    +            )
    +            2: Scalar_EncapsedStringPart(
    +                value:  }
    +            )
    +        )
    +    )
    +    13: Scalar_Encapsed(
    +        parts: array(
    +            0: Scalar_EncapsedStringPart(
    +                value: \
    +            )
    +            1: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +    14: Scalar_Encapsed(
    +        parts: array(
    +            0: Scalar_EncapsedStringPart(
    +                value: \{
    +            )
    +            1: Expr_Variable(
    +                name: A
    +            )
    +            2: Scalar_EncapsedStringPart(
    +                value:  }
    +            )
    +        )
    +    )
    +    15: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: Expr_Variable(
    +                    name: A
    +                )
    +            )
    +            1: Scalar_EncapsedStringPart(
    +                value: [B]
    +            )
    +        )
    +    )
    +    16: Scalar_Encapsed(
    +        parts: array(
    +            0: Scalar_EncapsedStringPart(
    +                value: $
    +            )
    +            1: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +    )
    +    17: Scalar_Encapsed(
    +        parts: array(
    +            0: Scalar_EncapsedStringPart(
    +                value: A
    +            )
    +            1: Expr_Variable(
    +                name: B
    +            )
    +            2: Scalar_EncapsedStringPart(
    +                value:  C
    +            )
    +        )
    +    )
    +    18: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +    19: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/scalar/float.test b/core/vendor/nikic/php-parser/test/code/parser/scalar/float.test
    new file mode 100644
    index 0000000..a16028e
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/scalar/float.test
    @@ -0,0 +1,74 @@
    +Different float syntaxes
    +-----
    + float overflows
    +// (all are actually the same number, just in different representations)
    +18446744073709551615;
    +0xFFFFFFFFFFFFFFFF;
    +01777777777777777777777;
    +0177777777777777777777787;
    +0b1111111111111111111111111111111111111111111111111111111111111111;
    +-----
    +array(
    +    0: Scalar_DNumber(
    +        value: 0
    +    )
    +    1: Scalar_DNumber(
    +        value: 0
    +    )
    +    2: Scalar_DNumber(
    +        value: 0
    +    )
    +    3: Scalar_DNumber(
    +        value: 0
    +    )
    +    4: Scalar_DNumber(
    +        value: 0
    +    )
    +    5: Scalar_DNumber(
    +        value: 0
    +    )
    +    6: Scalar_DNumber(
    +        value: 0
    +    )
    +    7: Scalar_DNumber(
    +        value: 302000000000
    +    )
    +    8: Scalar_DNumber(
    +        value: 3.002E+102
    +    )
    +    9: Scalar_DNumber(
    +        value: INF
    +    )
    +    10: Scalar_DNumber(
    +        value: 1.844674407371E+19
    +        comments: array(
    +            0: // various integer -> float overflows
    +            1: // (all are actually the same number, just in different representations)
    +        )
    +    )
    +    11: Scalar_DNumber(
    +        value: 1.844674407371E+19
    +    )
    +    12: Scalar_DNumber(
    +        value: 1.844674407371E+19
    +    )
    +    13: Scalar_DNumber(
    +        value: 1.844674407371E+19
    +    )
    +    14: Scalar_DNumber(
    +        value: 1.844674407371E+19
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/scalar/int.test b/core/vendor/nikic/php-parser/test/code/parser/scalar/int.test
    new file mode 100644
    index 0000000..5a21267
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/scalar/int.test
    @@ -0,0 +1,43 @@
    +Different integer syntaxes
    +-----
    +array();
    +$t->public();
    +
    +Test::list();
    +Test::protected();
    +
    +$t->class;
    +$t->private;
    +
    +Test::TRAIT;
    +Test::FINAL;
    +
    +class Foo {
    +    use TraitA, TraitB {
    +        TraitA::catch insteadof namespace\TraitB;
    +        TraitA::list as foreach;
    +        TraitB::throw as protected public;
    +        TraitB::self as protected;
    +        exit as die;
    +        \TraitC::exit as bye;
    +        namespace\TraitC::exit as byebye;
    +        TraitA::
    +            //
    +            /** doc comment */
    +            #
    +        catch /* comment */
    +            // comment
    +            # comment
    +        insteadof TraitB;
    +    }
    +}
    +-----
    +array(
    +    0: Stmt_Class(
    +        type: 0
    +        name: Test
    +        extends: null
    +        implements: array(
    +        )
    +        stmts: array(
    +            0: Stmt_ClassMethod(
    +                type: 0
    +                byRef: false
    +                name: array
    +                params: array(
    +                )
    +                returnType: null
    +                stmts: array(
    +                )
    +            )
    +            1: Stmt_ClassMethod(
    +                type: 0
    +                byRef: false
    +                name: public
    +                params: array(
    +                )
    +                returnType: null
    +                stmts: array(
    +                )
    +            )
    +            2: Stmt_ClassMethod(
    +                type: 8
    +                byRef: false
    +                name: list
    +                params: array(
    +                )
    +                returnType: null
    +                stmts: array(
    +                )
    +            )
    +            3: Stmt_ClassMethod(
    +                type: 8
    +                byRef: false
    +                name: protected
    +                params: array(
    +                )
    +                returnType: null
    +                stmts: array(
    +                )
    +            )
    +            4: Stmt_Property(
    +                type: 1
    +                props: array(
    +                    0: Stmt_PropertyProperty(
    +                        name: class
    +                        default: null
    +                    )
    +                )
    +            )
    +            5: Stmt_Property(
    +                type: 1
    +                props: array(
    +                    0: Stmt_PropertyProperty(
    +                        name: private
    +                        default: null
    +                    )
    +                )
    +            )
    +            6: Stmt_ClassConst(
    +                consts: array(
    +                    0: Const(
    +                        name: TRAIT
    +                        value: Scalar_LNumber(
    +                            value: 3
    +                        )
    +                    )
    +                    1: Const(
    +                        name: FINAL
    +                        value: Scalar_LNumber(
    +                            value: 4
    +                        )
    +                    )
    +                )
    +            )
    +            7: Stmt_ClassConst(
    +                consts: array(
    +                    0: Const(
    +                        name: __CLASS__
    +                        value: Scalar_LNumber(
    +                            value: 1
    +                        )
    +                    )
    +                    1: Const(
    +                        name: __TRAIT__
    +                        value: Scalar_LNumber(
    +                            value: 2
    +                        )
    +                    )
    +                    2: Const(
    +                        name: __FUNCTION__
    +                        value: Scalar_LNumber(
    +                            value: 3
    +                        )
    +                    )
    +                    3: Const(
    +                        name: __METHOD__
    +                        value: Scalar_LNumber(
    +                            value: 4
    +                        )
    +                    )
    +                    4: Const(
    +                        name: __LINE__
    +                        value: Scalar_LNumber(
    +                            value: 5
    +                        )
    +                    )
    +                    5: Const(
    +                        name: __FILE__
    +                        value: Scalar_LNumber(
    +                            value: 6
    +                        )
    +                    )
    +                    6: Const(
    +                        name: __DIR__
    +                        value: Scalar_LNumber(
    +                            value: 7
    +                        )
    +                    )
    +                    7: Const(
    +                        name: __NAMESPACE__
    +                        value: Scalar_LNumber(
    +                            value: 8
    +                        )
    +                    )
    +                )
    +            )
    +        )
    +    )
    +    1: Expr_Assign(
    +        var: Expr_Variable(
    +            name: t
    +        )
    +        expr: Expr_New(
    +            class: Name(
    +                parts: array(
    +                    0: Test
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +    )
    +    2: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: t
    +        )
    +        name: array
    +        args: array(
    +        )
    +    )
    +    3: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: t
    +        )
    +        name: public
    +        args: array(
    +        )
    +    )
    +    4: Expr_StaticCall(
    +        class: Name(
    +            parts: array(
    +                0: Test
    +            )
    +        )
    +        name: list
    +        args: array(
    +        )
    +    )
    +    5: Expr_StaticCall(
    +        class: Name(
    +            parts: array(
    +                0: Test
    +            )
    +        )
    +        name: protected
    +        args: array(
    +        )
    +    )
    +    6: Expr_PropertyFetch(
    +        var: Expr_Variable(
    +            name: t
    +        )
    +        name: class
    +    )
    +    7: Expr_PropertyFetch(
    +        var: Expr_Variable(
    +            name: t
    +        )
    +        name: private
    +    )
    +    8: Expr_ClassConstFetch(
    +        class: Name(
    +            parts: array(
    +                0: Test
    +            )
    +        )
    +        name: TRAIT
    +    )
    +    9: Expr_ClassConstFetch(
    +        class: Name(
    +            parts: array(
    +                0: Test
    +            )
    +        )
    +        name: FINAL
    +    )
    +    10: Stmt_Class(
    +        type: 0
    +        name: Foo
    +        extends: null
    +        implements: array(
    +        )
    +        stmts: array(
    +            0: Stmt_TraitUse(
    +                traits: array(
    +                    0: Name(
    +                        parts: array(
    +                            0: TraitA
    +                        )
    +                    )
    +                    1: Name(
    +                        parts: array(
    +                            0: TraitB
    +                        )
    +                    )
    +                )
    +                adaptations: array(
    +                    0: Stmt_TraitUseAdaptation_Precedence(
    +                        trait: Name(
    +                            parts: array(
    +                                0: TraitA
    +                            )
    +                        )
    +                        method: catch
    +                        insteadof: array(
    +                            0: Name_Relative(
    +                                parts: array(
    +                                    0: TraitB
    +                                )
    +                            )
    +                        )
    +                    )
    +                    1: Stmt_TraitUseAdaptation_Alias(
    +                        trait: Name(
    +                            parts: array(
    +                                0: TraitA
    +                            )
    +                        )
    +                        method: list
    +                        newModifier: null
    +                        newName: foreach
    +                    )
    +                    2: Stmt_TraitUseAdaptation_Alias(
    +                        trait: Name(
    +                            parts: array(
    +                                0: TraitB
    +                            )
    +                        )
    +                        method: throw
    +                        newModifier: 2
    +                        newName: public
    +                    )
    +                    3: Stmt_TraitUseAdaptation_Alias(
    +                        trait: Name(
    +                            parts: array(
    +                                0: TraitB
    +                            )
    +                        )
    +                        method: self
    +                        newModifier: 2
    +                        newName: null
    +                    )
    +                    4: Stmt_TraitUseAdaptation_Alias(
    +                        trait: null
    +                        method: exit
    +                        newModifier: null
    +                        newName: die
    +                    )
    +                    5: Stmt_TraitUseAdaptation_Alias(
    +                        trait: Name_FullyQualified(
    +                            parts: array(
    +                                0: TraitC
    +                            )
    +                        )
    +                        method: exit
    +                        newModifier: null
    +                        newName: bye
    +                    )
    +                    6: Stmt_TraitUseAdaptation_Alias(
    +                        trait: Name_Relative(
    +                            parts: array(
    +                                0: TraitC
    +                            )
    +                        )
    +                        method: exit
    +                        newModifier: null
    +                        newName: byebye
    +                    )
    +                    7: Stmt_TraitUseAdaptation_Precedence(
    +                        trait: Name(
    +                            parts: array(
    +                                0: TraitA
    +                            )
    +                        )
    +                        method: catch
    +                        insteadof: array(
    +                            0: Name(
    +                                parts: array(
    +                                    0: TraitB
    +                                )
    +                            )
    +                        )
    +                    )
    +                )
    +            )
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/stmt/blocklessStatement.test b/core/vendor/nikic/php-parser/test/code/parser/stmt/blocklessStatement.test
    new file mode 100644
    index 0000000..ae83dab
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/stmt/blocklessStatement.test
    @@ -0,0 +1,112 @@
    +Blockless statements for if/for/etc
    +-----
    + 'baz']
    +) {}
    +-----
    +array(
    +    0: Stmt_Function(
    +        byRef: false
    +        name: a
    +        params: array(
    +            0: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: b
    +                default: Expr_ConstFetch(
    +                    name: Name(
    +                        parts: array(
    +                            0: null
    +                        )
    +                    )
    +                )
    +            )
    +            1: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: c
    +                default: Scalar_String(
    +                    value: foo
    +                )
    +            )
    +            2: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: d
    +                default: Expr_ClassConstFetch(
    +                    class: Name(
    +                        parts: array(
    +                            0: A
    +                        )
    +                    )
    +                    name: B
    +                )
    +            )
    +            3: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: f
    +                default: Expr_UnaryPlus(
    +                    expr: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                )
    +            )
    +            4: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: g
    +                default: Expr_UnaryMinus(
    +                    expr: Scalar_DNumber(
    +                        value: 1
    +                    )
    +                )
    +            )
    +            5: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: h
    +                default: Expr_Array(
    +                    items: array(
    +                    )
    +                )
    +            )
    +            6: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: i
    +                default: Expr_Array(
    +                    items: array(
    +                    )
    +                )
    +            )
    +            7: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: j
    +                default: Expr_Array(
    +                    items: array(
    +                        0: Expr_ArrayItem(
    +                            key: null
    +                            value: Scalar_String(
    +                                value: foo
    +                            )
    +                            byRef: false
    +                        )
    +                    )
    +                )
    +            )
    +            8: Param(
    +                type: null
    +                byRef: false
    +                variadic: false
    +                name: k
    +                default: Expr_Array(
    +                    items: array(
    +                        0: Expr_ArrayItem(
    +                            key: null
    +                            value: Scalar_String(
    +                                value: foo
    +                            )
    +                            byRef: false
    +                        )
    +                        1: Expr_ArrayItem(
    +                            key: Scalar_String(
    +                                value: bar
    +                            )
    +                            value: Scalar_String(
    +                                value: baz
    +                            )
    +                            byRef: false
    +                        )
    +                    )
    +                )
    +            )
    +        )
    +        returnType: null
    +        stmts: array(
    +        )
    +    )
    +)
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/stmt/function/returnTypes.test b/core/vendor/nikic/php-parser/test/code/parser/stmt/function/returnTypes.test
    new file mode 100644
    index 0000000..ca6c310
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/stmt/function/returnTypes.test
    @@ -0,0 +1,52 @@
    +Return type declarations
    +-----
    + $value;
    +
    +    // expressions
    +    $data = yield;
    +    $data = (yield $value);
    +    $data = (yield $key => $value);
    +
    +    // yield in language constructs with their own parentheses
    +    if (yield $foo); elseif (yield $foo);
    +    if (yield $foo): elseif (yield $foo): endif;
    +    while (yield $foo);
    +    do {} while (yield $foo);
    +    switch (yield $foo) {}
    +    die(yield $foo);
    +
    +    // yield in function calls
    +    func(yield $foo);
    +    $foo->func(yield $foo);
    +    new Foo(yield $foo);
    +
    +    yield from $foo;
    +    yield from $foo and yield from $bar;
    +    yield from $foo + $bar;
    +}
    +-----
    +array(
    +    0: Stmt_Function(
    +        byRef: false
    +        name: gen
    +        params: array(
    +        )
    +        returnType: null
    +        stmts: array(
    +            0: Expr_Yield(
    +                key: null
    +                value: null
    +                comments: array(
    +                    0: // statements
    +                )
    +            )
    +            1: Expr_Yield(
    +                key: null
    +                value: Expr_Variable(
    +                    name: value
    +                )
    +            )
    +            2: Expr_Yield(
    +                key: Expr_Variable(
    +                    name: key
    +                )
    +                value: Expr_Variable(
    +                    name: value
    +                )
    +            )
    +            3: Expr_Assign(
    +                var: Expr_Variable(
    +                    name: data
    +                    comments: array(
    +                        0: // expressions
    +                    )
    +                )
    +                expr: Expr_Yield(
    +                    key: null
    +                    value: null
    +                )
    +                comments: array(
    +                    0: // expressions
    +                )
    +            )
    +            4: Expr_Assign(
    +                var: Expr_Variable(
    +                    name: data
    +                )
    +                expr: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: value
    +                    )
    +                )
    +            )
    +            5: Expr_Assign(
    +                var: Expr_Variable(
    +                    name: data
    +                )
    +                expr: Expr_Yield(
    +                    key: Expr_Variable(
    +                        name: key
    +                    )
    +                    value: Expr_Variable(
    +                        name: value
    +                    )
    +                )
    +            )
    +            6: Stmt_If(
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                stmts: array(
    +                )
    +                elseifs: array(
    +                    0: Stmt_ElseIf(
    +                        cond: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        stmts: array(
    +                        )
    +                    )
    +                )
    +                else: null
    +                comments: array(
    +                    0: // yield in language constructs with their own parentheses
    +                )
    +            )
    +            7: Stmt_If(
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                stmts: array(
    +                )
    +                elseifs: array(
    +                    0: Stmt_ElseIf(
    +                        cond: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        stmts: array(
    +                        )
    +                    )
    +                )
    +                else: null
    +            )
    +            8: Stmt_While(
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                stmts: array(
    +                )
    +            )
    +            9: Stmt_Do(
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                stmts: array(
    +                )
    +            )
    +            10: Stmt_Switch(
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                cases: array(
    +                )
    +            )
    +            11: Expr_Exit(
    +                expr: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +            )
    +            12: Expr_FuncCall(
    +                name: Name(
    +                    parts: array(
    +                        0: func
    +                    )
    +                    comments: array(
    +                        0: // yield in function calls
    +                    )
    +                )
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        byRef: false
    +                        unpack: false
    +                    )
    +                )
    +                comments: array(
    +                    0: // yield in function calls
    +                )
    +            )
    +            13: Expr_MethodCall(
    +                var: Expr_Variable(
    +                    name: foo
    +                )
    +                name: func
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        byRef: false
    +                        unpack: false
    +                    )
    +                )
    +            )
    +            14: Expr_New(
    +                class: Name(
    +                    parts: array(
    +                        0: Foo
    +                    )
    +                )
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        byRef: false
    +                        unpack: false
    +                    )
    +                )
    +            )
    +            15: Expr_YieldFrom(
    +                expr: Expr_Variable(
    +                    name: foo
    +                )
    +            )
    +            16: Expr_BinaryOp_LogicalAnd(
    +                left: Expr_YieldFrom(
    +                    expr: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                right: Expr_YieldFrom(
    +                    expr: Expr_Variable(
    +                        name: bar
    +                    )
    +                )
    +            )
    +            17: Expr_YieldFrom(
    +                expr: Expr_BinaryOp_Plus(
    +                    left: Expr_Variable(
    +                        name: foo
    +                    )
    +                    right: Expr_Variable(
    +                        name: bar
    +                    )
    +                )
    +            )
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/stmt/generator/yieldPrecedence.test b/core/vendor/nikic/php-parser/test/code/parser/stmt/generator/yieldPrecedence.test
    new file mode 100644
    index 0000000..ff0d4df
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/stmt/generator/yieldPrecedence.test
    @@ -0,0 +1,230 @@
    +Yield operator precedence
    +-----
    + "a" . "b";
    +    yield "k" => "a" or die;
    +    var_dump([yield "k" => "a" . "b"]);
    +    yield yield "k1" => yield "k2" => "a" . "b";
    +    yield yield "k1" => (yield "k2") => "a" . "b";
    +    var_dump([yield "k1" => yield "k2" => "a" . "b"]);
    +    var_dump([yield "k1" => (yield "k2") => "a" . "b"]);
    +}
    +-----
    +!!php7
    +array(
    +    0: Stmt_Function(
    +        byRef: false
    +        name: gen
    +        params: array(
    +        )
    +        returnType: null
    +        stmts: array(
    +            0: Expr_Yield(
    +                key: null
    +                value: Expr_BinaryOp_Concat(
    +                    left: Scalar_String(
    +                        value: a
    +                    )
    +                    right: Scalar_String(
    +                        value: b
    +                    )
    +                )
    +            )
    +            1: Expr_BinaryOp_LogicalOr(
    +                left: Expr_Yield(
    +                    key: null
    +                    value: Scalar_String(
    +                        value: a
    +                    )
    +                )
    +                right: Expr_Exit(
    +                    expr: null
    +                )
    +            )
    +            2: Expr_Yield(
    +                key: Scalar_String(
    +                    value: k
    +                )
    +                value: Expr_BinaryOp_Concat(
    +                    left: Scalar_String(
    +                        value: a
    +                    )
    +                    right: Scalar_String(
    +                        value: b
    +                    )
    +                )
    +            )
    +            3: Expr_BinaryOp_LogicalOr(
    +                left: Expr_Yield(
    +                    key: Scalar_String(
    +                        value: k
    +                    )
    +                    value: Scalar_String(
    +                        value: a
    +                    )
    +                )
    +                right: Expr_Exit(
    +                    expr: null
    +                )
    +            )
    +            4: Expr_FuncCall(
    +                name: Name(
    +                    parts: array(
    +                        0: var_dump
    +                    )
    +                )
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Array(
    +                            items: array(
    +                                0: Expr_ArrayItem(
    +                                    key: null
    +                                    value: Expr_Yield(
    +                                        key: Scalar_String(
    +                                            value: k
    +                                        )
    +                                        value: Expr_BinaryOp_Concat(
    +                                            left: Scalar_String(
    +                                                value: a
    +                                            )
    +                                            right: Scalar_String(
    +                                                value: b
    +                                            )
    +                                        )
    +                                    )
    +                                    byRef: false
    +                                )
    +                            )
    +                        )
    +                        byRef: false
    +                        unpack: false
    +                    )
    +                )
    +            )
    +            5: Expr_Yield(
    +                key: null
    +                value: Expr_Yield(
    +                    key: Scalar_String(
    +                        value: k1
    +                    )
    +                    value: Expr_Yield(
    +                        key: Scalar_String(
    +                            value: k2
    +                        )
    +                        value: Expr_BinaryOp_Concat(
    +                            left: Scalar_String(
    +                                value: a
    +                            )
    +                            right: Scalar_String(
    +                                value: b
    +                            )
    +                        )
    +                    )
    +                )
    +            )
    +            6: Expr_Yield(
    +                key: Expr_Yield(
    +                    key: Scalar_String(
    +                        value: k1
    +                    )
    +                    value: Expr_Yield(
    +                        key: null
    +                        value: Scalar_String(
    +                            value: k2
    +                        )
    +                    )
    +                )
    +                value: Expr_BinaryOp_Concat(
    +                    left: Scalar_String(
    +                        value: a
    +                    )
    +                    right: Scalar_String(
    +                        value: b
    +                    )
    +                )
    +            )
    +            7: Expr_FuncCall(
    +                name: Name(
    +                    parts: array(
    +                        0: var_dump
    +                    )
    +                )
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Array(
    +                            items: array(
    +                                0: Expr_ArrayItem(
    +                                    key: null
    +                                    value: Expr_Yield(
    +                                        key: Scalar_String(
    +                                            value: k1
    +                                        )
    +                                        value: Expr_Yield(
    +                                            key: Scalar_String(
    +                                                value: k2
    +                                            )
    +                                            value: Expr_BinaryOp_Concat(
    +                                                left: Scalar_String(
    +                                                    value: a
    +                                                )
    +                                                right: Scalar_String(
    +                                                    value: b
    +                                                )
    +                                            )
    +                                        )
    +                                    )
    +                                    byRef: false
    +                                )
    +                            )
    +                        )
    +                        byRef: false
    +                        unpack: false
    +                    )
    +                )
    +            )
    +            8: Expr_FuncCall(
    +                name: Name(
    +                    parts: array(
    +                        0: var_dump
    +                    )
    +                )
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Array(
    +                            items: array(
    +                                0: Expr_ArrayItem(
    +                                    key: Expr_Yield(
    +                                        key: Scalar_String(
    +                                            value: k1
    +                                        )
    +                                        value: Expr_Yield(
    +                                            key: null
    +                                            value: Scalar_String(
    +                                                value: k2
    +                                            )
    +                                        )
    +                                    )
    +                                    value: Expr_BinaryOp_Concat(
    +                                        left: Scalar_String(
    +                                            value: a
    +                                        )
    +                                        right: Scalar_String(
    +                                            value: b
    +                                        )
    +                                    )
    +                                    byRef: false
    +                                )
    +                            )
    +                        )
    +                        byRef: false
    +                        unpack: false
    +                    )
    +                )
    +            )
    +        )
    +    )
    +)
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/stmt/generator/yieldUnaryPrecedence.test b/core/vendor/nikic/php-parser/test/code/parser/stmt/generator/yieldUnaryPrecedence.test
    new file mode 100644
    index 0000000..13f9660
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/stmt/generator/yieldUnaryPrecedence.test
    @@ -0,0 +1,48 @@
    +Yield with unary operator argument
    +-----
    +
    +Hallo World!
    +-----
    +array(
    +    0: Expr_Variable(
    +        name: a
    +    )
    +    1: Stmt_HaltCompiler(
    +        remaining: Hallo World!
    +    )
    +)
    +-----
    +
    +#!/usr/bin/env php
    +-----
    +array(
    +    0: Stmt_InlineHTML(
    +        value: #!/usr/bin/env php
    +
    +    )
    +    1: Stmt_Echo(
    +        exprs: array(
    +            0: Scalar_String(
    +                value: foobar
    +            )
    +        )
    +    )
    +    2: Stmt_InlineHTML(
    +        value: #!/usr/bin/env php
    +    )
    +)
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/stmt/if.test b/core/vendor/nikic/php-parser/test/code/parser/stmt/if.test
    new file mode 100644
    index 0000000..e054c89
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/stmt/if.test
    @@ -0,0 +1,103 @@
    +If/Elseif/Else
    +-----
    +
    +B
    +
    + $c) {}
    +foreach ($a as $b => &$c) {}
    +foreach ($a as list($a, $b)) {}
    +foreach ($a as $a => list($b, , $c)) {}
    +
    +// foreach on expression
    +foreach (array() as $b) {}
    +
    +// alternative syntax
    +foreach ($a as $b):
    +endforeach;
    +-----
    +array(
    +    0: Stmt_Foreach(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        keyVar: null
    +        byRef: false
    +        valueVar: Expr_Variable(
    +            name: b
    +        )
    +        stmts: array(
    +        )
    +        comments: array(
    +            0: // foreach on variable
    +        )
    +    )
    +    1: Stmt_Foreach(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        keyVar: null
    +        byRef: true
    +        valueVar: Expr_Variable(
    +            name: b
    +        )
    +        stmts: array(
    +        )
    +    )
    +    2: Stmt_Foreach(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        keyVar: Expr_Variable(
    +            name: b
    +        )
    +        byRef: false
    +        valueVar: Expr_Variable(
    +            name: c
    +        )
    +        stmts: array(
    +        )
    +    )
    +    3: Stmt_Foreach(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        keyVar: Expr_Variable(
    +            name: b
    +        )
    +        byRef: true
    +        valueVar: Expr_Variable(
    +            name: c
    +        )
    +        stmts: array(
    +        )
    +    )
    +    4: Stmt_Foreach(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        keyVar: null
    +        byRef: false
    +        valueVar: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: a
    +                )
    +                1: Expr_Variable(
    +                    name: b
    +                )
    +            )
    +        )
    +        stmts: array(
    +        )
    +    )
    +    5: Stmt_Foreach(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        keyVar: Expr_Variable(
    +            name: a
    +        )
    +        byRef: false
    +        valueVar: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: b
    +                )
    +                1: null
    +                2: Expr_Variable(
    +                    name: c
    +                )
    +            )
    +        )
    +        stmts: array(
    +        )
    +    )
    +    6: Stmt_Foreach(
    +        expr: Expr_Array(
    +            items: array(
    +            )
    +        )
    +        keyVar: null
    +        byRef: false
    +        valueVar: Expr_Variable(
    +            name: b
    +        )
    +        stmts: array(
    +        )
    +        comments: array(
    +            0: // foreach on expression
    +        )
    +    )
    +    7: Stmt_Foreach(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        keyVar: null
    +        byRef: false
    +        valueVar: Expr_Variable(
    +            name: b
    +        )
    +        stmts: array(
    +        )
    +        comments: array(
    +            0: // alternative syntax
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/parser/stmt/loop/while.test b/core/vendor/nikic/php-parser/test/code/parser/stmt/loop/while.test
    new file mode 100644
    index 0000000..65f6b23
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/parser/stmt/loop/while.test
    @@ -0,0 +1,25 @@
    +While loop
    +-----
    +
    +Hi!
    +-----
    +array(
    +    0: Stmt_Declare(
    +        declares: array(
    +            0: Stmt_DeclareDeclare(
    +                key: A
    +                value: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +        stmts: null
    +    )
    +    1: Stmt_Namespace(
    +        name: Name(
    +            parts: array(
    +                0: B
    +            )
    +        )
    +        stmts: array(
    +        )
    +    )
    +    2: Stmt_HaltCompiler(
    +        remaining: Hi!
    +    )
    +)
    +-----
    +a = $a;
    +    }
    +};
    +-----
    +new class
    +{
    +};
    +new class extends A implements B, C
    +{
    +};
    +new class($a) extends A
    +{
    +    private $a;
    +    public function __construct($a)
    +    {
    +        $this->a = $a;
    +    }
    +};
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/call.test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/call.test
    new file mode 100644
    index 0000000..0ec8925
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/call.test
    @@ -0,0 +1,13 @@
    +Calls
    +-----
    +d}
    +STR;
    +
    +call(
    +    <<d}
    +STR;
    +call(<<> $b;
    +$a < $b;
    +$a <= $b;
    +$a > $b;
    +$a >= $b;
    +$a == $b;
    +$a != $b;
    +$a <> $b;
    +$a === $b;
    +$a !== $b;
    +$a <=> $b;
    +$a & $b;
    +$a ^ $b;
    +$a | $b;
    +$a && $b;
    +$a || $b;
    +$a ? $b : $c;
    +$a ?: $c;
    +$a ?? $c;
    +
    +$a = $b;
    +$a **= $b;
    +$a *= $b;
    +$a /= $b;
    +$a %= $b;
    +$a += $b;
    +$a -= $b;
    +$a .= $b;
    +$a <<= $b;
    +$a >>= $b;
    +$a &= $b;
    +$a ^= $b;
    +$a |= $b;
    +$a =& $b;
    +
    +$a and $b;
    +$a xor $b;
    +$a or $b;
    +
    +$a instanceof Foo;
    +$a instanceof $b;
    +-----
    +$a ** $b;
    +++$a;
    +--$a;
    +$a++;
    +$a--;
    +@$a;
    +~$a;
    +-$a;
    ++$a;
    +(int) $a;
    +(int) $a;
    +(double) $a;
    +(double) $a;
    +(double) $a;
    +(string) $a;
    +(string) $a;
    +(array) $a;
    +(object) $a;
    +(bool) $a;
    +(bool) $a;
    +(unset) $a;
    +$a * $b;
    +$a / $b;
    +$a % $b;
    +$a + $b;
    +$a - $b;
    +$a . $b;
    +$a << $b;
    +$a >> $b;
    +$a < $b;
    +$a <= $b;
    +$a > $b;
    +$a >= $b;
    +$a == $b;
    +$a != $b;
    +$a != $b;
    +$a === $b;
    +$a !== $b;
    +$a <=> $b;
    +$a & $b;
    +$a ^ $b;
    +$a | $b;
    +$a && $b;
    +$a || $b;
    +$a ? $b : $c;
    +$a ?: $c;
    +$a ?? $c;
    +$a = $b;
    +$a **= $b;
    +$a *= $b;
    +$a /= $b;
    +$a %= $b;
    +$a += $b;
    +$a -= $b;
    +$a .= $b;
    +$a <<= $b;
    +$a >>= $b;
    +$a &= $b;
    +$a ^= $b;
    +$a |= $b;
    +$a =& $b;
    +$a and $b;
    +$a xor $b;
    +$a or $b;
    +$a instanceof Foo;
    +$a instanceof $b;
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/parentheses.test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/parentheses.test
    new file mode 100644
    index 0000000..1f18b65
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/parentheses.test
    @@ -0,0 +1,77 @@
    +Pretty printer generates least-parentheses output
    +-----
    + 0) > (1 < 0);
    +++$a + $b;
    +$a + $b++;
    +
    +$a ** $b ** $c;
    +($a ** $b) ** $c;
    +-1 ** 2;
    +
    +yield from $a and yield from $b;
    +yield from ($a and yield from $b);
    +
    +print ($a and print $b);
    +
    +// The following will currently add unnecessary parentheses, because the pretty printer is not aware that assignment
    +// and incdec only work on variables.
    +!$a = $b;
    +++$a ** $b;
    +$a ** $b++;
    +-----
    +echo 'abc' . 'cde' . 'fgh';
    +echo 'abc' . ('cde' . 'fgh');
    +echo 'abc' . 1 + 2 . 'fgh';
    +echo 'abc' . (1 + 2) . 'fgh';
    +echo 1 * 2 + 3 / 4 % 5 . 6;
    +echo 1 * (2 + 3) / (4 % (5 . 6));
    +$a = $b = $c = $d = $f && true;
    +($a = $b = $c = $d = $f) && true;
    +$a = $b = $c = $d = $f and true;
    +$a = $b = $c = $d = ($f and true);
    +$a ? $b : $c ? $d : $e ? $f : $g;
    +$a ? $b : ($c ? $d : ($e ? $f : $g));
    +$a ? $b ? $c : $d : $f;
    +$a ?? $b ?? $c;
    +($a ?? $b) ?? $c;
    +$a ?? ($b ? $c : $d);
    +$a || ($b ?? $c);
    +(1 > 0) > (1 < 0);
    +++$a + $b;
    +$a + $b++;
    +$a ** $b ** $c;
    +($a ** $b) ** $c;
    +-1 ** 2;
    +yield from $a and yield from $b;
    +yield from ($a and yield from $b);
    +print ($a and print $b);
    +// The following will currently add unnecessary parentheses, because the pretty printer is not aware that assignment
    +// and incdec only work on variables.
    +!($a = $b);
    +(++$a) ** $b;
    +$a ** ($b++);
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/shortArraySyntax.test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/shortArraySyntax.test
    new file mode 100644
    index 0000000..082c2e0
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/shortArraySyntax.test
    @@ -0,0 +1,11 @@
    +Short array syntax
    +-----
    + 'b', 'c' => 'd'];
    +-----
    +[];
    +array(1, 2, 3);
    +['a' => 'b', 'c' => 'd'];
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/stringEscaping.test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/stringEscaping.test
    new file mode 100644
    index 0000000..02877ad
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/stringEscaping.test
    @@ -0,0 +1,23 @@
    +Escape sequences in double-quoted strings
    +-----
    +b)();
    +(A::$b)();
    +-----
    +!!php7
    +(function () {
    +})();
    +array('a', 'b')()();
    +A::$b::$c;
    +$A::$b[$c]();
    +$A::{$b[$c]}();
    +A::${$b}[$c]();
    +($a->b)();
    +(A::$b)();
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/variables.test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/variables.test
    new file mode 100644
    index 0000000..4e0fa2e
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/variables.test
    @@ -0,0 +1,73 @@
    +Variables
    +-----
    +b;
    +$a->b();
    +$a->b($c);
    +$a->$b();
    +$a->{$b}();
    +$a->$b[$c]();
    +$$a->b;
    +$a[$b];
    +$a[$b]();
    +$$a[$b];
    +$a::B;
    +$a::$b;
    +$a::b();
    +$a::b($c);
    +$a::$b();
    +$a::$b[$c];
    +$a::$b[$c]($d);
    +$a::{$b[$c]}($d);
    +$a::{$b->c}();
    +A::$$b[$c]();
    +a();
    +$a();
    +$a()[$b];
    +$a->b()[$c];
    +$a::$b()[$c];
    +(new A)->b;
    +(new A())->b();
    +(new $$a)[$b];
    +(new $a->b)->c;
    +
    +global $a, $$a, $$a[$b], $$a->b;
    +-----
    +!!php5
    +$a;
    +${$a};
    +${$a};
    +$a->b;
    +$a->b();
    +$a->b($c);
    +$a->{$b}();
    +$a->{$b}();
    +$a->{$b[$c]}();
    +${$a}->b;
    +$a[$b];
    +$a[$b]();
    +${$a[$b]};
    +$a::B;
    +$a::$b;
    +$a::b();
    +$a::b($c);
    +$a::$b();
    +$a::$b[$c];
    +$a::{$b[$c]}($d);
    +$a::{$b[$c]}($d);
    +$a::{$b->c}();
    +A::${$b[$c]}();
    +a();
    +$a();
    +$a()[$b];
    +$a->b()[$c];
    +$a::$b()[$c];
    +(new A())->b;
    +(new A())->b();
    +(new ${$a}())[$b];
    +(new $a->b())->c;
    +global $a, ${$a}, ${$a[$b]}, ${$a->b};
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/yield.test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/yield.test
    new file mode 100644
    index 0000000..12ab7de
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/expr/yield.test
    @@ -0,0 +1,46 @@
    +Yield
    +-----
    + $b;
    +    $a = yield;
    +    $a = (yield $b);
    +    $a = (yield $b => $c);
    +}
    +// TODO Get rid of parens for cases 2 and 3
    +-----
    +function gen()
    +{
    +    yield;
    +    (yield $a);
    +    (yield $a => $b);
    +    $a = yield;
    +    $a = (yield $b);
    +    $a = (yield $b => $c);
    +}
    +// TODO Get rid of parens for cases 2 and 3
    +-----
    + $c;
    +    yield from $a;
    +    $a = yield from $b;
    +}
    +// TODO Get rid of parens for last case
    +-----
    +!!php7
    +function gen()
    +{
    +    $a = (yield $b);
    +    $a = (yield $b => $c);
    +    yield from $a;
    +    $a = (yield from $b);
    +}
    +// TODO Get rid of parens for last case
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/inlineHTMLandPHPtest.file-test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/inlineHTMLandPHPtest.file-test
    new file mode 100644
    index 0000000..74a2685
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/inlineHTMLandPHPtest.file-test
    @@ -0,0 +1,52 @@
    +File containing both inline HTML and PHP
    +-----
    +HTML
    +
    +HTML
    +-----
    +
    +HTML
    +-----
    +HTML
    +
    +HTML
    +-----
    +HTML
    +
    +HTML
    +-----
    +HTML
    +
    +HTML
    +
    +HTML
    +-----
    +HTML
    +
    +HTML
    +
    +HTML
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/onlyInlineHTML.file-test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/onlyInlineHTML.file-test
    new file mode 100644
    index 0000000..0e2d4f7
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/onlyInlineHTML.file-test
    @@ -0,0 +1,11 @@
    +File containing only inline HTML
    +-----
    +Hallo World
    +Foo Bar
    +Bar Foo
    +World Hallo
    +-----
    +Hallo World
    +Foo Bar
    +Bar Foo
    +World Hallo
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/onlyPHP.file-test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/onlyPHP.file-test
    new file mode 100644
    index 0000000..9550b10
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/onlyPHP.file-test
    @@ -0,0 +1,16 @@
    +File containing only PHP
    +-----
    +a = 'bar';
    +        echo 'test';
    +    }
    +
    +    protected function baz() {}
    +    public function foo() {}
    +    abstract static function bar() {}
    +}
    +
    +trait Bar
    +{
    +    function test()
    +    {
    +    }
    +}
    +-----
    +class Foo extends Bar implements ABC, \DEF, namespace\GHI
    +{
    +    var $a = 'foo';
    +    private $b = 'bar';
    +    static $c = 'baz';
    +    function test()
    +    {
    +        $this->a = 'bar';
    +        echo 'test';
    +    }
    +    protected function baz()
    +    {
    +    }
    +    public function foo()
    +    {
    +    }
    +    static abstract function bar()
    +    {
    +    }
    +}
    +trait Bar
    +{
    +    function test()
    +    {
    +    }
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/stmt/const.test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/stmt/const.test
    new file mode 100644
    index 0000000..6b17642
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/stmt/const.test
    @@ -0,0 +1,11 @@
    +Constant declarations
    +-----
    + $val) {
    +
    +}
    +
    +foreach ($arr as $key => &$val) {
    +
    +}
    +-----
    +foreach ($arr as $val) {
    +}
    +foreach ($arr as &$val) {
    +}
    +foreach ($arr as $key => $val) {
    +}
    +foreach ($arr as $key => &$val) {
    +}
    \ No newline at end of file
    diff --git a/core/vendor/nikic/php-parser/test/code/prettyPrinter/stmt/function_signatures.test b/core/vendor/nikic/php-parser/test/code/prettyPrinter/stmt/function_signatures.test
    new file mode 100644
    index 0000000..af1088a
    --- /dev/null
    +++ b/core/vendor/nikic/php-parser/test/code/prettyPrinter/stmt/function_signatures.test
    @@ -0,0 +1,43 @@
    +Function signatures
    +-----
    +parse($code);
    +        $parseTime += microtime(true) - $startTime;
    +
    +        $startTime = microtime(true);
    +        $code = 'prettyPrint($stmts);
    +        $ppTime += microtime(true) - $startTime;
    +
    +        try {
    +            $startTime = microtime(true);
    +            $ppStmts = $parser->parse($code);
    +            $reparseTime += microtime(true) - $startTime;
    +
    +            $startTime = microtime(true);
    +            $same = $nodeDumper->dump($stmts) == $nodeDumper->dump($ppStmts);
    +            $compareTime += microtime(true) - $startTime;
    +
    +            if (!$same) {
    +                echo $file, ":\n    Result of initial parse and parse after pretty print differ\n";
    +                if ($verbose) {
    +                    echo "Pretty printer output:\n=====\n$code\n=====\n\n";
    +                }
    +
    +                ++$compareFail;
    +            }
    +        } catch (PhpParser\Error $e) {
    +            echo $file, ":\n    Parse of pretty print failed with message: {$e->getMessage()}\n";
    +            if ($verbose) {
    +                echo "Pretty printer output:\n=====\n$code\n=====\n\n";
    +            }
    +
    +            ++$ppFail;
    +        }
    +    } catch (PhpParser\Error $e) {
    +        echo $file, ":\n    Parse failed with message: {$e->getMessage()}\n";
    +
    +        ++$parseFail;
    +    }
    +}
    +
    +if (0 === $parseFail && 0 === $ppFail && 0 === $compareFail) {
    +    $exit = 0;
    +    echo "\n\n", 'All tests passed.', "\n";
    +} else {
    +    $exit = 1;
    +    echo "\n\n", '==========', "\n\n", 'There were: ', "\n";
    +    if (0 !== $parseFail) {
    +        echo '    ', $parseFail,   ' parse failures.',        "\n";
    +    }
    +    if (0 !== $ppFail) {
    +        echo '    ', $ppFail,      ' pretty print failures.', "\n";
    +    }
    +    if (0 !== $compareFail) {
    +        echo '    ', $compareFail, ' compare failures.',      "\n";
    +    }
    +}
    +
    +echo "\n",
    +     'Tested files:         ', $count,        "\n",
    +     "\n",
    +     'Reading files took:   ', $readTime,    "\n",
    +     'Parsing took:         ', $parseTime,   "\n",
    +     'Pretty printing took: ', $ppTime,      "\n",
    +     'Reparsing took:       ', $reparseTime, "\n",
    +     'Comparing took:       ', $compareTime, "\n",
    +     "\n",
    +     'Total time:           ', microtime(true) - $totalStartTime, "\n",
    +     'Maximum memory usage: ', memory_get_peak_usage(true), "\n";
    +
    +exit($exit);
    diff --git a/core/vendor/paragonie/random_compat/CHANGELOG.md b/core/vendor/paragonie/random_compat/CHANGELOG.md
    new file mode 100644
    index 0000000..56d5a7c
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/CHANGELOG.md
    @@ -0,0 +1,290 @@
    +### Version 1.4.3 - 2018-04-04
    +
    +* Fix version number in constant in lib/random.php
    +* Upgrade your dependency from `^1` to `^1|^2` if you want other
    +  changes (i.e. better compatibility with type-safety), because the
    +  v2 branch is where most of the development effort is focused.
    +  Continued support for v1.x is considered "only for emergencies".
    +
    +### Version 1.4.2 - 2017-03-13
    +
    +* Backport changes from version 2:
    +  * Version 2.0.9 - 2017-03-03
    +    * More Psalm integration fixes.
    +  * Version 2.0.8 - 2017-03-03
    +    * Prevent function already declared error for `random_int()` caused by misusing
    +      the library (really you should only ever include `lib/random.php` and never any 
    +      of the other files). See [#125](https://github.com/paragonie/random_compat/issues/125).
    +  * Version 2.0.6, 2.0.7 - 2017-02-27
    +    * Just updates to psalm.xml to silence false positives.
    +  * Version 2.0.5 - 2017-02-27
    +    * Run random_compat through the static analysis tool, [psalm](https://github.com/vimeo/psalm),
    +      as part of our continuous integration process.
    +    * Minor readability enhancements ([#122](https://github.com/paragonie/random_compat/issues/122)
    +      and several docblock changes).
    +  * Version 2.0.4 - 2016-11-07
    +     * Don't unnecessarily prevent `mcrypt_create_iv()` from being used.
    +       See [#111](https://github.com/paragonie/random_compat/issues/111).
    +  * Version 2.0.3 - 2016-10-17
    +    * Updated `lib/error_polyfill.php` [to resolve corner cases](https://github.com/paragonie/random_compat/issues/104).
    +    * The README was updated to help users troubleshoot and fix insecure environments.
    +    * Tags will now be signed by [the GnuPG key used by the security team at Paragon Initiative Enterprises, LLC](https://paragonie.com/static/gpg-public-key.txt).
    +  * Version 2.0.2 - 2016-04-03
    +    * Added a consistency check (discovered by Taylor Hornby in his 
    +      [PHP encryption library](https://github.com/defuse/php-encryption)). It
    +      wasn't likely causing any trouble for us.
    +
    +### Version 1.4.1 - 2016-03-18
    +
    +Update comment in random.php
    +
    +### Version 1.4.0 - 2016-03-18
    +
    +Restored OpenSSL in the version 1 branch in preparation to remove
    +OpenSSL in version 2.
    +
    +### Version 1.3.1/1.2.3 - 2016-03-18
    +
    +* Add more possible values to `open_baseir` check.
    +
    +### Version 1.3.0 - 2016-03-17
    +
    +* Removed `openssl_random_pseudo_bytes()` entirely. If you are using
    +  random_compat in PHP on a Unix-like OS but cannot access
    +  `/dev/urandom`, version 1.3+ will throw an `Exception`. If you want to
    +  trust OpenSSL, feel free to write your own fallback code. e.g.
    +  
    +  ```php
    +  try {
    +      $bytes = random_bytes(32);
    +  } catch (Exception $ex) {
    +      $strong = false;
    +      $bytes = openssl_random_pseudo_bytes(32, $strong);
    +      if (!$strong) {
    +          throw $ex;
    +      }
    +  }
    +  ```
    +
    +### Version 1.2.2 - 2016-03-11
    +
    +* To prevent applications from hanging, if `/dev/urandom` is not
    +  accessible to PHP, skip mcrypt (which just fails before giving OpenSSL
    +  a chance and was morally equivalent to not offering OpenSSL at all).
    +
    +### Version 1.2.1 - 2016-02-29
    +
    +* PHP 5.6.10 - 5.6.12 will hang when mcrypt is used on Unix-based operating 
    +  systems ([PHP bug 69833](https://bugs.php.net/bug.php?id=69833)). If you are
    +  running one of these versions, please upgrade (or make sure `/dev/urandom` is
    +  readable) otherwise you're relying on OpenSSL.
    +
    +### Version 1.2.0 - 2016-02-05
    +
    +* Whitespace and other cosmetic changes
    +* Added a changelog.
    +* We now ship with a command line utility to build a PHP Archive from the 
    +  command line.
    +  
    +  Every time we publish a new release, we will also upload a .phar
    +  to Github. Our public key is signed by our GPG key.
    +
    +### Version 1.1.6 - 2016-01-29
    +
    +* Eliminate `open_basedir` warnings by detecting this configuration setting. 
    +  (Thanks [@oucil](https://github.com/oucil) for reporting this.)
    +* Added install instructions to the README.
    +* Documentation cleanup (there is, in fact, no `MCRYPT_CREATE_IV` constant, I 
    +  meant to write `MCRYPT_DEV_URANDOM`)
    +
    +### Version 1.1.5 - 2016-01-06
    +
    +Prevent fatal errors on platforms with older versions of libsodium.
    +
    +### Version 1.1.4 - 2015-12-10
    +
    +Thanks [@narfbg](https://github.com/narfbg) for [critiquing the previous patch](https://github.com/paragonie/random_compat/issues/79#issuecomment-163590589)
    +and suggesting a fix.
    +
    +### Version 1.1.3 - 2015-12-09
    +
    +The test for COM in disabled_classes is now case-insensitive.
    +
    +### Version 1.1.2 - 2015-12-09
    +
    +Don't instantiate COM if it's a disabled class. Removes the E_WARNING on Windows.
    +
    +### Version 1.1.1 - 2015-11-30
    +
    +Fix a performance issue with `/dev/urandom` buffering.
    +
    +### Version 1.1.0 - 2015-11-09
    +
    +Fix performance issues with ancient versions of PHP on Windows, but dropped 
    +support for PHP < 5.4.1 without mcrypt on Windows 7+ in the process. Since this
    + is a BC break, semver dictates a minor version bump.
    +
    +### Version 1.0.10 - 2015-10-23
    +
    +* Avoid a performance killer with OpenSSL on Windows PHP 5.3.0 - 5.3.3 that was 
    +  affecting [WordPress users](https://core.trac.wordpress.org/ticket/34409).
    +* Use `$var = null` instead of `unset($var)` to avoid triggering the garbage 
    +  collector and slowing things down.
    +
    +### Version 1.0.9 - 2015-10-20
    +
    +There is an outstanding issue `mcrypt_create_iv()` and PHP 7's `random_bytes()`
    +on Windows reported by [@nicolas-grekas](https://github.com/nicolas-grekas) caused by `proc_open()` and environment
    +variable handling (discovered by Appveyor when developing Symfony).
    +
    +Since the break is consistent, it's not our responsibility to fix it, but we 
    +should fail the same way PHP 7 will (i.e. throw an `Exception` rather than raise
    +an error and then throw an `Exception`).
    +
    +### Version 1.0.8 - 2015-10-18
    +
    +* Fix usability issues with Windows (`new COM('CAPICOM.Utilities.1')` is not 
    +  always available).
    +* You can now test all the possible drivers by running `phpunit.sh each` in the
    +  `tests` directory.
    +
    +### Version 1.0.7 - 2015-10-16
    +
    +Several large integer handling bugfixes were contributed by [@oittaa](https://github.com/oittaa).
    +
    +### Version 1.0.6 - 2015-10-15
    +
    +Don't let the version number fool you, this was a pretty significant change.
    +
    +1. Added support for ext-libsodium, if it exists on the system. This is morally
    +   equivalent to adding `getrandom(2)` support without having to expose the 
    +   syscall interface in PHP-land.
    +2. Relaxed open_basedir restrictions. In previous versions, if open_basedir was 
    +   set, PHP wouldn't even try to read from `/dev/urandom`. Now it will still do 
    +   so if you can.
    +3. Fixed integer casting inconsistencies between random_compat and PHP 7.
    +4. Handle edge cases where an integer overflow turns one of the parameters into
    +   a float.
    +
    +One change that we discussed was making `random_bytes()` and `random_int()` 
    +strict typed; meaning you could *only* pass integers to either function. While 
    +most veteran programmers are probably only doing this already (we strongly 
    +encourage it), it wouldn't be consistent with how these functions behave in PHP
    +7. Please use these functions responsibly.
    +
    +We've had even more of the PHP community involved in this release; the 
    +contributors list has been updated. If I forgot anybody, I promise you it's not
    +because your contributions (either code or ideas) aren't valued, it's because 
    +I'm a bit overloaded with information at the moment. Please let me know 
    +immediately and I will correct my oversight.
    +
    +Thanks everyone for helping make random_compat better. 
    +
    +### Version 1.0.5 - 2015-10-08
    +
    +Got rid of the methods in the `Throwable` interface, which was causing problems 
    +on PHP 5.2. While we would normally not care about 5.2 (since [5.4 and earlier are EOL'd](https://secure.php.net/supported-versions.php)),
    +we do want to encourage widespread adoption (e.g. [Wordpress](https://core.trac.wordpress.org/ticket/28633)).
    +
    +### Version 1.0.4 - 2015-10-02
    +
    +Removed redundant `if()` checks, since `lib/random.php` is the entrypoint people
    +should use.
    +
    +### Version 1.0.3 - 2015-10-02
    +
    +This release contains bug fixes contributed by the community.
    +
    +* Avoid a PHP Notice when PHP is running without the mbstring extension
    +* Use a compatible version of PHPUnit for testing on older versions of PHP
    +
    +Although none of these bugs were outright security-affecting, updating ASAP is
    +still strongly encouraged.
    +
    +### Version 1.0.2 - 2015-09-23
    +
    +Less strict input validation on `random_int()` parameters. PHP 7's `random_int()`
    +accepts strings and floats that look like numbers, so we should too.
    +
    +Thanks [@dd32](https://github.com/@dd32) for correcting this oversight.
    +
    +### Version 1.0.1 - 2015-09-10
    +
    +Instead of throwing an Exception immediately on insecure platforms, only do so 
    +when `random_bytes()` is invoked.
    +
    +### Version 1.0.0 - 2015-09-07
    +
    +Our API is now stable and forward-compatible with the CSPRNG features in PHP 7
    +(as of 7.0.0 RC3).
    +
    +A lot of great people have contributed their time and expertise to make this 
    +compatibility library possible. That this library has reached a stable release 
    +is more a reflection on the community than it is on PIE.
    +
    +We are confident that random_compat will serve as the simplest and most secure
    +CSPRNG interface available for PHP5 projects.
    +
    +### Version 0.9.7 (pre-release) - 2015-09-01
    +
    +An attempt to achieve compatibility with Error/TypeError in the RFC.
    +
    +This should be identical to 1.0.0 sans any last-minute changes or performance enhancements.
    +
    +### Version 0.9.6 (pre-release) - 2015-08-06
    +
    +* Split the implementations into their own file (for ease of auditing)
    +* Corrected the file type check after `/dev/urandom` has been opened (thanks
    +  [@narfbg](https://github.com/narfbg) and [@jedisct1](https://github.com/jedisct1))
    +
    +### Version 0.9.5 (pre-release) - 2015-07-31
    +
    +* Validate that `/dev/urandom` is a character device 
    +  * Reported by [@lokdnet](https://twitter.com/lokdnet)
    +  * Investigated by [@narfbg](https://github.com/narfbg) and [frymaster](http://stackoverflow.com/users/1226810/frymaster) on [StackOverflow](http://stackoverflow.com/q/31631066/2224584)
    +* Remove support for `/dev/arandom` which is an old OpenBSD feature, thanks [@jedisct1](https://github.com/jedisct1)
    +* Prevent race conditions on the `filetype()` check, thanks [@jedisct1](https://github.com/jedisct1)
    +* Buffer file reads to 8 bytes (performance optimization; PHP defaults to 8192 bytes)
    +
    +### Version 0.9.4 (pre-release) - 2015-07-27
    +
    +* Add logic to verify that `/dev/arandom` and `/dev/urandom` are actually devices.
    +* Some clean-up in the comments
    +
    +### Version 0.9.3 (pre-release) - 2015-07-22
    +
    +Unless the Exceptions change to PHP 7 fails, this should be the last pre-release
    +version. If need be, we'll make one more pre-release version with compatible 
    +behavior.
    +
    +Changes since 0.9.2:
    +
    +* Prioritize `/dev/arandom` and `/dev/urandom` over mcrypt.
    +[@oittaa](https://github.com/oittaa) removed the -1 and +1 juggling on `$range` calculations for `random_int()`
    +* Whitespace and comment clean-up, plus better variable names
    +* Actually put a description in the composer.json file...
    +
    +### Version 0.9.2 (pre-release) - 2015-07-16
    +
    +* Consolidated `$range > PHP_INT_MAX` logic with `$range <= PHP_INT_MAX` (thanks
    +  [@oittaa](https://github.com/oittaa) and [@CodesInChaos](https://github.com/CodesInChaos))
    +* `tests/phpunit.sh` now also runs the tests with `mbstring.func_overload` and 
    +  `open_basedir`
    +* Style consistency, whitespace cleanup, more meaningful variable names
    +
    +### Version 0.9.1 (pre-release) - 2015-07-09
    +
    +* Return random values on integer ranges > `PHP_INT_MAX` (thanks [@CodesInChaos](https://github.com/CodesInChaos))
    +* Determined CSPRNG preference:
    +    1. `mcrypt_create_iv()` with `MCRYPT_DEV_URANDOM`
    +    2. `/dev/arandom`
    +    3. `/dev/urandom`
    +    4. `openssl_random_pseudo_bytes()`
    +* Optimized backend selection (thanks [@lt](https://github.com/lt))
    +* Fix #3 (thanks [@scottchiefbaker](https://github.com/scottchiefbaker))
    +
    +### Version 0.9.0 (pre-release) - 2015-07-07
    +
    +This should be a sane polyfill for PHP 7's `random_bytes()` and `random_int()`.
    +We hesitate to call it production ready until it has received sufficient third
    +party review.
    \ No newline at end of file
    diff --git a/core/vendor/paragonie/random_compat/LICENSE b/core/vendor/paragonie/random_compat/LICENSE
    new file mode 100644
    index 0000000..45c7017
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/LICENSE
    @@ -0,0 +1,22 @@
    +The MIT License (MIT)
    +
    +Copyright (c) 2015 Paragon Initiative Enterprises
    +
    +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/core/vendor/paragonie/random_compat/RATIONALE.md b/core/vendor/paragonie/random_compat/RATIONALE.md
    new file mode 100644
    index 0000000..a6e7307
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/RATIONALE.md
    @@ -0,0 +1,42 @@
    +## Rationale (Design Decisions)
    +
    +### Reasoning Behind the Order of Preferred Random Data Sources
    +
    +The order is:
    +
    + 1. `libsodium if available`
    + 2. `fread() /dev/urandom if available`
    + 3. `mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)`
    + 4. `COM('CAPICOM.Utilities.1')->GetRandom()`
    + 5. `openssl_random_pseudo_bytes()`
    +
    +If libsodium is available, we get random data from it. This is the preferred
    +method on all OSes, but libsodium is not very widely installed, so other
    +fallbacks are available.
    +
    +Next, we read `/dev/urandom` (if it exists). This is the preferred file to read
    +for random data for cryptographic purposes for BSD and Linux. This step
    +is skipped on Windows, because someone could create a `C:\dev\urandom`
    +file and PHP would helpfully (but insecurely) return bytes from it.
    +
    +Despite [strongly urging people not to use mcrypt in their projects](https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong)
    +(because libmcrypt is abandonware and the API puts too much responsibility on the
    +implementor) we prioritize `mcrypt_create_iv()` with `MCRYPT_DEV_URANDOM` above
    +the remaining implementations.
    +
    +The reason is simple: `mcrypt_create_iv()` is part of PHP's `ext/mcrypt` code,
    +and is not part `libmcrypt`. It actually does the right thing:
    +
    + * On Unix-based operating systems, it reads from `/dev/urandom` which
    +   (unlike `/dev/random`) is the sane and correct thing to do.
    + * On Windows, it reads from `CryptGenRandom`, which is an exclusively Windows
    +   way to get random bytes.
    +
    +If we're on Windows and don't have access to `mcrypt`, we use `CAPICOM.Utilities.1`.
    +
    +Finally, we use `openssl_random_pseudo_bytes()` **as a last resort**, due to
    +[PHP bug #70014](https://bugs.php.net/bug.php?id=70014). Internally, this 
    +function calls `RAND_pseudo_bytes()`, which has been [deprecated](https://github.com/paragonie/random_compat/issues/5)
    +by the OpenSSL team. Furthermore, [it might silently return weak random data](https://github.com/paragonie/random_compat/issues/6#issuecomment-119564973)
    +if it is called before OpenSSL's **userspace** CSPRNG is seeded. Also, 
    +[you want the OS CSPRNG, not a userspace CSPRNG](http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/).
    diff --git a/core/vendor/paragonie/random_compat/README.md b/core/vendor/paragonie/random_compat/README.md
    new file mode 100644
    index 0000000..63c6d71
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/README.md
    @@ -0,0 +1,176 @@
    +# random_compat
    +
    +[![Build Status](https://travis-ci.org/paragonie/random_compat.svg?branch=v1.x)](https://travis-ci.org/paragonie/random_compat)
    +[![Scrutinizer](https://scrutinizer-ci.com/g/paragonie/random_compat/badges/quality-score.png?b=v1.x)](https://scrutinizer-ci.com/g/paragonie/random_compat)
    +
    +PHP 5.x polyfill for `random_bytes()` and `random_int()` created and maintained
    +by [Paragon Initiative Enterprises](https://paragonie.com).
    +
    +Although this library *should* function in earlier versions of PHP, we will only
    +consider issues relevant to [supported PHP versions](https://secure.php.net/supported-versions.php).
    +**If you are using an unsupported version of PHP, please upgrade as soon as possible.**
    +
    +## Important
    +
    +Although this library has been examined by some security experts in the PHP 
    +community, there will always be a chance that we overlooked something. Please 
    +ask your favorite trusted hackers to hammer it for implementation errors and
    +bugs before even thinking about deploying it in production.
    +
    +**Do not use the master branch, use a [stable release](https://github.com/paragonie/random_compat/releases/latest).**
    +
    +For the background of this library, please refer to our blog post on 
    +[Generating Random Integers and Strings in PHP](https://paragonie.com/blog/2015/07/how-safely-generate-random-strings-and-integers-in-php).
    +
    +### Usability Notice
    +
    +If PHP cannot safely generate random data, this library will throw an `Exception`.
    +It will never fall back to insecure random data. If this keeps happening, upgrade
    +to a newer version of PHP immediately.
    +
    +## Installing
    +
    +**With [Composer](https://getcomposer.org):**
    +
    +    composer require paragonie/random_compat
    +
    +**Signed PHP Archive:**
    +
    +As of version 1.2.0, we also ship an ECDSA-signed PHP Archive with each stable 
    +release on Github.
    +
    +1. Download [the `.phar`, `.phar.pubkey`, and `.phar.pubkey.asc`](https://github.com/paragonie/random_compat/releases/latest) files.
    +2. (**Recommended** but not required) Verify the PGP signature of `.phar.pubkey` 
    +   (contained within the `.asc` file) using the [PGP public key for Paragon Initiative Enterprises](https://paragonie.com/static/gpg-public-key.txt).
    +3. Extract both `.phar` and `.phar.pubkey` files to the same directory.
    +4. `require_once "/path/to/random_compat.phar";`
    +5. When a new version is released, you only need to replace the `.phar` file;
    +   the `.pubkey` will not change (unless our signing key is ever compromised).
    +
    +**Manual Installation:**
    +
    +1. Download [a stable release](https://github.com/paragonie/random_compat/releases/latest).
    +2. Extract the files into your project.
    +3. `require_once "/path/to/random_compat/lib/random.php";`
    +
    +## Usage
    +
    +This library exposes the [CSPRNG functions added in PHP 7](https://secure.php.net/manual/en/ref.csprng.php)
    +for use in PHP 5 projects. Their behavior should be identical.
    +
    +### Generate a string of random bytes
    +
    +```php
    +try {
    +    $string = random_bytes(32);
    +} catch (TypeError $e) {
    +    // Well, it's an integer, so this IS unexpected.
    +    die("An unexpected error has occurred"); 
    +} catch (Error $e) {
    +    // This is also unexpected because 32 is a reasonable integer.
    +    die("An unexpected error has occurred");
    +} catch (Exception $e) {
    +    // If you get this message, the CSPRNG failed hard.
    +    die("Could not generate a random string. Is our OS secure?");
    +}
    +
    +var_dump(bin2hex($string));
    +// string(64) "5787c41ae124b3b9363b7825104f8bc8cf27c4c3036573e5f0d4a91ad2eeac6f"
    +```
    +
    +### Generate a random integer between two given integers (inclusive)
    +
    +```php
    +try {
    +    $int = random_int(0,255);
    +
    +} catch (TypeError $e) {
    +    // Well, it's an integer, so this IS unexpected.
    +    die("An unexpected error has occurred"); 
    +} catch (Error $e) {
    +    // This is also unexpected because 0 and 255 are both reasonable integers.
    +    die("An unexpected error has occurred");
    +} catch (Exception $e) {
    +    // If you get this message, the CSPRNG failed hard.
    +    die("Could not generate a random string. Is our OS secure?");
    +}
    +
    +var_dump($int);
    +// int(47)
    +```
    +
    +### Exception handling
    +
    +When handling exceptions and errors you must account for differences between
    +PHP 5 and PHP7.
    +
    +The differences:
    +
    +* Catching `Error` works, so long as it is caught before `Exception`.
    +* Catching `Exception` has different behavior, without previously catching `Error`.
    +* There is *no* portable way to catch all errors/exceptions.
    +
    +#### Our recommendation
    +
    +**Always** catch `Error` before `Exception`.
    +
    +#### Example
    +
    +```php
    +try {
    +    return random_int(1, $userInput);
    +} catch (TypeError $e) {
    +    // This is okay, so long as `Error` is caught before `Exception`.
    +    throw new Exception('Please enter a number!');
    +} catch (Error $e) {
    +    // This is required, if you do not need to do anything just rethrow.
    +    throw $e;
    +} catch (Exception $e) {
    +    // This is optional and maybe omitted if you do not want to handle errors
    +    // during generation.
    +    throw new InternalServerErrorException(
    +        'Oops, our server is bust and cannot generate any random data.',
    +        500,
    +        $e
    +    );
    +}
    +```
    +
    +## Contributors
    +
    +This project would not be anywhere near as excellent as it is today if it 
    +weren't for the contributions of the following individuals:
    +
    +* [@AndrewCarterUK (Andrew Carter)](https://github.com/AndrewCarterUK)
    +* [@asgrim (James Titcumb)](https://github.com/asgrim)
    +* [@bcremer (Benjamin Cremer)](https://github.com/bcremer)
    +* [@CodesInChaos (Christian Winnerlein)](https://github.com/CodesInChaos)
    +* [@chriscct7 (Chris Christoff)](https://github.com/chriscct7)
    +* [@cs278 (Chris Smith)](https://github.com/cs278)
    +* [@cweagans (Cameron Eagans)](https://github.com/cweagans)
    +* [@dd32 (Dion Hulse)](https://github.com/dd32)
    +* [@geggleto (Glenn Eggleton)](https://github.com/geggleto)
    +* [@ircmaxell (Anthony Ferrara)](https://github.com/ircmaxell)
    +* [@jedisct1 (Frank Denis)](https://github.com/jedisct1)
    +* [@juliangut (Julián Gutiérrez)](https://github.com/juliangut)
    +* [@kelunik (Niklas Keller)](https://github.com/kelunik)
    +* [@lt (Leigh)](https://github.com/lt)
    +* [@MasonM (Mason Malone)](https://github.com/MasonM)
    +* [@mmeyer2k (Michael M)](https://github.com/mmeyer2k)
    +* [@narfbg (Andrey Andreev)](https://github.com/narfbg)
    +* [@nicolas-grekas (Nicolas Grekas)](https://github.com/nicolas-grekas)
    +* [@oittaa](https://github.com/oittaa)
    +* [@oucil (Kevin Farley)](https://github.com/oucil)
    +* [@redragonx (Stephen Chavez)](https://github.com/redragonx)
    +* [@rchouinard (Ryan Chouinard)](https://github.com/rchouinard)
    +* [@SammyK (Sammy Kaye Powers)](https://github.com/SammyK)
    +* [@scottchiefbaker (Scott Baker)](https://github.com/scottchiefbaker)
    +* [@skyosev (Stoyan Kyosev)](https://github.com/skyosev)
    +* [@stof (Christophe Coevoet)](https://github.com/stof)
    +* [@teohhanhui (Teoh Han Hui)](https://github.com/teohhanhui)
    +* [@tom-- (Tom Worster)](https://github.com/tom--)
    +* [@tsyr2ko](https://github.com/tsyr2ko)
    +* [@trowski (Aaron Piotrowski)](https://github.com/trowski)
    +* [@twistor (Chris Lepannen)](https://github.com/twistor)
    +* [@voku (Lars Moelleken)](https://github.com/voku)
    +* [@xabbuh (Christian Flothmann)](https://github.com/xabbuh)
    diff --git a/core/vendor/paragonie/random_compat/SECURITY.md b/core/vendor/paragonie/random_compat/SECURITY.md
    new file mode 100644
    index 0000000..8f731b3
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/SECURITY.md
    @@ -0,0 +1,108 @@
    +# An Invitation to Security Researchers
    +
    +Every company says they take security "very seriously." Rather than bore anyone 
    +with banal boilerplate, here are some quick answers followed by detailed
    +elaboration. If you have any questions about our policies, please email them to
    +`scott@paragonie.com`.
    +
    +## Quick Answers
    +
    +* There is no compulsion to disclose vulnerabilities privately, but we 
    +  appreciate a head's up.
    +* `security@paragonie.com` will get your reports to the right person. Our GPG 
    +  fingerprint, should you decide to encrypt your report, is 
    +  `7F52 D5C6 1D12 55C7 3136  2E82 6B97 A1C2 8264 04DA`.
    +
    +* **YES**, we will reward security researchers who disclose vulnerabilities in
    +  our software.
    +* In most cases, **No Proof-of-Concept Required.**
    +
    +## How to Report a Security Bug to Paragon Initiative Enterprises
    +
    +### There is no compulsion to disclose privately.
    +
    +We believe vulnerability disclosure style is a personal choice and enjoy working
    +with a diverse community. We understand and appreciate the importance of Full 
    +Disclosure in the history and practice of security research.
    +
    +We would *like* to know about high-severity bugs before they become public
    +knowledge, so we can fix them in a timely manner, but **we do not believe in 
    +threatening researchers or trying to enforce vulnerability embargoes**.
    +
    +Ultimately, if you discover a security-affecting vulnerability, what you do with
    +it is your choice. We would like to work with people, and to celebrate and 
    +reward their skill, experience, and dedication. We appreciate being informed of
    +our mistakes so we can learn from them and build a better product. Our goal is
    +to empower the community.
    +
    +### Where to Send Security Vulnerabilities
    +
    +Our security email address is `security@paragonie.com`. Also feel free to open a
    +new issue on Github if you want to disclose publicly.
    +
    +```
    +-----BEGIN PGP PUBLIC KEY BLOCK-----
    +Version: GnuPG
    +
    +mQENBFUgwRUBCADcIpqNwyYc5UmY/tpx1sF/rQ3knR1YNXYZThzFV+Gmqhp1fDH5
    +qBs9foh1xwI6O7knWmQngnf/nBumI3x6xj7PuOdEZUh2FwCG/VWnglW8rKmoHzHA
    +ivjiu9SLnPIPAgHSHeh2XD7q3Ndm3nenbjAiRFNl2iXcwA2cTQp9Mmfw9vVcw0G0
    +z1o0G3s8cC8ZS6flFySIervvfSRWj7A1acI5eE3+AH/qXJRdEJ+9J8OB65p1JMfk
    +6+fWgOB1XZxMpz70S0rW6IX38WDSRhEK2fXyZJAJjyt+YGuzjZySNSoQR/V6vNYn
    +syrNPCJ2i5CgZQxAkyBBcr7koV9RIhPRzct/ABEBAAG0IVNlY3VyaXR5IDxzZWN1
    +cml0eUBwYXJhZ29uaWUuY29tPokBOQQTAQIAIwUCVSDBFQIbAwcLCQgHAwIBBhUI
    +AgkKCwQWAgMBAh4BAheAAAoJEGuXocKCZATat2YIAIoejNFEQ2c1iaOEtSuB7Pn/
    +WLbsDsHNLDKOV+UnfaCjv/vL7D+5NMChFCi2frde/NQb2TsjqmIH+V+XbnJtlrXD
    +Vj7yvMVal+Jqjwj7v4eOEWcKVcFZk+9cfUgh7t92T2BMX58RpgZF0IQZ6Z1R3FfC
    +9Ub4X6ykW+te1q0/4CoRycniwmlQi6iGSr99LQ5pfJq2Qlmz/luTZ0UX0h575T7d
    +cp2T1sX/zFRk/fHeANWSksipdDBjAXR7NMnYZgw2HghEdFk/xRDY7K1NRWNZBf05
    +WrMHmh6AIVJiWZvI175URxEe268hh+wThBhXQHMhFNJM1qPIuzb4WogxM3UUD7m5
    +AQ0EVSDBFQEIALNkpzSuJsHAHh79sc0AYWztdUe2MzyofQbbOnOCpWZebYsC3EXU
    +335fIg59k0m6f+O7GmEZzzIv5v0i99GS1R8CJm6FvhGqtH8ZqmOGbc71WdJSiNVE
    +0kpQoJlVzRbig6ZyyjzrggbM1eh5OXOk5pw4+23FFEdw7JWU0HJS2o71r1hwp05Z
    +vy21kcUEobz/WWQQyGS0Neo7PJn+9KS6wOxXul/UE0jct/5f7KLMdWMJ1VgniQmm
    +hjvkHLPSICteqCI04RfcmMseW9gueHQXeUu1SNIvsWa2MhxjeBej3pDnrZWszKwy
    +gF45GO9/v4tkIXNMy5J1AtOyRgQ3IUMqp8EAEQEAAYkBHwQYAQIACQUCVSDBFQIb
    +DAAKCRBrl6HCgmQE2jnIB/4/xFz8InpM7eybnBOAir3uGcYfs3DOmaKn7qWVtGzv
    +rKpQPYnVtlU2i6Z5UO4c4jDLT/8Xm1UDz3Lxvqt4xCaDwJvBZexU5BMK8l5DvOzH
    +6o6P2L1UDu6BvmPXpVZz7/qUhOnyf8VQg/dAtYF4/ax19giNUpI5j5o5mX5w80Rx
    +qSXV9NdSL4fdjeG1g/xXv2luhoV53T1bsycI3wjk/x5tV+M2KVhZBvvuOm/zhJje
    +oLWp0saaESkGXIXqurj6gZoujJvSvzl0n9F9VwqMEizDUfrXgtD1siQGhP0sVC6q
    +ha+F/SAEJ0jEquM4TfKWWU2S5V5vgPPpIQSYRnhQW4b1
    +=xJPW
    +-----END PGP PUBLIC KEY BLOCK-----
    +```
    +
    +### We Will Reward Security Researchers
    +
    +**This process has not been formalized; nor have dollar amounts been 
    +discussed.**
    +
    +However, if you report a valid security-affecting bug, we will compensate you
    +for the time spent finding the vulnerability and reward you for being a good
    +neighbor.
    +
    +#### What does a "valid" bug mean?
    +
    +There are two sides to this:
    +
    +1. Some have spammed projects with invalid bug reports hoping to collect
    +   bounties for pressing a button and running an automated analysis tool. This
    +   is not cool.
    +2. There is a potential for the developers of a project to declare all security
    +   bug reports as invalid to save money.
    +
    +Our team members have an established history of reporting vulnerabilities to
    +large open source projects. **We aren't in the business of ripping people off.**
    +When in doubt, our policy is to err on the side of generosity.
    +
    +### No Proof-of-Concept Required
    +
    +We might ask for one if we feel we do not understand some of the details 
    +pertaining to a specific vulnerability. We certainly appreciate them if you 
    +include them in your report, but we believe **the burden lies with the developer
    +to prove their software *is* secure** rather than with the researcher to prove
    +that it isn't.
    +
    +In our experience, most bugs are simpler to fix than they are to exploit.
    +
    diff --git a/core/vendor/paragonie/random_compat/build-phar.sh b/core/vendor/paragonie/random_compat/build-phar.sh
    new file mode 100644
    index 0000000..b4a5ba3
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/build-phar.sh
    @@ -0,0 +1,5 @@
    +#!/usr/bin/env bash
    +
    +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )
    +
    +php -dphar.readonly=0 "$basedir/other/build_phar.php" $*
    \ No newline at end of file
    diff --git a/core/vendor/paragonie/random_compat/composer.json b/core/vendor/paragonie/random_compat/composer.json
    new file mode 100644
    index 0000000..d363f4c
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/composer.json
    @@ -0,0 +1,35 @@
    +{
    +    "name":         "paragonie/random_compat",
    +    "description":  "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
    +    "keywords": [
    +            "csprng",
    +            "random",
    +            "pseudorandom"
    +        ],
    +    "license":      "MIT",
    +    "type":         "library",
    +    "authors": [
    +            {
    +                "name":     "Paragon Initiative Enterprises",
    +                "email":    "security@paragonie.com",
    +                "homepage": "https://paragonie.com"
    +            }
    +        ],
    +    "support": {
    +        "issues":   "https://github.com/paragonie/random_compat/issues",
    +        "email":    "info@paragonie.com",
    +        "source":   "https://github.com/paragonie/random_compat"
    +    },
    +    "require": {
    +        "php": ">=5.2.0"
    +    },
    +    "require-dev": {
    +        "phpunit/phpunit": "4.*|5.*"
    +    },
    +    "suggest": {
    +        "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
    +    },
    +    "autoload": {
    +        "files": ["lib/random.php"]
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/core/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey
    new file mode 100644
    index 0000000..eb50ebf
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey
    @@ -0,0 +1,5 @@
    +-----BEGIN PUBLIC KEY-----
    +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
    +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
    ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
    +-----END PUBLIC KEY-----
    diff --git a/core/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/core/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc
    new file mode 100644
    index 0000000..6a1d7f3
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc
    @@ -0,0 +1,11 @@
    +-----BEGIN PGP SIGNATURE-----
    +Version: GnuPG v2.0.22 (MingW32)
    +
    +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
    +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
    +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
    +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
    +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
    +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
    +=B6+8
    +-----END PGP SIGNATURE-----
    diff --git a/core/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/core/vendor/paragonie/random_compat/lib/byte_safe_strings.php
    new file mode 100644
    index 0000000..6de294f
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/lib/byte_safe_strings.php
    @@ -0,0 +1,181 @@
    + RandomCompat_strlen($binary_string)) {
    +                return '';
    +            }
    +
    +            return (string) mb_substr($binary_string, $start, $length, '8bit');
    +        }
    +
    +    } else {
    +
    +        /**
    +         * substr() implementation that isn't brittle to mbstring.func_overload
    +         *
    +         * This version just uses the default substr()
    +         *
    +         * @param string $binary_string
    +         * @param int $start
    +         * @param int $length (optional)
    +         *
    +         * @throws TypeError
    +         *
    +         * @return string
    +         */
    +        function RandomCompat_substr($binary_string, $start, $length = null)
    +        {
    +            if (!is_string($binary_string)) {
    +                throw new TypeError(
    +                    'RandomCompat_substr(): First argument should be a string'
    +                );
    +            }
    +
    +            if (!is_int($start)) {
    +                throw new TypeError(
    +                    'RandomCompat_substr(): Second argument should be an integer'
    +                );
    +            }
    +
    +            if ($length !== null) {
    +                if (!is_int($length)) {
    +                    throw new TypeError(
    +                        'RandomCompat_substr(): Third argument should be an integer, or omitted'
    +                    );
    +                }
    +
    +                return (string) substr($binary_string, $start, $length);
    +            }
    +
    +            return (string) substr($binary_string, $start);
    +        }
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/lib/cast_to_int.php b/core/vendor/paragonie/random_compat/lib/cast_to_int.php
    new file mode 100644
    index 0000000..dc4048c
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/lib/cast_to_int.php
    @@ -0,0 +1,74 @@
    + operators might accidentally let a float
    +     * through.
    +     *
    +     * @param int|float $number    The number we want to convert to an int
    +     * @param boolean   $fail_open Set to true to not throw an exception
    +     *
    +     * @return float|int
    +     *
    +     * @throws TypeError
    +     */
    +    function RandomCompat_intval($number, $fail_open = false)
    +    {
    +        if (is_int($number) || is_float($number)) {
    +            $number += 0;
    +        } elseif (is_numeric($number)) {
    +            $number += 0;
    +        }
    +
    +        if (
    +            is_float($number)
    +            &&
    +            $number > ~PHP_INT_MAX
    +            &&
    +            $number < PHP_INT_MAX
    +        ) {
    +            $number = (int) $number;
    +        }
    +
    +        if (is_int($number)) {
    +            return (int) $number;
    +        } elseif (!$fail_open) {
    +            throw new TypeError(
    +                'Expected an integer.'
    +            );
    +        }
    +        return $number;
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/lib/error_polyfill.php b/core/vendor/paragonie/random_compat/lib/error_polyfill.php
    new file mode 100644
    index 0000000..17ece7b
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/lib/error_polyfill.php
    @@ -0,0 +1,49 @@
    += 70000) {
    +    return;
    +}
    +
    +if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
    +    define('RANDOM_COMPAT_READ_BUFFER', 8);
    +}
    +
    +$RandomCompatDIR = dirname(__FILE__);
    +
    +require_once $RandomCompatDIR . '/byte_safe_strings.php';
    +require_once $RandomCompatDIR . '/cast_to_int.php';
    +require_once $RandomCompatDIR . '/error_polyfill.php';
    +
    +if (!is_callable('random_bytes')) {
    +    /**
    +     * PHP 5.2.0 - 5.6.x way to implement random_bytes()
    +     *
    +     * We use conditional statements here to define the function in accordance
    +     * to the operating environment. It's a micro-optimization.
    +     *
    +     * In order of preference:
    +     *   1. Use libsodium if available.
    +     *   2. fread() /dev/urandom if available (never on Windows)
    +     *   3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
    +     *   4. COM('CAPICOM.Utilities.1')->GetRandom()
    +     *
    +     * See RATIONALE.md for our reasoning behind this particular order
    +     */
    +    if (extension_loaded('libsodium')) {
    +        // See random_bytes_libsodium.php
    +        if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
    +            require_once $RandomCompatDIR . '/random_bytes_libsodium.php';
    +        } elseif (method_exists('Sodium', 'randombytes_buf')) {
    +            require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php';
    +        }
    +    }
    +
    +    /**
    +     * Reading directly from /dev/urandom:
    +     */
    +    if (DIRECTORY_SEPARATOR === '/') {
    +        // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
    +        // way to exclude Windows.
    +        $RandomCompatUrandom = true;
    +        $RandomCompat_basedir = ini_get('open_basedir');
    +
    +        if (!empty($RandomCompat_basedir)) {
    +            $RandomCompat_open_basedir = explode(
    +                PATH_SEPARATOR,
    +                strtolower($RandomCompat_basedir)
    +            );
    +            $RandomCompatUrandom = (array() !== array_intersect(
    +                    array('/dev', '/dev/', '/dev/urandom'),
    +                    $RandomCompat_open_basedir
    +                ));
    +            $RandomCompat_open_basedir = null;
    +        }
    +
    +        if (
    +            !is_callable('random_bytes')
    +            &&
    +            $RandomCompatUrandom
    +            &&
    +            @is_readable('/dev/urandom')
    +        ) {
    +            // Error suppression on is_readable() in case of an open_basedir
    +            // or safe_mode failure. All we care about is whether or not we
    +            // can read it at this point. If the PHP environment is going to
    +            // panic over trying to see if the file can be read in the first
    +            // place, that is not helpful to us here.
    +
    +            // See random_bytes_dev_urandom.php
    +            require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php';
    +        }
    +        // Unset variables after use
    +        $RandomCompat_basedir = null;
    +    } else {
    +        $RandomCompatUrandom = false;
    +    }
    +
    +    /**
    +     * mcrypt_create_iv()
    +     *
    +     * We only want to use mcypt_create_iv() if:
    +     *
    +     * - random_bytes() hasn't already been defined
    +     * - the mcrypt extensions is loaded
    +     * - One of these two conditions is true:
    +     *   - We're on Windows (DIRECTORY_SEPARATOR !== '/')
    +     *   - We're not on Windows and /dev/urandom is readabale
    +     *     (i.e. we're not in a chroot jail)
    +     * - Special case:
    +     *   - If we're not on Windows, but the PHP version is between
    +     *     5.6.10 and 5.6.12, we don't want to use mcrypt. It will
    +     *     hang indefinitely. This is bad.
    +     *   - If we're on Windows, we want to use PHP >= 5.3.7 or else
    +     *     we get insufficient entropy errors.
    +     */
    +    if (
    +        !is_callable('random_bytes')
    +        &&
    +        // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
    +        (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
    +        &&
    +        // Prevent this code from hanging indefinitely on non-Windows;
    +        // see https://bugs.php.net/bug.php?id=69833
    +        (
    +            DIRECTORY_SEPARATOR !== '/' ||
    +            (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
    +        )
    +        &&
    +        extension_loaded('mcrypt')
    +    ) {
    +        // See random_bytes_mcrypt.php
    +        require_once $RandomCompatDIR . '/random_bytes_mcrypt.php';
    +    }
    +    $RandomCompatUrandom = null;
    +
    +    /**
    +     * This is a Windows-specific fallback, for when the mcrypt extension
    +     * isn't loaded.
    +     */
    +    if (
    +        !is_callable('random_bytes')
    +        &&
    +        extension_loaded('com_dotnet')
    +        &&
    +        class_exists('COM')
    +    ) {
    +        $RandomCompat_disabled_classes = preg_split(
    +            '#\s*,\s*#',
    +            strtolower(ini_get('disable_classes'))
    +        );
    +
    +        if (!in_array('com', $RandomCompat_disabled_classes)) {
    +            try {
    +                $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
    +                if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
    +                    // See random_bytes_com_dotnet.php
    +                    require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php';
    +                }
    +            } catch (com_exception $e) {
    +                // Don't try to use it.
    +            }
    +        }
    +        $RandomCompat_disabled_classes = null;
    +        $RandomCompatCOMtest = null;
    +    }
    +
    +    /**
    +     * openssl_random_pseudo_bytes()
    +     */
    +    if (
    +        (
    +            // Unix-like with PHP >= 5.3.0 or
    +            (
    +                DIRECTORY_SEPARATOR === '/'
    +                &&
    +                PHP_VERSION_ID >= 50300
    +            )
    +            ||
    +            // Windows with PHP >= 5.4.1
    +            PHP_VERSION_ID >= 50401
    +        )
    +        &&
    +        !function_exists('random_bytes')
    +        &&
    +        extension_loaded('openssl')
    +    ) {
    +        // See random_bytes_openssl.php
    +        require_once $RandomCompatDIR . '/random_bytes_openssl.php';
    +    }
    +
    +    /**
    +     * throw new Exception
    +     */
    +    if (!is_callable('random_bytes')) {
    +        /**
    +         * We don't have any more options, so let's throw an exception right now
    +         * and hope the developer won't let it fail silently.
    +         *
    +         * @param mixed $length
    +         * @return void
    +         * @throws Exception
    +         */
    +        function random_bytes($length)
    +        {
    +            unset($length); // Suppress "variable not used" warnings.
    +            throw new Exception(
    +                'There is no suitable CSPRNG installed on your system'
    +            );
    +        }
    +    }
    +}
    +
    +if (!is_callable('random_int')) {
    +    require_once $RandomCompatDIR . '/random_int.php';
    +}
    +
    +$RandomCompatDIR = null;
    diff --git a/core/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/core/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php
    new file mode 100644
    index 0000000..28cc56a
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php
    @@ -0,0 +1,88 @@
    +GetRandom($bytes, 0));
    +            if (RandomCompat_strlen($buf) >= $bytes) {
    +                /**
    +                 * Return our random entropy buffer here:
    +                 */
    +                return RandomCompat_substr($buf, 0, $bytes);
    +            }
    +            ++$execCount;
    +        } while ($execCount < $bytes);
    +
    +        /**
    +         * If we reach here, PHP has failed us.
    +         */
    +        throw new Exception(
    +            'Could not gather sufficient random data'
    +        );
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/core/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php
    new file mode 100644
    index 0000000..8bf7034
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php
    @@ -0,0 +1,150 @@
    + 0);
    +
    +            /**
    +             * Is our result valid?
    +             */
    +            if ($buf !== false) {
    +                if (RandomCompat_strlen($buf) === $bytes) {
    +                    /**
    +                     * Return our random entropy buffer here:
    +                     */
    +                    return $buf;
    +                }
    +            }
    +        }
    +
    +        /**
    +         * If we reach here, PHP has failed us.
    +         */
    +        throw new Exception(
    +            'Error reading from source device'
    +        );
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/core/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php
    new file mode 100644
    index 0000000..7d32b21
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php
    @@ -0,0 +1,88 @@
    + 2147483647) {
    +            $buf = '';
    +            for ($i = 0; $i < $bytes; $i += 1073741824) {
    +                $n = ($bytes - $i) > 1073741824
    +                    ? 1073741824
    +                    : $bytes - $i;
    +                $buf .= \Sodium\randombytes_buf($n);
    +            }
    +        } else {
    +            $buf = \Sodium\randombytes_buf($bytes);
    +        }
    +
    +        if ($buf !== false) {
    +            if (RandomCompat_strlen($buf) === $bytes) {
    +                return $buf;
    +            }
    +        }
    +
    +        /**
    +         * If we reach here, PHP has failed us.
    +         */
    +        throw new Exception(
    +            'Could not gather sufficient random data'
    +        );
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/core/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php
    new file mode 100644
    index 0000000..ba93c40
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php
    @@ -0,0 +1,92 @@
    + 2147483647) {
    +            for ($i = 0; $i < $bytes; $i += 1073741824) {
    +                $n = ($bytes - $i) > 1073741824
    +                    ? 1073741824
    +                    : $bytes - $i;
    +                $buf .= Sodium::randombytes_buf($n);
    +            }
    +        } else {
    +            $buf .= Sodium::randombytes_buf($bytes);
    +        }
    +
    +        if (is_string($buf)) {
    +            if (RandomCompat_strlen($buf) === $bytes) {
    +                return $buf;
    +            }
    +        }
    +
    +        /**
    +         * If we reach here, PHP has failed us.
    +         */
    +        throw new Exception(
    +            'Could not gather sufficient random data'
    +        );
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/core/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php
    new file mode 100644
    index 0000000..3bce91a
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php
    @@ -0,0 +1,77 @@
    + operators might accidentally let a float
    +         * through.
    +         */
    +
    +        try {
    +            $min = RandomCompat_intval($min);
    +        } catch (TypeError $ex) {
    +            throw new TypeError(
    +                'random_int(): $min must be an integer'
    +            );
    +        }
    +
    +        try {
    +            $max = RandomCompat_intval($max);
    +        } catch (TypeError $ex) {
    +            throw new TypeError(
    +                'random_int(): $max must be an integer'
    +            );
    +        }
    +
    +        /**
    +         * Now that we've verified our weak typing system has given us an integer,
    +         * let's validate the logic then we can move forward with generating random
    +         * integers along a given range.
    +         */
    +        if ($min > $max) {
    +            throw new Error(
    +                'Minimum value must be less than or equal to the maximum value'
    +            );
    +        }
    +
    +        if ($max === $min) {
    +            return $min;
    +        }
    +
    +        /**
    +         * Initialize variables to 0
    +         *
    +         * We want to store:
    +         * $bytes => the number of random bytes we need
    +         * $mask => an integer bitmask (for use with the &) operator
    +         *          so we can minimize the number of discards
    +         */
    +        $attempts = $bits = $bytes = $mask = $valueShift = 0;
    +
    +        /**
    +         * At this point, $range is a positive number greater than 0. It might
    +         * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
    +         * a float and we will lose some precision.
    +         */
    +        $range = $max - $min;
    +
    +        /**
    +         * Test for integer overflow:
    +         */
    +        if (!is_int($range)) {
    +
    +            /**
    +             * Still safely calculate wider ranges.
    +             * Provided by @CodesInChaos, @oittaa
    +             *
    +             * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
    +             *
    +             * We use ~0 as a mask in this case because it generates all 1s
    +             *
    +             * @ref https://eval.in/400356 (32-bit)
    +             * @ref http://3v4l.org/XX9r5  (64-bit)
    +             */
    +            $bytes = PHP_INT_SIZE;
    +            $mask = ~0;
    +
    +        } else {
    +
    +            /**
    +             * $bits is effectively ceil(log($range, 2)) without dealing with
    +             * type juggling
    +             */
    +            while ($range > 0) {
    +                if ($bits % 8 === 0) {
    +                    ++$bytes;
    +                }
    +                ++$bits;
    +                $range >>= 1;
    +                $mask = $mask << 1 | 1;
    +            }
    +            $valueShift = $min;
    +        }
    +
    +        $val = 0;
    +        /**
    +         * Now that we have our parameters set up, let's begin generating
    +         * random integers until one falls between $min and $max
    +         */
    +        do {
    +            /**
    +             * The rejection probability is at most 0.5, so this corresponds
    +             * to a failure probability of 2^-128 for a working RNG
    +             */
    +            if ($attempts > 128) {
    +                throw new Exception(
    +                    'random_int: RNG is broken - too many rejections'
    +                );
    +            }
    +
    +            /**
    +             * Let's grab the necessary number of random bytes
    +             */
    +            $randomByteString = random_bytes($bytes);
    +
    +            /**
    +             * Let's turn $randomByteString into an integer
    +             *
    +             * This uses bitwise operators (<< and |) to build an integer
    +             * out of the values extracted from ord()
    +             *
    +             * Example: [9F] | [6D] | [32] | [0C] =>
    +             *   159 + 27904 + 3276800 + 201326592 =>
    +             *   204631455
    +             */
    +            $val &= 0;
    +            for ($i = 0; $i < $bytes; ++$i) {
    +                $val |= ord($randomByteString[$i]) << ($i * 8);
    +            }
    +
    +            /**
    +             * Apply mask
    +             */
    +            $val &= $mask;
    +            $val += $valueShift;
    +
    +            ++$attempts;
    +            /**
    +             * If $val overflows to a floating point number,
    +             * ... or is larger than $max,
    +             * ... or smaller than $min,
    +             * then try again.
    +             */
    +        } while (!is_int($val) || $val > $max || $val < $min);
    +
    +        return (int)$val;
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/other/build_phar.php b/core/vendor/paragonie/random_compat/other/build_phar.php
    new file mode 100644
    index 0000000..70ef4b2
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/other/build_phar.php
    @@ -0,0 +1,57 @@
    +buildFromDirectory(dirname(__DIR__).'/lib');
    +rename(
    +    dirname(__DIR__).'/lib/index.php', 
    +    dirname(__DIR__).'/lib/random.php'
    +);
    +
    +/**
    + * If we pass an (optional) path to a private key as a second argument, we will
    + * sign the Phar with OpenSSL.
    + * 
    + * If you leave this out, it will produce an unsigned .phar!
    + */
    +if ($argc > 1) {
    +    if (!@is_readable($argv[1])) {
    +        echo 'Could not read the private key file:', $argv[1], "\n";
    +        exit(255);
    +    }
    +    $pkeyFile = file_get_contents($argv[1]);
    +    
    +    $private = openssl_get_privatekey($pkeyFile);
    +    if ($private !== false) {
    +        $pkey = '';
    +        openssl_pkey_export($private, $pkey);
    +        $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
    +        
    +        /**
    +         * Save the corresponding public key to the file
    +         */
    +        if (!@is_readable($dist.'/random_compat.phar.pubkey')) {
    +            $details = openssl_pkey_get_details($private);
    +            file_put_contents(
    +                $dist.'/random_compat.phar.pubkey',
    +                $details['key']
    +            );
    +        }
    +    } else {
    +        echo 'An error occurred reading the private key from OpenSSL.', "\n";
    +        exit(255);
    +    }
    +}
    diff --git a/core/vendor/paragonie/random_compat/other/ide_stubs/COM.php b/core/vendor/paragonie/random_compat/other/ide_stubs/COM.php
    new file mode 100644
    index 0000000..4ba4bb3
    --- /dev/null
    +++ b/core/vendor/paragonie/random_compat/other/ide_stubs/COM.php
    @@ -0,0 +1,20 @@
    +
    +
    +    
    +        
    +    
    +    
    +        
    +        
    +        
    +    
    +
    diff --git a/core/vendor/psr/log/.gitignore b/core/vendor/psr/log/.gitignore
    new file mode 100644
    index 0000000..22d0d82
    --- /dev/null
    +++ b/core/vendor/psr/log/.gitignore
    @@ -0,0 +1 @@
    +vendor
    diff --git a/core/vendor/psr/log/LICENSE b/core/vendor/psr/log/LICENSE
    new file mode 100644
    index 0000000..474c952
    --- /dev/null
    +++ b/core/vendor/psr/log/LICENSE
    @@ -0,0 +1,19 @@
    +Copyright (c) 2012 PHP Framework Interoperability Group
    +
    +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/core/vendor/psr/log/Psr/Log/AbstractLogger.php b/core/vendor/psr/log/Psr/Log/AbstractLogger.php
    new file mode 100644
    index 0000000..90e721a
    --- /dev/null
    +++ b/core/vendor/psr/log/Psr/Log/AbstractLogger.php
    @@ -0,0 +1,128 @@
    +log(LogLevel::EMERGENCY, $message, $context);
    +    }
    +
    +    /**
    +     * Action must be taken immediately.
    +     *
    +     * Example: Entire website down, database unavailable, etc. This should
    +     * trigger the SMS alerts and wake you up.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function alert($message, array $context = array())
    +    {
    +        $this->log(LogLevel::ALERT, $message, $context);
    +    }
    +
    +    /**
    +     * Critical conditions.
    +     *
    +     * Example: Application component unavailable, unexpected exception.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function critical($message, array $context = array())
    +    {
    +        $this->log(LogLevel::CRITICAL, $message, $context);
    +    }
    +
    +    /**
    +     * Runtime errors that do not require immediate action but should typically
    +     * be logged and monitored.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function error($message, array $context = array())
    +    {
    +        $this->log(LogLevel::ERROR, $message, $context);
    +    }
    +
    +    /**
    +     * Exceptional occurrences that are not errors.
    +     *
    +     * Example: Use of deprecated APIs, poor use of an API, undesirable things
    +     * that are not necessarily wrong.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function warning($message, array $context = array())
    +    {
    +        $this->log(LogLevel::WARNING, $message, $context);
    +    }
    +
    +    /**
    +     * Normal but significant events.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function notice($message, array $context = array())
    +    {
    +        $this->log(LogLevel::NOTICE, $message, $context);
    +    }
    +
    +    /**
    +     * Interesting events.
    +     *
    +     * Example: User logs in, SQL logs.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function info($message, array $context = array())
    +    {
    +        $this->log(LogLevel::INFO, $message, $context);
    +    }
    +
    +    /**
    +     * Detailed debug information.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function debug($message, array $context = array())
    +    {
    +        $this->log(LogLevel::DEBUG, $message, $context);
    +    }
    +}
    diff --git a/core/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/core/vendor/psr/log/Psr/Log/InvalidArgumentException.php
    new file mode 100644
    index 0000000..67f852d
    --- /dev/null
    +++ b/core/vendor/psr/log/Psr/Log/InvalidArgumentException.php
    @@ -0,0 +1,7 @@
    +logger = $logger;
    +    }
    +}
    diff --git a/core/vendor/psr/log/Psr/Log/LoggerInterface.php b/core/vendor/psr/log/Psr/Log/LoggerInterface.php
    new file mode 100644
    index 0000000..5ea7243
    --- /dev/null
    +++ b/core/vendor/psr/log/Psr/Log/LoggerInterface.php
    @@ -0,0 +1,123 @@
    +log(LogLevel::EMERGENCY, $message, $context);
    +    }
    +
    +    /**
    +     * Action must be taken immediately.
    +     *
    +     * Example: Entire website down, database unavailable, etc. This should
    +     * trigger the SMS alerts and wake you up.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function alert($message, array $context = array())
    +    {
    +        $this->log(LogLevel::ALERT, $message, $context);
    +    }
    +
    +    /**
    +     * Critical conditions.
    +     *
    +     * Example: Application component unavailable, unexpected exception.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function critical($message, array $context = array())
    +    {
    +        $this->log(LogLevel::CRITICAL, $message, $context);
    +    }
    +
    +    /**
    +     * Runtime errors that do not require immediate action but should typically
    +     * be logged and monitored.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function error($message, array $context = array())
    +    {
    +        $this->log(LogLevel::ERROR, $message, $context);
    +    }
    +
    +    /**
    +     * Exceptional occurrences that are not errors.
    +     *
    +     * Example: Use of deprecated APIs, poor use of an API, undesirable things
    +     * that are not necessarily wrong.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function warning($message, array $context = array())
    +    {
    +        $this->log(LogLevel::WARNING, $message, $context);
    +    }
    +
    +    /**
    +     * Normal but significant events.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function notice($message, array $context = array())
    +    {
    +        $this->log(LogLevel::NOTICE, $message, $context);
    +    }
    +
    +    /**
    +     * Interesting events.
    +     *
    +     * Example: User logs in, SQL logs.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function info($message, array $context = array())
    +    {
    +        $this->log(LogLevel::INFO, $message, $context);
    +    }
    +
    +    /**
    +     * Detailed debug information.
    +     *
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function debug($message, array $context = array())
    +    {
    +        $this->log(LogLevel::DEBUG, $message, $context);
    +    }
    +
    +    /**
    +     * Logs with an arbitrary level.
    +     *
    +     * @param mixed  $level
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    abstract public function log($level, $message, array $context = array());
    +}
    diff --git a/core/vendor/psr/log/Psr/Log/NullLogger.php b/core/vendor/psr/log/Psr/Log/NullLogger.php
    new file mode 100644
    index 0000000..d8cd682
    --- /dev/null
    +++ b/core/vendor/psr/log/Psr/Log/NullLogger.php
    @@ -0,0 +1,28 @@
    +logger) { }`
    + * blocks.
    + */
    +class NullLogger extends AbstractLogger
    +{
    +    /**
    +     * Logs with an arbitrary level.
    +     *
    +     * @param mixed  $level
    +     * @param string $message
    +     * @param array  $context
    +     *
    +     * @return void
    +     */
    +    public function log($level, $message, array $context = array())
    +    {
    +        // noop
    +    }
    +}
    diff --git a/core/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/core/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
    new file mode 100644
    index 0000000..4b861c3
    --- /dev/null
    +++ b/core/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
    @@ -0,0 +1,144 @@
    + ".
    +     *
    +     * Example ->error('Foo') would yield "error Foo".
    +     *
    +     * @return string[]
    +     */
    +    abstract public function getLogs();
    +
    +    public function testImplements()
    +    {
    +        $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
    +    }
    +
    +    /**
    +     * @dataProvider provideLevelsAndMessages
    +     */
    +    public function testLogsAtAllLevels($level, $message)
    +    {
    +        $logger = $this->getLogger();
    +        $logger->{$level}($message, array('user' => 'Bob'));
    +        $logger->log($level, $message, array('user' => 'Bob'));
    +
    +        $expected = array(
    +            $level.' message of level '.$level.' with context: Bob',
    +            $level.' message of level '.$level.' with context: Bob',
    +        );
    +        $this->assertEquals($expected, $this->getLogs());
    +    }
    +
    +    public function provideLevelsAndMessages()
    +    {
    +        return array(
    +            LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
    +            LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
    +            LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
    +            LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
    +            LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
    +            LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
    +            LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
    +            LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
    +        );
    +    }
    +
    +    /**
    +     * @expectedException \Psr\Log\InvalidArgumentException
    +     */
    +    public function testThrowsOnInvalidLevel()
    +    {
    +        $logger = $this->getLogger();
    +        $logger->log('invalid level', 'Foo');
    +    }
    +
    +    public function testContextReplacement()
    +    {
    +        $logger = $this->getLogger();
    +        $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
    +
    +        $expected = array('info {Message {nothing} Bob Bar a}');
    +        $this->assertEquals($expected, $this->getLogs());
    +    }
    +
    +    public function testObjectCastToString()
    +    {
    +        if (method_exists($this, 'createPartialMock')) {
    +            $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
    +        } else {
    +            $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
    +        }
    +        $dummy->expects($this->once())
    +            ->method('__toString')
    +            ->will($this->returnValue('DUMMY'));
    +
    +        $this->getLogger()->warning($dummy);
    +
    +        $expected = array('warning DUMMY');
    +        $this->assertEquals($expected, $this->getLogs());
    +    }
    +
    +    public function testContextCanContainAnything()
    +    {
    +        $closed = fopen('php://memory', 'r');
    +        fclose($closed);
    +
    +        $context = array(
    +            'bool' => true,
    +            'null' => null,
    +            'string' => 'Foo',
    +            'int' => 0,
    +            'float' => 0.5,
    +            'nested' => array('with object' => new DummyTest),
    +            'object' => new \DateTime,
    +            'resource' => fopen('php://memory', 'r'),
    +            'closed' => $closed,
    +        );
    +
    +        $this->getLogger()->warning('Crazy context data', $context);
    +
    +        $expected = array('warning Crazy context data');
    +        $this->assertEquals($expected, $this->getLogs());
    +    }
    +
    +    public function testContextExceptionKeyCanBeExceptionOrOtherValues()
    +    {
    +        $logger = $this->getLogger();
    +        $logger->warning('Random message', array('exception' => 'oops'));
    +        $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
    +
    +        $expected = array(
    +            'warning Random message',
    +            'critical Uncaught Exception!'
    +        );
    +        $this->assertEquals($expected, $this->getLogs());
    +    }
    +}
    +
    +class DummyTest
    +{
    +    public function __toString()
    +    {
    +    }
    +}
    diff --git a/core/vendor/psr/log/Psr/Log/Test/TestLogger.php b/core/vendor/psr/log/Psr/Log/Test/TestLogger.php
    new file mode 100644
    index 0000000..0cdffe4
    --- /dev/null
    +++ b/core/vendor/psr/log/Psr/Log/Test/TestLogger.php
    @@ -0,0 +1,146 @@
    + $level,
    +            'message' => $message,
    +            'context' => $context,
    +        ];
    +
    +        $this->recordsByLevel[$record['level']][] = $record;
    +        $this->records[] = $record;
    +    }
    +
    +    public function hasRecords($level)
    +    {
    +        return isset($this->recordsByLevel[$level]);
    +    }
    +
    +    public function hasRecord($record, $level)
    +    {
    +        if (is_string($record)) {
    +            $record = ['message' => $record];
    +        }
    +        return $this->hasRecordThatPasses(function ($rec) use ($record) {
    +            if ($rec['message'] !== $record['message']) {
    +                return false;
    +            }
    +            if (isset($record['context']) && $rec['context'] !== $record['context']) {
    +                return false;
    +            }
    +            return true;
    +        }, $level);
    +    }
    +
    +    public function hasRecordThatContains($message, $level)
    +    {
    +        return $this->hasRecordThatPasses(function ($rec) use ($message) {
    +            return strpos($rec['message'], $message) !== false;
    +        }, $level);
    +    }
    +
    +    public function hasRecordThatMatches($regex, $level)
    +    {
    +        return $this->hasRecordThatPasses(function ($rec) use ($regex) {
    +            return preg_match($regex, $rec['message']) > 0;
    +        }, $level);
    +    }
    +
    +    public function hasRecordThatPasses(callable $predicate, $level)
    +    {
    +        if (!isset($this->recordsByLevel[$level])) {
    +            return false;
    +        }
    +        foreach ($this->recordsByLevel[$level] as $i => $rec) {
    +            if (call_user_func($predicate, $rec, $i)) {
    +                return true;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    public function __call($method, $args)
    +    {
    +        if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
    +            $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
    +            $level = strtolower($matches[2]);
    +            if (method_exists($this, $genericMethod)) {
    +                $args[] = $level;
    +                return call_user_func_array([$this, $genericMethod], $args);
    +            }
    +        }
    +        throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
    +    }
    +
    +    public function reset()
    +    {
    +        $this->records = [];
    +    }
    +}
    diff --git a/core/vendor/psr/log/README.md b/core/vendor/psr/log/README.md
    new file mode 100644
    index 0000000..5571a25
    --- /dev/null
    +++ b/core/vendor/psr/log/README.md
    @@ -0,0 +1,52 @@
    +PSR Log
    +=======
    +
    +This repository holds all interfaces/classes/traits related to
    +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
    +
    +Note that this is not a logger of its own. It is merely an interface that
    +describes a logger. See the specification for more details.
    +
    +Installation
    +------------
    +
    +```bash
    +composer require psr/log
    +```
    +
    +Usage
    +-----
    +
    +If you need a logger, you can use the interface like this:
    +
    +```php
    +logger = $logger;
    +    }
    +
    +    public function doSomething()
    +    {
    +        if ($this->logger) {
    +            $this->logger->info('Doing work');
    +        }
    +
    +        // do something useful
    +    }
    +}
    +```
    +
    +You can then pick one of the implementations of the interface to get a logger.
    +
    +If you want to implement the interface, you can require this package and
    +implement `Psr\Log\LoggerInterface` in your code. Please read the
    +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
    +for details.
    diff --git a/core/vendor/psr/log/composer.json b/core/vendor/psr/log/composer.json
    new file mode 100644
    index 0000000..87934d7
    --- /dev/null
    +++ b/core/vendor/psr/log/composer.json
    @@ -0,0 +1,26 @@
    +{
    +    "name": "psr/log",
    +    "description": "Common interface for logging libraries",
    +    "keywords": ["psr", "psr-3", "log"],
    +    "homepage": "https://github.com/php-fig/log",
    +    "license": "MIT",
    +    "authors": [
    +        {
    +            "name": "PHP-FIG",
    +            "homepage": "http://www.php-fig.org/"
    +        }
    +    ],
    +    "require": {
    +        "php": ">=5.3.0"
    +    },
    +    "autoload": {
    +        "psr-4": {
    +            "Psr\\Log\\": "Psr/Log/"
    +        }
    +    },
    +    "extra": {
    +        "branch-alias": {
    +            "dev-master": "1.0.x-dev"
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/.editorconfig b/core/vendor/psy/psysh/.editorconfig
    new file mode 100644
    index 0000000..779f99a
    --- /dev/null
    +++ b/core/vendor/psy/psysh/.editorconfig
    @@ -0,0 +1,12 @@
    +root = true
    +
    +[*]
    +indent_style = space
    +indent_size = 4
    +end_of_line = lf
    +charset = utf-8
    +trim_trailing_whitespace = true
    +insert_final_newline = true
    +
    +[*.md]
    +trim_trailing_whitespace = false
    diff --git a/core/vendor/psy/psysh/.gitignore b/core/vendor/psy/psysh/.gitignore
    new file mode 100644
    index 0000000..a7fd20d
    --- /dev/null
    +++ b/core/vendor/psy/psysh/.gitignore
    @@ -0,0 +1,6 @@
    +build-vendor/
    +vendor/
    +composer.lock
    +manual/
    +__pycache__
    +.php_cs.cache
    diff --git a/core/vendor/psy/psysh/.php_cs b/core/vendor/psy/psysh/.php_cs
    new file mode 100644
    index 0000000..cb3e56b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/.php_cs
    @@ -0,0 +1,43 @@
    +level(FixerInterface::SYMFONY_LEVEL)
    +    ->fixers(array(
    +        'align_double_arrow',
    +        'concat_with_spaces',
    +        'header_comment',
    +        'long_array_syntax',
    +        'ordered_use',
    +        'strict',
    +        '-concat_without_spaces',
    +        '-method_argument_space',
    +        '-pre_increment',
    +        '-unalign_double_arrow',
    +        '-unalign_equals',
    +    ))
    +    ->setUsingLinter(false);
    +
    +$finder = $config->getFinder()
    +    ->in(__DIR__)
    +    ->name('.php_cs')
    +    ->name('build-manual')
    +    ->name('build-phar')
    +    ->exclude('build-vendor');
    +
    +return $config;
    diff --git a/core/vendor/psy/psysh/.styleci.yml b/core/vendor/psy/psysh/.styleci.yml
    new file mode 100644
    index 0000000..c6fa146
    --- /dev/null
    +++ b/core/vendor/psy/psysh/.styleci.yml
    @@ -0,0 +1,22 @@
    +preset: symfony
    +
    +enabled:
    +  - align_double_arrow
    +  - concat_with_spaces
    +  - long_array_syntax
    +  - ordered_use
    +  - strict
    +
    +disabled:
    +  - concat_without_spaces
    +  - method_argument_space
    +  - pre_increment
    +  - unalign_double_arrow
    +  - unalign_equals
    +
    +finder:
    +  name:
    +    - "*.php"
    +    - ".php_cs"
    +    - "build-manual"
    +    - "build-phar"
    diff --git a/core/vendor/psy/psysh/.travis.yml b/core/vendor/psy/psysh/.travis.yml
    new file mode 100644
    index 0000000..ca4f5ad
    --- /dev/null
    +++ b/core/vendor/psy/psysh/.travis.yml
    @@ -0,0 +1,24 @@
    +language: php
    +
    +php:
    +  - 5.3
    +  - 5.4
    +  - 5.5
    +  - 5.6
    +  - 7.0
    +  - hhvm
    +
    +install:
    +  - travis_retry composer install --no-interaction --prefer-source
    +  - '[ -z "$MIN_VERSIONS" ] || composer require --no-interaction --prefer-source $MIN_VERSIONS'
    +
    +script:
    +  - vendor/bin/phpunit
    +
    +matrix:
    +  fast_finish: true
    +  include:
    +    - php: 5.3
    +      env: MIN_VERSIONS="symfony/console:2.3.10 symfony/var-dumper:2.7.0 nikic/php-parser:1.2.1 jakub-onderka/php-console-highlighter:0.3.0"
    +  allow_failures:
    +    - env: MIN_VERSIONS="symfony/console:2.3.10 symfony/var-dumper:2.7.0 nikic/php-parser:1.2.1 jakub-onderka/php-console-highlighter:0.3.0"
    diff --git a/core/vendor/psy/psysh/CONTRIBUTING.md b/core/vendor/psy/psysh/CONTRIBUTING.md
    new file mode 100644
    index 0000000..a86e6e9
    --- /dev/null
    +++ b/core/vendor/psy/psysh/CONTRIBUTING.md
    @@ -0,0 +1,18 @@
    +## Code style
    +
    +Please make your code look like the other code in the project. PsySH follows [PSR-1](http://php-fig.org/psr/psr-1/) and [PSR-2](http://php-fig.org/psr/psr-2/). The easiest way to do make sure you're following the coding standard is to run `vendor/bin/php-cs-fixer fix` before committing.
    +
    +## Branching model
    +
    +Please branch off and send pull requests to the `develop` branch.
    +
    +## Building the manual
    +
    +```sh
    +svn co https://svn.php.net/repository/phpdoc/en/trunk/reference/ php_manual
    +bin/build_manual phpdoc_manual ~/.local/share/psysh/php_manual.sqlite
    +```
    +
    +To build the manual for another language, switch out `en` above for `de`, `es`, or any of the other languages listed in the README.
    +
    +[Partial or outdated documentation is available for other languages](http://www.php.net/manual/help-translate.php) but these translations are outdated, so their content may be completely wrong or insecure!
    diff --git a/core/vendor/psy/psysh/LICENSE b/core/vendor/psy/psysh/LICENSE
    new file mode 100644
    index 0000000..0d094e0
    --- /dev/null
    +++ b/core/vendor/psy/psysh/LICENSE
    @@ -0,0 +1,21 @@
    +The MIT License (MIT)
    +
    +Copyright (c) 2012-2015 Justin Hileman
    +
    +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/core/vendor/psy/psysh/README.md b/core/vendor/psy/psysh/README.md
    new file mode 100644
    index 0000000..7394d03
    --- /dev/null
    +++ b/core/vendor/psy/psysh/README.md
    @@ -0,0 +1,179 @@
    +# PsySH
    +
    +[![Package version](https://img.shields.io/packagist/v/psy/psysh.svg?style=flat-square)](https://packagist.org/packages/psy/psysh)
    +[![Monthly downloads](http://img.shields.io/packagist/dm/psy/psysh.svg?style=flat-square)](https://packagist.org/packages/psy/psysh)
    +[![Made out of awesome](https://img.shields.io/badge/made_out_of_awesome-✓-brightgreen.svg?style=flat-square)](http://psysh.org)
    +
    +[![Build status](https://img.shields.io/travis/bobthecow/psysh/master.svg?style=flat-square)](http://travis-ci.org/bobthecow/psysh)
    +[![StyleCI](https://styleci.io/repos/4549925/shield)](https://styleci.io/repos/4549925)
    +
    +
    +## About
    +
    +PsySH is a runtime developer console, interactive debugger and [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) for PHP. Learn more at [psysh.org](http://psysh.org/). Check out the [Interactive Debugging in PHP talk from OSCON](https://presentate.com/bobthecow/talks/php-for-pirates) on Presentate.
    +
    +
    +## Installation
    +
    +Download the `psysh` phar to install:
    +
    +```
    +wget psysh.org/psysh
    +chmod +x psysh
    +./psysh
    +```
    +
    +It's even awesomer if you put it somewhere in your system path (like `/usr/local/bin` or `~/bin`)!
    +
    +PsySH [is available via Composer](https://packagist.org/packages/psy/psysh), so you can use it in your project as well:
    +
    +```
    +composer require psy/psysh:@stable
    +./vendor/bin/psysh
    +```
    +
    +Or you can use by checking out the the repository directly:
    +
    +```
    +git clone https://github.com/bobthecow/psysh.git
    +cd psysh
    +./bin/psysh
    +```
    +
    +
    +## PsySH configuration
    +
    +While PsySH strives to detect the right settings automatically, you might want to configure it yourself. Just add a file to `~/.config/psysh/config.php` (or `C:\Users\{USER}\AppData\Roaming\PsySH\config.php` on Windows):
    +
    +```php
    + 'more',
    +
    +    // Sets the maximum number of entries the history can contain.
    +    // If set to zero, the history size is unlimited.
    +    'historySize' => 0,
    +
    +    // If set to true, the history will not keep duplicate entries.
    +    // Newest entries override oldest.
    +    // This is the equivalent of the HISTCONTROL=erasedups setting in bash.
    +    'eraseDuplicates' => false,
    +
    +    // By default, PsySH will use a 'forking' execution loop if pcntl is
    +    // installed. This is by far the best way to use it, but you can override
    +    // the default by explicitly enabling or disabling this functionality here.
    +    'usePcntl' => false,
    +
    +    // PsySH uses readline if you have it installed, because interactive input
    +    // is pretty awful without it. But you can explicitly disable it if you hate
    +    // yourself or something.
    +    'useReadline' => false,
    +
    +    // PsySH automatically inserts semicolons at the end of input if a statement
    +    // is missing one. To disable this, set `requireSemicolons` to true.
    +    'requireSemicolons' => true,
    +
    +    // PsySH uses a couple of UTF-8 characters in its own output. These can be
    +    // disabled, mostly to work around code page issues. Because Windows.
    +    //
    +    // Note that this does not disable Unicode output in general, it just makes
    +    // it so PsySH won't output any itself.
    +    'useUnicode' => false,
    +
    +    // While PsySH respects the current `error_reporting` level, and doesn't throw
    +    // exceptions for all errors, it does log all errors regardless of level. Set
    +    // `errorLoggingLevel` to 0 to prevent logging non-thrown errors. Set it to any
    +    // valid `error_reporting` value to log only errors which match that level.
    +    'errorLoggingLevel' => E_ALL & ~E_NOTICE,
    +
    +    // "Default includes" will be included once at the beginning of every PsySH
    +    // session. This is a good place to add autoloaders for your favorite
    +    // libraries.
    +    'defaultIncludes' => array(
    +        __DIR__ . '/include/bootstrap.php',
    +    ),
    +
    +    // While PsySH ships with a bunch of great commands, it's possible to add
    +    // your own for even more awesome. Any Psy command added here will be
    +    // available in your Psy shell sessions.
    +    'commands' => array(
    +        // The `parse` command is a command used in the development of PsySH.
    +        // Given a string of PHP code, it pretty-prints the
    +        // [PHP Parser](https://github.com/nikic/PHP-Parser) parse tree. It
    +        // prolly won't be super useful for most of you, but it's there if you
    +        // want to play :)
    +        new \Psy\Command\ParseCommand,
    +    ),
    +
    +    // PsySH uses symfony/var-dumper's casters for presenting scalars, resources,
    +    // arrays and objects. You can enable additional casters, or write your own!
    +    // See http://symfony.com/doc/current/components/var_dumper/advanced.html#casters
    +    'casters' => array(
    +        'MyFooClass' => 'MyFooClassCaster::castMyFooObject',
    +    ),
    +
    +    // You can disable tab completion if you want to. Not sure why you'd want to.
    +    'tabCompletion' => false,
    +
    +    // You can write your own tab completion matchers, too! Here are some that enable
    +    // tab completion for MongoDB database and collection names:
    +    'tabCompletionMatchers' => array(
    +        new \Psy\TabCompletion\Matcher\MongoClientMatcher,
    +        new \Psy\TabCompletion\Matcher\MongoDatabaseMatcher,
    +    ),
    +
    +    // If multiple versions of the same configuration or data file exist, PsySH will
    +    // use the file with highest precedence, and will silently ignore all others. With
    +    // this enabled, a warning will be emitted (but not an exception thrown) if multiple
    +    // configuration or data files are found.
    +    //
    +    // This will default to true in a future release, but is false for now.
    +    'warnOnMultipleConfigs' => true,
    +
    +    // By default, output contains colors if support for them is detected. To override:
    +    'colorMode' => \Psy\Configuration::COLOR_MODE_FORCED,   // force colors in output
    +    'colorMode' => \Psy\Configuration::COLOR_MODE_DISABLED, // disable colors in output
    +);
    +```
    +
    +
    +## Downloading the manual
    +
    +The PsySH `doc` command is great for documenting source code, but you'll need a little something extra for PHP core documentation. Download one of the following PHP Manual files and drop it in `~/.local/share/psysh/`, `/usr/local/share/psysh/` or `C:\Users\{USER}\AppData\Roaming\PsySH\` on Windows:
    +
    + * **[English](http://psysh.org/manual/en/php_manual.sqlite)**
    + * [Brazilian Portuguese](http://psysh.org/manual/pt_BR/php_manual.sqlite)
    + * [Chinese (Simplified)](http://psysh.org/manual/zh/php_manual.sqlite)
    + * [French](http://psysh.org/manual/fr/php_manual.sqlite)
    + * [German](http://psysh.org/manual/de/php_manual.sqlite)
    + * [Italian](http://psysh.org/manual/it/php_manual.sqlite)
    + * [Japanese](http://psysh.org/manual/ja/php_manual.sqlite)
    + * [Polish](http://psysh.org/manual/pl/php_manual.sqlite)
    + * [Romanian](http://psysh.org/manual/ro/php_manual.sqlite)
    + * [Russian](http://psysh.org/manual/ru/php_manual.sqlite)
    + * [Persian](http://psysh.org/manual/fa/php_manual.sqlite)
    + * [Spanish](http://psysh.org/manual/es/php_manual.sqlite)
    + * [Turkish](http://psysh.org/manual/tr/php_manual.sqlite)
    +
    +
    +
    +## As Seen On…
    +
    + * Cake: [`cake console`](http://book.cakephp.org/3.0/en/console-and-shells/repl.html)
    + * Drupal: [`drush php`](http://drushcommands.com/drush-8x/core/core-cli/), [drush-psysh](https://github.com/grota/drush-psysh)
    + * eZ Publish: [`ezsh`](https://github.com/lolautruche/ezsh)
    + * Laravel: [`artisan tinker`](https://github.com/laravel/framework/blob/5.0/src/Illuminate/Foundation/Console/TinkerCommand.php)
    + * Lumen: [`artisan tinker`](https://github.com/vluzrmos/lumen-tinker)
    + * Magento: [`magerun console`](https://github.com/netz98/n98-magerun/blob/develop/src/N98/Magento/Command/Developer/ConsoleCommand.php)
    + * Pantheon CLI: [`terminus cli console`](https://github.com/pantheon-systems/terminus)
    + * Symfony: [sf1-psysh-bootstrap](https://github.com/varas/sf1-psysh-bootstrap)
    + * Symfony2: [`psymf`](https://github.com/navitronic/psymf), [sf2-psysh-bootstrap](https://github.com/varas/sf2-psysh-bootstrap), [symfony-repl](https://github.com/luxifer/symfony-repl), [PsyshBundle](https://github.com/theofidry/PsyshBundle)
    + * WordPress: [`wp-cli shell`](https://github.com/wp-cli/wp-cli/blob/master/php/commands/shell.php)
    + * Zend Framework 2: [PsyshModule](https://zfmodules.com/gianarb/zf2-psysh-module)
    diff --git a/core/vendor/psy/psysh/bin/build b/core/vendor/psy/psysh/bin/build
    new file mode 100644
    index 0000000..3598932
    --- /dev/null
    +++ b/core/vendor/psy/psysh/bin/build
    @@ -0,0 +1,6 @@
    +#!/usr/bin/env bash
    +
    +cd "${BASH_SOURCE%/*}/.."
    +
    +./bin/build-vendor || exit 1
    +./bin/build-phar
    diff --git a/core/vendor/psy/psysh/bin/build-manual b/core/vendor/psy/psysh/bin/build-manual
    new file mode 100644
    index 0000000..fca601d
    --- /dev/null
    +++ b/core/vendor/psy/psysh/bin/build-manual
    @@ -0,0 +1,285 @@
    +#!/usr/bin/env php
    +':
    +                $inTag = false;
    +
    +            default:
    +        }
    +
    +        if ($inTag) {
    +            $tagWidth++;
    +        }
    +
    +        $i++;
    +
    +        if (!$inTag && ($i - $tagWidth > $width)) {
    +            $lastSpace = $lastSpace ?: $width;
    +
    +            $return[] = trim(substr($text, 0, $lastSpace));
    +            $text     = substr($text, $lastSpace);
    +            $len      = strlen($text);
    +
    +            $i = $tagWidth = 0;
    +        }
    +    } while ($i < $len);
    +
    +    $return[] = trim($text);
    +
    +    return implode("\n", $return);
    +}
    +
    +function extract_paragraphs($element)
    +{
    +    $paragraphs = array();
    +    foreach ($element->getElementsByTagName('para') as $p) {
    +        $text = '';
    +        foreach ($p->childNodes as $child) {
    +            // @todo: figure out if there's something we can do with tables.
    +            if ($child instanceof DOMElement && $child->tagName === 'table') {
    +                continue;
    +            }
    +
    +            // skip references, because ugh.
    +            if (preg_match('{^\s*&[a-z][a-z\.]+;\s*$}', $child->textContent)) {
    +                continue;
    +            }
    +
    +            $text .= $child->ownerDocument->saveXML($child);
    +        }
    +
    +        if ($text = trim(preg_replace('{\n[ \t]+}', ' ', $text))) {
    +            $paragraphs[] = $text;
    +        }
    +    }
    +
    +    return implode("\n\n", $paragraphs);
    +}
    +
    +function format_doc($doc)
    +{
    +    $chunks   = array();
    +
    +    if (!empty($doc['description'])) {
    +        $chunks[] = 'Description:';
    +        $chunks[] = indent_text(htmlwrap(thunk_tags($doc['description']), WRAP_WIDTH - 2));
    +        $chunks[] = '';
    +    }
    +
    +    if (!empty($doc['params'])) {
    +        $chunks[] = 'Param:';
    +
    +        $typeMax = max(array_map(function ($param) {
    +            return strlen($param['type']);
    +        }, $doc['params']));
    +
    +        $max = max(array_map(function ($param) {
    +            return strlen($param['name']);
    +        }, $doc['params']));
    +
    +        $template  = '  %-' . $typeMax . 's  %-' . $max . 's  %s';
    +        $indent    = str_repeat(' ', $typeMax + $max + 6);
    +        $wrapWidth = WRAP_WIDTH - strlen($indent);
    +
    +        foreach ($doc['params'] as $param) {
    +            $desc = indent_text(htmlwrap(thunk_tags($param['description']), $wrapWidth), $indent, false);
    +            $chunks[] = sprintf($template, $param['type'], $param['name'], $desc);
    +        }
    +        $chunks[] = '';
    +    }
    +
    +    if (isset($doc['return']) || isset($doc['return_type'])) {
    +        $chunks[] = 'Return:';
    +
    +        $type   = isset($doc['return_type']) ? $doc['return_type'] : 'unknown';
    +        $desc   = isset($doc['return']) ? $doc['return'] : '';
    +
    +        $indent    = str_repeat(' ', strlen($type) + 4);
    +        $wrapWidth = WRAP_WIDTH - strlen($indent);
    +
    +        if (!empty($desc)) {
    +            $desc = indent_text(htmlwrap(thunk_tags($doc['return']), $wrapWidth), $indent, false);
    +        }
    +
    +        $chunks[] = sprintf('  %s  %s', $type, $desc);
    +        $chunks[] = '';
    +    }
    +
    +    array_pop($chunks); // get rid of the trailing newline
    +
    +    return implode("\n", $chunks);
    +}
    +
    +function thunk_tags($text)
    +{
    +    $tagMap = array(
    +        'parameter>' => 'strong>',
    +        'function>'  => 'strong>',
    +        'literal>'   => 'return>',
    +        'type>'      => 'info>',
    +        'constant>'  => 'info>',
    +    );
    +
    +    $andBack = array(
    +        '&'       => '&',
    +        '&true;'  => 'true',
    +        '&false;' => 'false',
    +        '&null;'  => 'null',
    +    );
    +
    +    return strtr(strip_tags(strtr($text, $tagMap), ''), $andBack);
    +}
    +
    +function indent_text($text, $indent = '  ', $leading = true)
    +{
    +    return ($leading ? $indent : '') . str_replace("\n", "\n" . $indent, $text);
    +}
    +
    +function find_type($xml, $paramName)
    +{
    +    foreach ($xml->getElementsByTagName('methodparam') as $param) {
    +        if ($type = $param->getElementsByTagName('type')->item(0)) {
    +            if ($parameter = $param->getElementsByTagName('parameter')->item(0)) {
    +                if ($paramName === $parameter->textContent) {
    +                    return $type->textContent;
    +                }
    +            }
    +        }
    +    }
    +}
    +
    +$docs = array();
    +foreach (glob($argv[1] . '/*/*/*.xml') as $function) {
    +    $funcname = basename($function);
    +    if ($funcname === 'main.xml' || strpos($funcname, 'entities.') === 0) {
    +        continue;
    +    }
    +
    +    $xmlstr = str_replace('&', '&', file_get_contents($function));
    +
    +    $xml = new DOMDocument();
    +    $xml->preserveWhiteSpace = false;
    +
    +    if (!@$xml->loadXml($xmlstr)) {
    +        echo "XML Parse Error: $function\n";
    +        continue;
    +    }
    +
    +    $doc = array();
    +    $refsect1s = $xml->getElementsByTagName('refsect1');
    +    foreach ($refsect1s as $refsect1) {
    +        $role = $refsect1->getAttribute('role');
    +        switch ($role) {
    +            case 'description':
    +                $doc['description'] = extract_paragraphs($refsect1);
    +
    +                if ($synopsis = $refsect1->getElementsByTagName('methodsynopsis')->item(0)) {
    +                    foreach ($synopsis->childNodes as $node) {
    +                        if ($node instanceof DOMElement && $node->tagName === 'type') {
    +                            $doc['return_type'] = $node->textContent;
    +                            break;
    +                        }
    +                    }
    +                }
    +                break;
    +
    +            case 'returnvalues':
    +                // do nothing.
    +                $doc['return'] = extract_paragraphs($refsect1);
    +                break;
    +
    +            case 'parameters':
    +                $params = array();
    +                $vars = $refsect1->getElementsByTagName('varlistentry');
    +                foreach ($vars as $var) {
    +                    if ($name = $var->getElementsByTagName('parameter')->item(0)) {
    +                        $params[] = array(
    +                            'name'        => '$' . $name->textContent,
    +                            'type'        => find_type($xml, $name->textContent),
    +                            'description' => extract_paragraphs($var),
    +                        );
    +                    }
    +                }
    +
    +                $doc['params'] = $params;
    +                break;
    +        }
    +    }
    +
    +    // and the purpose
    +    if ($purpose = $xml->getElementsByTagName('refpurpose')->item(0)) {
    +        $desc = htmlwrap($purpose->textContent);
    +        if (isset($doc['description'])) {
    +            $desc .= "\n\n" . $doc['description'];
    +        }
    +
    +        $doc['description'] = trim($desc);
    +    }
    +
    +    $formatted = format_doc($doc);
    +    foreach ($xml->getElementsByTagName('refname') as $ref) {
    +        $docs[$ref->textContent] = $formatted;
    +    }
    +}
    +
    +if (is_file($argv[2])) {
    +    unlink($argv[2]);
    +}
    +
    +$db = new PDO('sqlite:' . $argv[2]);
    +
    +$db->query('CREATE TABLE php_manual (id char(256) PRIMARY KEY, doc TEXT)');
    +$cmd = $db->prepare('INSERT INTO php_manual (id, doc) VALUES (?, ?)');
    +foreach ($docs as $id => $doc) {
    +    $cmd->execute(array($id, $doc));
    +}
    diff --git a/core/vendor/psy/psysh/bin/build-phar b/core/vendor/psy/psysh/bin/build-phar
    new file mode 100644
    index 0000000..accdee7
    --- /dev/null
    +++ b/core/vendor/psy/psysh/bin/build-phar
    @@ -0,0 +1,33 @@
    +#!/usr/bin/env php
    +compile();
    diff --git a/core/vendor/psy/psysh/bin/build-vendor b/core/vendor/psy/psysh/bin/build-vendor
    new file mode 100644
    index 0000000..e2375b0
    --- /dev/null
    +++ b/core/vendor/psy/psysh/bin/build-vendor
    @@ -0,0 +1,7 @@
    +#!/usr/bin/env bash
    +
    +cd "${BASH_SOURCE%/*}/.."
    +
    +rm -rf build-vendor
    +
    +COMPOSER_VENDOR_DIR=build-vendor composer install --ignore-platform-reqs --no-dev --no-progress --classmap-authoritative
    diff --git a/core/vendor/psy/psysh/bin/psysh b/core/vendor/psy/psysh/bin/psysh
    new file mode 100644
    index 0000000..4fbd223
    --- /dev/null
    +++ b/core/vendor/psy/psysh/bin/psysh
    @@ -0,0 +1,135 @@
    +#!/usr/bin/env php
    + $arg) {
    +        if ($arg === '--cwd') {
    +            if ($i >= count($argv) - 1) {
    +                echo 'Missing --cwd argument.' . PHP_EOL;
    +                exit(1);
    +            }
    +            $cwd = $argv[$i + 1];
    +            break;
    +        }
    +
    +        if (preg_match('/^--cwd=/', $arg)) {
    +            $cwd = substr($arg, 6);
    +            break;
    +        }
    +    }
    +
    +    // Or fall back to the actual cwd
    +    if (!isset($cwd)) {
    +        $cwd = getcwd();
    +    }
    +
    +    $cwd = str_replace('\\', '/', $cwd);
    +
    +    $chunks = explode('/', $cwd);
    +    while (!empty($chunks)) {
    +        $path = implode('/', $chunks);
    +
    +        // Find composer.json
    +        if (is_file($path . '/composer.json')) {
    +            if ($cfg = json_decode(file_get_contents($path . '/composer.json'), true)) {
    +                if (isset($cfg['name']) && $cfg['name'] === 'psy/psysh') {
    +                    // We're inside the psysh project. Let's use the local
    +                    // Composer autoload.
    +                    if (is_file($path . '/vendor/autoload.php')) {
    +                        require $path . '/vendor/autoload.php';
    +                    }
    +
    +                    return;
    +                }
    +            }
    +        }
    +
    +        // Or a composer.lock
    +        if (is_file($path . '/composer.lock')) {
    +            if ($cfg = json_decode(file_get_contents($path . '/composer.lock'), true)) {
    +                foreach (array_merge($cfg['packages'], $cfg['packages-dev']) as $pkg) {
    +                    if (isset($pkg['name']) && $pkg['name'] === 'psy/psysh') {
    +                        // We're inside a project which requires psysh. We'll
    +                        // use the local Composer autoload.
    +                        if (is_file($path . '/vendor/autoload.php')) {
    +                            require $path . '/vendor/autoload.php';
    +                        }
    +
    +                        return;
    +                    }
    +                }
    +            }
    +        }
    +
    +        array_pop($chunks);
    +    }
    +});
    +
    +// We didn't find an autoloader for a local version, so use the autoloader that
    +// came with this script.
    +if (!class_exists('Psy\Shell')) {
    +/* <<< */
    +    if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    +        require __DIR__ . '/../vendor/autoload.php';
    +    } elseif (is_file(__DIR__ . '/../../../autoload.php')) {
    +        require __DIR__ . '/../../../autoload.php';
    +    } else {
    +        echo 'PsySH dependencies not found, be sure to run `composer install`.' . PHP_EOL;
    +        echo 'See https://getcomposer.org to get Composer.' . PHP_EOL;
    +        exit(1);
    +    }
    +/* >>> */
    +}
    +
    +// If the psysh binary was included directly, assume they just wanted an
    +// autoloader and bail early.
    +if (version_compare(PHP_VERSION, '5.3.6', '<')) {
    +    $trace = debug_backtrace();
    +} elseif (version_compare(PHP_VERSION, '5.4.0', '<')) {
    +    $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
    +} else {
    +    $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
    +}
    +
    +if (Psy\Shell::isIncluded($trace)) {
    +    unset($trace);
    +
    +    return;
    +}
    +
    +// Clean up after ourselves.
    +unset($trace);
    +
    +// If the local version is too old, we can't do this
    +if (!function_exists('Psy\bin')) {
    +    $argv = $_SERVER['argv'];
    +    $first = array_shift($argv);
    +    if (preg_match('/php(\.exe)?$/', $first)) {
    +        array_shift($argv);
    +    }
    +    array_unshift($argv, 'vendor/bin/psysh');
    +
    +    echo 'A local PsySH dependency was found, but it cannot be loaded. Please update to' . PHP_EOL;
    +    echo 'the latest version, or run the local copy directly, e.g.:' . PHP_EOL;
    +    echo PHP_EOL;
    +    echo '    ' . implode(' ', $argv) . PHP_EOL;
    +    exit(1);
    +}
    +
    +// And go!
    +call_user_func(Psy\bin());
    diff --git a/core/vendor/psy/psysh/composer.json b/core/vendor/psy/psysh/composer.json
    new file mode 100644
    index 0000000..fd3b477
    --- /dev/null
    +++ b/core/vendor/psy/psysh/composer.json
    @@ -0,0 +1,52 @@
    +{
    +    "name": "psy/psysh",
    +    "description": "An interactive shell for modern PHP.",
    +    "type": "library",
    +    "keywords": ["console", "interactive", "shell", "repl"],
    +    "homepage": "http://psysh.org",
    +    "license": "MIT",
    +    "authors": [
    +        {
    +            "name": "Justin Hileman",
    +            "email": "justin@justinhileman.info",
    +            "homepage": "http://justinhileman.com"
    +        }
    +    ],
    +    "require": {
    +        "php": ">=5.3.9",
    +        "symfony/console": "~2.3.10|^2.4.2|~3.0",
    +        "symfony/var-dumper": "~2.7|~3.0",
    +        "nikic/php-parser": "^1.2.1|~2.0",
    +        "dnoegel/php-xdg-base-dir": "0.1",
    +        "jakub-onderka/php-console-highlighter": "0.3.*"
    +    },
    +    "require-dev": {
    +        "phpunit/phpunit": "~3.7|~4.0|~5.0",
    +        "symfony/finder": "~2.1|~3.0",
    +        "squizlabs/php_codesniffer": "~2.0",
    +        "fabpot/php-cs-fixer": "~1.5"
    +    },
    +    "suggest": {
    +        "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
    +        "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.",
    +        "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.",
    +        "ext-pdo-sqlite": "The doc command requires SQLite to work."
    +    },
    +    "autoload": {
    +        "files": ["src/Psy/functions.php"],
    +        "psr-4": {
    +            "Psy\\": "src/Psy/"
    +        }
    +    },
    +    "autoload-dev": {
    +        "psr-4": {
    +            "Psy\\Test\\": "test/Psy/Test/"
    +        }
    +    },
    +    "bin": ["bin/psysh"],
    +    "extra": {
    +        "branch-alias": {
    +            "dev-develop": "0.8.x-dev"
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/phpcs.xml b/core/vendor/psy/psysh/phpcs.xml
    new file mode 100644
    index 0000000..2815b96
    --- /dev/null
    +++ b/core/vendor/psy/psysh/phpcs.xml
    @@ -0,0 +1,11 @@
    +
    +
    +  The coding standard for PsySH
    +
    +  */bin/*
    +  */vendor/*
    +
    +  
    +    
    +  
    +
    diff --git a/core/vendor/psy/psysh/phpunit.xml.dist b/core/vendor/psy/psysh/phpunit.xml.dist
    new file mode 100644
    index 0000000..78b2b33
    --- /dev/null
    +++ b/core/vendor/psy/psysh/phpunit.xml.dist
    @@ -0,0 +1,12 @@
    +
    +
    +	
    +		./test
    +	
    +
    +	
    +		
    +			./src/Psy
    +		
    +	
    +
    diff --git a/core/vendor/psy/psysh/src/Psy/Autoloader.php b/core/vendor/psy/psysh/src/Psy/Autoloader.php
    new file mode 100644
    index 0000000..db09ded
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Autoloader.php
    @@ -0,0 +1,45 @@
    +createParser();
    +        }
    +
    +        $this->parser    = $parser;
    +        $this->printer   = $printer   ?: new Printer();
    +        $this->traverser = $traverser ?: new NodeTraverser();
    +
    +        foreach ($this->getDefaultPasses() as $pass) {
    +            $this->traverser->addVisitor($pass);
    +        }
    +    }
    +
    +    /**
    +     * Get default CodeCleaner passes.
    +     *
    +     * @return array
    +     */
    +    private function getDefaultPasses()
    +    {
    +        return array(
    +            new AbstractClassPass(),
    +            new AssignThisVariablePass(),
    +            new FunctionReturnInWriteContextPass(),
    +            new CallTimePassByReferencePass(),
    +            new CalledClassPass(),
    +            new InstanceOfPass(),
    +            new LeavePsyshAlonePass(),
    +            new LegacyEmptyPass(),
    +            new ImplicitReturnPass(),
    +            new UseStatementPass(),      // must run before namespace and validation passes
    +            new NamespacePass($this),    // must run after the implicit return pass
    +            new StrictTypesPass(),
    +            new StaticConstructorPass(),
    +            new ValidFunctionNamePass(),
    +            new ValidClassNamePass(),
    +            new ValidConstantPass(),
    +            new MagicConstantsPass(),
    +            new ExitPass(),
    +        );
    +    }
    +
    +    /**
    +     * Clean the given array of code.
    +     *
    +     * @throws ParseErrorException if the code is invalid PHP, and cannot be coerced into valid PHP.
    +     *
    +     * @param array $codeLines
    +     * @param bool  $requireSemicolons
    +     *
    +     * @return string|false Cleaned PHP code, False if the input is incomplete.
    +     */
    +    public function clean(array $codeLines, $requireSemicolons = false)
    +    {
    +        $stmts = $this->parse('traverser->traverse($stmts);
    +
    +        return $this->printer->prettyPrint($stmts);
    +    }
    +
    +    /**
    +     * Set the current local namespace.
    +     *
    +     * @param null|array $namespace (default: null)
    +     *
    +     * @return null|array
    +     */
    +    public function setNamespace(array $namespace = null)
    +    {
    +        $this->namespace = $namespace;
    +    }
    +
    +    /**
    +     * Get the current local namespace.
    +     *
    +     * @return null|array
    +     */
    +    public function getNamespace()
    +    {
    +        return $this->namespace;
    +    }
    +
    +    /**
    +     * Lex and parse a block of code.
    +     *
    +     * @see Parser::parse
    +     *
    +     * @param string $code
    +     * @param bool   $requireSemicolons
    +     *
    +     * @return array A set of statements
    +     */
    +    protected function parse($code, $requireSemicolons = false)
    +    {
    +        try {
    +            return $this->parser->parse($code);
    +        } catch (\PhpParser\Error $e) {
    +            if ($this->parseErrorIsUnclosedString($e, $code)) {
    +                return false;
    +            }
    +
    +            if (!$this->parseErrorIsEOF($e)) {
    +                throw ParseErrorException::fromParseError($e);
    +            }
    +
    +            if ($requireSemicolons) {
    +                return false;
    +            }
    +
    +            try {
    +                // Unexpected EOF, try again with an implicit semicolon
    +                return $this->parser->parse($code . ';');
    +            } catch (\PhpParser\Error $e) {
    +                return false;
    +            }
    +        }
    +    }
    +
    +    private function parseErrorIsEOF(\PhpParser\Error $e)
    +    {
    +        $msg = $e->getRawMessage();
    +
    +        return ($msg === 'Unexpected token EOF') || (strpos($msg, 'Syntax error, unexpected EOF') !== false);
    +    }
    +
    +    /**
    +     * A special test for unclosed single-quoted strings.
    +     *
    +     * Unlike (all?) other unclosed statements, single quoted strings have
    +     * their own special beautiful snowflake syntax error just for
    +     * themselves.
    +     *
    +     * @param \PhpParser\Error $e
    +     * @param string           $code
    +     *
    +     * @return bool
    +     */
    +    private function parseErrorIsUnclosedString(\PhpParser\Error $e, $code)
    +    {
    +        if ($e->getRawMessage() !== 'Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE') {
    +            return false;
    +        }
    +
    +        try {
    +            $this->parser->parse($code . "';");
    +        } catch (\Exception $e) {
    +            return false;
    +        }
    +
    +        return true;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/AbstractClassPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/AbstractClassPass.php
    new file mode 100644
    index 0000000..e0ec08c
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/AbstractClassPass.php
    @@ -0,0 +1,69 @@
    +class = $node;
    +            $this->abstractMethods = array();
    +        } elseif ($node instanceof ClassMethod) {
    +            if ($node->isAbstract()) {
    +                $name = sprintf('%s::%s', $this->class->name, $node->name);
    +                $this->abstractMethods[] = $name;
    +
    +                if ($node->stmts !== null) {
    +                    throw new FatalErrorException(sprintf('Abstract function %s cannot contain body', $name));
    +                }
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @throws RuntimeException if the node is a non-abstract class with abstract methods.
    +     *
    +     * @param Node $node
    +     */
    +    public function leaveNode(Node $node)
    +    {
    +        if ($node instanceof ClassStmt) {
    +            $count = count($this->abstractMethods);
    +            if ($count > 0 && !$node->isAbstract()) {
    +                throw new FatalErrorException(sprintf(
    +                    'Class %s contains %d abstract method%s must therefore be declared abstract or implement the remaining methods (%s)',
    +                    $node->name,
    +                    $count,
    +                    ($count === 0) ? '' : 's',
    +                    implode(', ', $this->abstractMethods)
    +                ));
    +            }
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/AssignThisVariablePass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/AssignThisVariablePass.php
    new file mode 100644
    index 0000000..97f7b4e
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/AssignThisVariablePass.php
    @@ -0,0 +1,39 @@
    +
    + */
    +class AssignThisVariablePass extends CodeCleanerPass
    +{
    +    /**
    +     * Validate that the user input does not assign the `$this` variable.
    +     *
    +     * @throws RuntimeException if the user assign the `$this` variable.
    +     *
    +     * @param Node $node
    +     */
    +    public function enterNode(Node $node)
    +    {
    +        if ($node instanceof Assign && $node->var instanceof Variable && $node->var->name === 'this') {
    +            throw new FatalErrorException('Cannot re-assign $this');
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/CallTimePassByReferencePass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/CallTimePassByReferencePass.php
    new file mode 100644
    index 0000000..608ba1e
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/CallTimePassByReferencePass.php
    @@ -0,0 +1,52 @@
    +
    + */
    +class CallTimePassByReferencePass extends CodeCleanerPass
    +{
    +    /**
    +     * Validate of use call-time pass-by-reference.
    +     *
    +     * @throws RuntimeException if the user used call-time pass-by-reference in PHP >= 5.4.0
    +     *
    +     * @param Node $node
    +     */
    +    public function enterNode(Node $node)
    +    {
    +        if (version_compare(PHP_VERSION, '5.4', '<')) {
    +            return;
    +        }
    +
    +        if (!$node instanceof FunctionCall && !$node instanceof MethodCall && !$node instanceof StaticCall) {
    +            return;
    +        }
    +
    +        foreach ($node->args as $arg) {
    +            if ($arg->byRef) {
    +                throw new FatalErrorException('Call-time pass-by-reference has been removed');
    +            }
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/CalledClassPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/CalledClassPass.php
    new file mode 100644
    index 0000000..4a0b065
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/CalledClassPass.php
    @@ -0,0 +1,83 @@
    +inClass = false;
    +    }
    +
    +    /**
    +     * @throws ErrorException if get_class or get_called_class is called without an object from outside a class
    +     *
    +     * @param Node $node
    +     */
    +    public function enterNode(Node $node)
    +    {
    +        if ($node instanceof ClassStmt || $node instanceof TraitStmt) {
    +            $this->inClass = true;
    +        } elseif ($node instanceof FuncCall && !$this->inClass) {
    +            // We'll give any args at all (besides null) a pass.
    +            // Technically we should be checking whether the args are objects, but this will do for now.
    +            //
    +            // TODO: switch this to actually validate args when we get context-aware code cleaner passes.
    +            if (!empty($node->args) && !$this->isNull($node->args[0])) {
    +                return;
    +            }
    +
    +            // We'll ignore name expressions as well (things like `$foo()`)
    +            if (!($node->name instanceof Name)) {
    +                return;
    +            }
    +
    +            $name = strtolower($node->name);
    +            if (in_array($name, array('get_class', 'get_called_class'))) {
    +                $msg = sprintf('%s() called without object from outside a class', $name);
    +                throw new ErrorException($msg, 0, E_USER_WARNING, null, $node->getLine());
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @param Node $node
    +     */
    +    public function leaveNode(Node $node)
    +    {
    +        if ($node instanceof ClassStmt) {
    +            $this->inClass = false;
    +        }
    +    }
    +
    +    private function isNull(Node $node)
    +    {
    +        return $node->value instanceof ConstFetch && strtolower($node->value->name) === 'null';
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/CodeCleanerPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/CodeCleanerPass.php
    new file mode 100644
    index 0000000..e503593
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/CodeCleanerPass.php
    @@ -0,0 +1,22 @@
    +
    + */
    +class FunctionReturnInWriteContextPass extends CodeCleanerPass
    +{
    +    const EXCEPTION_MESSAGE = "Can't use function return value in write context";
    +
    +    private $isPhp55;
    +
    +    public function __construct()
    +    {
    +        $this->isPhp55 = version_compare(PHP_VERSION, '5.5', '>=');
    +    }
    +
    +    /**
    +     * Validate that the functions are used correctly.
    +     *
    +     * @throws FatalErrorException if a function is passed as an argument reference
    +     * @throws FatalErrorException if a function is used as an argument in the isset
    +     * @throws FatalErrorException if a function is used as an argument in the empty, only for PHP < 5.5
    +     * @throws FatalErrorException if a value is assigned to a function
    +     *
    +     * @param Node $node
    +     */
    +    public function enterNode(Node $node)
    +    {
    +        if ($node instanceof ArrayNode || $this->isCallNode($node)) {
    +            $items = $node instanceof ArrayNode ? $node->items : $node->args;
    +            foreach ($items as $item) {
    +                if ($item->byRef && $this->isCallNode($item->value)) {
    +                    throw new FatalErrorException(self::EXCEPTION_MESSAGE);
    +                }
    +            }
    +        } elseif ($node instanceof IssetNode) {
    +            foreach ($node->vars as $var) {
    +                if (!$this->isCallNode($var)) {
    +                    continue;
    +                }
    +
    +                if ($this->isPhp55) {
    +                    throw new FatalErrorException('Cannot use isset() on the result of a function call (you can use "null !== func()" instead)');
    +                } else {
    +                    throw new FatalErrorException(self::EXCEPTION_MESSAGE);
    +                }
    +            }
    +        } elseif ($node instanceof EmptyNode && !$this->isPhp55 && $this->isCallNode($node->expr)) {
    +            throw new FatalErrorException(self::EXCEPTION_MESSAGE);
    +        } elseif ($node instanceof AssignNode && $this->isCallNode($node->var)) {
    +            throw new FatalErrorException(self::EXCEPTION_MESSAGE);
    +        }
    +    }
    +
    +    private function isCallNode(Node $node)
    +    {
    +        return $node instanceof FunctionCall || $node instanceof MethodCall || $node instanceof StaticCall;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/ImplicitReturnPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/ImplicitReturnPass.php
    new file mode 100644
    index 0000000..b425b9d
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/ImplicitReturnPass.php
    @@ -0,0 +1,39 @@
    + $last->getLine(),
    +                'endLine'   => $last->getLine(),
    +            ));
    +        }
    +
    +        return $nodes;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/InstanceOfPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/InstanceOfPass.php
    new file mode 100644
    index 0000000..a5f37c0
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/InstanceOfPass.php
    @@ -0,0 +1,45 @@
    +
    + */
    +class InstanceOfPass extends CodeCleanerPass
    +{
    +    /**
    +     * Validate that the instanceof statement does not receive a scalar value or a non-class constant.
    +     *
    +     * @throws FatalErrorException if a scalar or a non-class constant is given
    +     *
    +     * @param Node $node
    +     */
    +    public function enterNode(Node $node)
    +    {
    +        if (!$node instanceof InstanceofStmt) {
    +            return;
    +        }
    +
    +        if (($node->expr instanceof Scalar && !$node->expr instanceof Encapsed) || $node->expr instanceof ConstFetch) {
    +            throw new FatalErrorException('instanceof expects an object instance, constant given');
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/LeavePsyshAlonePass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/LeavePsyshAlonePass.php
    new file mode 100644
    index 0000000..f77eae0
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/LeavePsyshAlonePass.php
    @@ -0,0 +1,36 @@
    +name === '__psysh__') {
    +            throw new RuntimeException('Don\'t mess with $__psysh__. Bad things will happen.');
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/LegacyEmptyPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/LegacyEmptyPass.php
    new file mode 100644
    index 0000000..f89fbc7
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/LegacyEmptyPass.php
    @@ -0,0 +1,64 @@
    +=')) {
    +            return;
    +        }
    +
    +        if (!$node instanceof ExprEmpty) {
    +            return;
    +        }
    +
    +        if (!$node->expr instanceof Variable) {
    +            $msg = sprintf('syntax error, unexpected %s', $this->getUnexpectedThing($node->expr));
    +
    +            throw new ParseErrorException($msg, $node->expr->getLine());
    +        }
    +    }
    +
    +    private function getUnexpectedThing(Node $node)
    +    {
    +        switch ($node->getType()) {
    +            case 'Scalar_String':
    +            case 'Scalar_LNumber':
    +            case 'Scalar_DNumber':
    +                return json_encode($node->value);
    +
    +            case 'Expr_ConstFetch':
    +                return (string) $node->name;
    +
    +            default:
    +                return $node->getType();
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/MagicConstantsPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/MagicConstantsPass.php
    new file mode 100644
    index 0000000..bee2847
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/MagicConstantsPass.php
    @@ -0,0 +1,42 @@
    +getAttributes());
    +        } elseif ($node instanceof File) {
    +            return new StringNode('', $node->getAttributes());
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/NamespaceAwarePass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/NamespaceAwarePass.php
    new file mode 100644
    index 0000000..554e6e1
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/NamespaceAwarePass.php
    @@ -0,0 +1,71 @@
    +namespace    = array();
    +        $this->currentScope = array();
    +    }
    +
    +    /**
    +     * TODO: should this be final? Extending classes should be sure to either use
    +     * leaveNode or call parent::enterNode() when overloading.
    +     *
    +     * @param Node $node
    +     */
    +    public function enterNode(Node $node)
    +    {
    +        if ($node instanceof NamespaceStmt) {
    +            $this->namespace = isset($node->name) ? $node->name->parts : array();
    +        }
    +    }
    +
    +    /**
    +     * Get a fully-qualified name (class, function, interface, etc).
    +     *
    +     * @param mixed $name
    +     *
    +     * @return string
    +     */
    +    protected function getFullyQualifiedName($name)
    +    {
    +        if ($name instanceof FullyQualifiedName) {
    +            return implode('\\', $name->parts);
    +        } elseif ($name instanceof Name) {
    +            $name = $name->parts;
    +        } elseif (!is_array($name)) {
    +            $name = array($name);
    +        }
    +
    +        return implode('\\', array_merge($this->namespace, $name));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/NamespacePass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/NamespacePass.php
    new file mode 100644
    index 0000000..137358a
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/NamespacePass.php
    @@ -0,0 +1,79 @@
    +cleaner = $cleaner;
    +    }
    +
    +    /**
    +     * If this is a standalone namespace line, remember it for later.
    +     *
    +     * Otherwise, apply remembered namespaces to the code until a new namespace
    +     * is encountered.
    +     *
    +     * @param array $nodes
    +     */
    +    public function beforeTraverse(array $nodes)
    +    {
    +        $first = reset($nodes);
    +        if (count($nodes) === 1 && $first instanceof NamespaceStmt && empty($first->stmts)) {
    +            $this->setNamespace($first->name);
    +        } else {
    +            foreach ($nodes as $key => $node) {
    +                if ($node instanceof NamespaceStmt) {
    +                    $this->setNamespace(null);
    +                } elseif ($this->namespace !== null) {
    +                    $nodes[$key] = new NamespaceStmt($this->namespace, array($node));
    +                }
    +            }
    +        }
    +
    +        return $nodes;
    +    }
    +
    +    /**
    +     * Remember the namespace and (re)set the namespace on the CodeCleaner as
    +     * well.
    +     *
    +     * @param null|Name $namespace
    +     */
    +    private function setNamespace($namespace)
    +    {
    +        $this->namespace = $namespace;
    +        $this->cleaner->setNamespace($namespace === null ? null : $namespace->parts);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/StaticConstructorPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/StaticConstructorPass.php
    new file mode 100644
    index 0000000..c5854ac
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/StaticConstructorPass.php
    @@ -0,0 +1,87 @@
    +
    + */
    +class StaticConstructorPass extends CodeCleanerPass
    +{
    +    private $isPHP533;
    +    private $namespace;
    +
    +    public function __construct()
    +    {
    +        $this->isPHP533 = version_compare(PHP_VERSION, '5.3.3', '>=');
    +    }
    +
    +    public function beforeTraverse(array $nodes)
    +    {
    +        $this->namespace = array();
    +    }
    +
    +    /**
    +     * Validate that the old-style constructor function is not static.
    +     *
    +     * @throws FatalErrorException if the old-style constructor function is static.
    +     *
    +     * @param Node $node
    +     */
    +    public function enterNode(Node $node)
    +    {
    +        if ($node instanceof NamespaceStmt) {
    +            $this->namespace = isset($node->name) ? $node->name->parts : array();
    +        } elseif ($node instanceof ClassStmt) {
    +            // Bail early if this is PHP 5.3.3 and we have a namespaced class
    +            if (!empty($this->namespace) && $this->isPHP533) {
    +                return;
    +            }
    +
    +            $constructor = null;
    +            foreach ($node->stmts as $stmt) {
    +                if ($stmt instanceof ClassMethod) {
    +                    // Bail early if we find a new-style constructor
    +                    if ('__construct' === strtolower($stmt->name)) {
    +                        return;
    +                    }
    +
    +                    // We found a possible old-style constructor
    +                    // (unless there is also a __construct method)
    +                    if (strtolower($node->name) === strtolower($stmt->name)) {
    +                        $constructor = $stmt;
    +                    }
    +                }
    +            }
    +
    +            if ($constructor && $constructor->isStatic()) {
    +                throw new FatalErrorException(sprintf(
    +                    'Constructor %s::%s() cannot be static',
    +                    implode('\\', array_merge($this->namespace, (array) $node->name)),
    +                    $constructor->name
    +                ));
    +            }
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/StrictTypesPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/StrictTypesPass.php
    new file mode 100644
    index 0000000..a33a460
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/StrictTypesPass.php
    @@ -0,0 +1,76 @@
    +strictTypes;
    +
    +        foreach ($nodes as $key => $node) {
    +            if ($node instanceof DeclareStmt) {
    +                foreach ($node->declares as $declare) {
    +                    if ($declare->key === 'strict_types') {
    +                        $value = $declare->value;
    +                        if (!$value instanceof LNumber || ($value->value !== 0 && $value->value !== 1)) {
    +                            throw new FatalErrorException('strict_types declaration must have 0 or 1 as its value');
    +                        }
    +
    +                        $this->strictTypes = $value->value === 1;
    +                    }
    +                }
    +            }
    +        }
    +
    +        if ($prependStrictTypes) {
    +            $first = reset($nodes);
    +            if (!$first instanceof DeclareStmt) {
    +                $declare = new DeclareStmt(array(new DeclareDeclare('strict_types', new LNumber(1))));
    +                array_unshift($nodes, $declare);
    +            }
    +        }
    +
    +        return $nodes;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/UseStatementPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/UseStatementPass.php
    new file mode 100644
    index 0000000..ee96a1c
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/UseStatementPass.php
    @@ -0,0 +1,111 @@
    +name) === strtolower($this->lastNamespace)) {
    +                $this->aliases = $this->lastAliases;
    +            }
    +        }
    +    }
    +
    +    /**
    +     * If this statement is a namespace, forget all the aliases we had.
    +     *
    +     * If it's a use statement, remember the alias for later. Otherwise, apply
    +     * remembered aliases to the code.
    +     *
    +     * @param Node $node
    +     */
    +    public function leaveNode(Node $node)
    +    {
    +        if ($node instanceof UseStmt) {
    +            // Store a reference to every "use" statement, because we'll need
    +            // them in a bit.
    +            foreach ($node->uses as $use) {
    +                $this->aliases[strtolower($use->alias)] = $use->name;
    +            }
    +
    +            return false;
    +        } elseif ($node instanceof NamespaceStmt) {
    +            // Start fresh, since we're done with this namespace.
    +            $this->lastNamespace = $node->name;
    +            $this->lastAliases   = $this->aliases;
    +            $this->aliases       = array();
    +        } else {
    +            foreach ($node as $name => $subNode) {
    +                if ($subNode instanceof Name) {
    +                    // Implicitly thunk all aliases.
    +                    if ($replacement = $this->findAlias($subNode)) {
    +                        $node->$name = $replacement;
    +                    }
    +                }
    +            }
    +
    +            return $node;
    +        }
    +    }
    +
    +    /**
    +     * Find class/namespace aliases.
    +     *
    +     * @param Name $name
    +     *
    +     * @return FullyQualifiedName|null
    +     */
    +    private function findAlias(Name $name)
    +    {
    +        $that = strtolower($name);
    +        foreach ($this->aliases as $alias => $prefix) {
    +            if ($that === $alias) {
    +                return new FullyQualifiedName($prefix->toString());
    +            } elseif (substr($that, 0, strlen($alias) + 1) === $alias . '\\') {
    +                return new FullyQualifiedName($prefix->toString() . substr($name, strlen($alias)));
    +            }
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidClassNamePass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidClassNamePass.php
    new file mode 100644
    index 0000000..843c2de
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidClassNamePass.php
    @@ -0,0 +1,365 @@
    +checkTraits = function_exists('trait_exists');
    +    }
    +
    +    /**
    +     * Validate class, interface and trait definitions.
    +     *
    +     * Validate them upon entering the node, so that we know about their
    +     * presence and can validate constant fetches and static calls in class or
    +     * trait methods.
    +     *
    +     * @param Node
    +     */
    +    public function enterNode(Node $node)
    +    {
    +        parent::enterNode($node);
    +
    +        if ($node instanceof ClassStmt) {
    +            $this->validateClassStatement($node);
    +        } elseif ($node instanceof InterfaceStmt) {
    +            $this->validateInterfaceStatement($node);
    +        } elseif ($node instanceof TraitStmt) {
    +            $this->validateTraitStatement($node);
    +        }
    +    }
    +
    +    /**
    +     * Validate `new` expressions, class constant fetches, and static calls.
    +     *
    +     * @throws FatalErrorException if a class, interface or trait is referenced which does not exist.
    +     * @throws FatalErrorException if a class extends something that is not a class.
    +     * @throws FatalErrorException if a class implements something that is not an interface.
    +     * @throws FatalErrorException if an interface extends something that is not an interface.
    +     * @throws FatalErrorException if a class, interface or trait redefines an existing class, interface or trait name.
    +     *
    +     * @param Node $node
    +     */
    +    public function leaveNode(Node $node)
    +    {
    +        if ($node instanceof NewExpr) {
    +            $this->validateNewExpression($node);
    +        } elseif ($node instanceof ClassConstFetch) {
    +            $this->validateClassConstFetchExpression($node);
    +        } elseif ($node instanceof StaticCall) {
    +            $this->validateStaticCallExpression($node);
    +        }
    +    }
    +
    +    /**
    +     * Validate a class definition statement.
    +     *
    +     * @param ClassStmt $stmt
    +     */
    +    protected function validateClassStatement(ClassStmt $stmt)
    +    {
    +        $this->ensureCanDefine($stmt);
    +        if (isset($stmt->extends)) {
    +            $this->ensureClassExists($this->getFullyQualifiedName($stmt->extends), $stmt);
    +        }
    +        $this->ensureInterfacesExist($stmt->implements, $stmt);
    +    }
    +
    +    /**
    +     * Validate an interface definition statement.
    +     *
    +     * @param InterfaceStmt $stmt
    +     */
    +    protected function validateInterfaceStatement(InterfaceStmt $stmt)
    +    {
    +        $this->ensureCanDefine($stmt);
    +        $this->ensureInterfacesExist($stmt->extends, $stmt);
    +    }
    +
    +    /**
    +     * Validate a trait definition statement.
    +     *
    +     * @param TraitStmt $stmt
    +     */
    +    protected function validateTraitStatement(TraitStmt $stmt)
    +    {
    +        $this->ensureCanDefine($stmt);
    +    }
    +
    +    /**
    +     * Validate a `new` expression.
    +     *
    +     * @param NewExpr $stmt
    +     */
    +    protected function validateNewExpression(NewExpr $stmt)
    +    {
    +        // if class name is an expression or an anonymous class, give it a pass for now
    +        if (!$stmt->class instanceof Expr && !$stmt->class instanceof ClassStmt) {
    +            $this->ensureClassExists($this->getFullyQualifiedName($stmt->class), $stmt);
    +        }
    +    }
    +
    +    /**
    +     * Validate a class constant fetch expression's class.
    +     *
    +     * @param ClassConstFetch $stmt
    +     */
    +    protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
    +    {
    +        // there is no need to check exists for ::class const for php 5.5 or newer
    +        if (strtolower($stmt->name) === 'class'
    +            && version_compare(PHP_VERSION, '5.5', '>=')) {
    +            return;
    +        }
    +
    +        // if class name is an expression, give it a pass for now
    +        if (!$stmt->class instanceof Expr) {
    +            $this->ensureClassOrInterfaceExists($this->getFullyQualifiedName($stmt->class), $stmt);
    +        }
    +    }
    +
    +    /**
    +     * Validate a class constant fetch expression's class.
    +     *
    +     * @param StaticCall $stmt
    +     */
    +    protected function validateStaticCallExpression(StaticCall $stmt)
    +    {
    +        // if class name is an expression, give it a pass for now
    +        if (!$stmt->class instanceof Expr) {
    +            $this->ensureMethodExists($this->getFullyQualifiedName($stmt->class), $stmt->name, $stmt);
    +        }
    +    }
    +
    +    /**
    +     * Ensure that no class, interface or trait name collides with a new definition.
    +     *
    +     * @throws FatalErrorException
    +     *
    +     * @param Stmt $stmt
    +     */
    +    protected function ensureCanDefine(Stmt $stmt)
    +    {
    +        $name = $this->getFullyQualifiedName($stmt->name);
    +
    +        // check for name collisions
    +        $errorType = null;
    +        if ($this->classExists($name)) {
    +            $errorType = self::CLASS_TYPE;
    +        } elseif ($this->interfaceExists($name)) {
    +            $errorType = self::INTERFACE_TYPE;
    +        } elseif ($this->traitExists($name)) {
    +            $errorType = self::TRAIT_TYPE;
    +        }
    +
    +        if ($errorType !== null) {
    +            throw $this->createError(sprintf('%s named %s already exists', ucfirst($errorType), $name), $stmt);
    +        }
    +
    +        // Store creation for the rest of this code snippet so we can find local
    +        // issue too
    +        $this->currentScope[strtolower($name)] = $this->getScopeType($stmt);
    +    }
    +
    +    /**
    +     * Ensure that a referenced class exists.
    +     *
    +     * @throws FatalErrorException
    +     *
    +     * @param string $name
    +     * @param Stmt   $stmt
    +     */
    +    protected function ensureClassExists($name, $stmt)
    +    {
    +        if (!$this->classExists($name)) {
    +            throw $this->createError(sprintf('Class \'%s\' not found', $name), $stmt);
    +        }
    +    }
    +
    +    /**
    +     * Ensure that a referenced class _or interface_ exists.
    +     *
    +     * @throws FatalErrorException
    +     *
    +     * @param string $name
    +     * @param Stmt   $stmt
    +     */
    +    protected function ensureClassOrInterfaceExists($name, $stmt)
    +    {
    +        if (!$this->classExists($name) && !$this->interfaceExists($name)) {
    +            throw $this->createError(sprintf('Class \'%s\' not found', $name), $stmt);
    +        }
    +    }
    +
    +    /**
    +     * Ensure that a statically called method exists.
    +     *
    +     * @throws FatalErrorException
    +     *
    +     * @param string $class
    +     * @param string $name
    +     * @param Stmt   $stmt
    +     */
    +    protected function ensureMethodExists($class, $name, $stmt)
    +    {
    +        $this->ensureClassExists($class, $stmt);
    +
    +        // let's pretend all calls to self, parent and static are valid
    +        if (in_array(strtolower($class), array('self', 'parent', 'static'))) {
    +            return;
    +        }
    +
    +        // if method name is an expression, give it a pass for now
    +        if ($name instanceof Expr) {
    +            return;
    +        }
    +
    +        if (!method_exists($class, $name) && !method_exists($class, '__callStatic')) {
    +            throw $this->createError(sprintf('Call to undefined method %s::%s()', $class, $name), $stmt);
    +        }
    +    }
    +
    +    /**
    +     * Ensure that a referenced interface exists.
    +     *
    +     * @throws FatalErrorException
    +     *
    +     * @param $interfaces
    +     * @param Stmt $stmt
    +     */
    +    protected function ensureInterfacesExist($interfaces, $stmt)
    +    {
    +        foreach ($interfaces as $interface) {
    +            /** @var string $name */
    +            $name = $this->getFullyQualifiedName($interface);
    +            if (!$this->interfaceExists($name)) {
    +                throw $this->createError(sprintf('Interface \'%s\' not found', $name), $stmt);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Get a symbol type key for storing in the scope name cache.
    +     *
    +     * @param Stmt $stmt
    +     *
    +     * @return string
    +     */
    +    protected function getScopeType(Stmt $stmt)
    +    {
    +        if ($stmt instanceof ClassStmt) {
    +            return self::CLASS_TYPE;
    +        } elseif ($stmt instanceof InterfaceStmt) {
    +            return self::INTERFACE_TYPE;
    +        } elseif ($stmt instanceof TraitStmt) {
    +            return self::TRAIT_TYPE;
    +        }
    +    }
    +
    +    /**
    +     * Check whether a class exists, or has been defined in the current code snippet.
    +     *
    +     * Gives `self`, `static` and `parent` a free pass.
    +     *
    +     * @param string $name
    +     *
    +     * @return bool
    +     */
    +    protected function classExists($name)
    +    {
    +        // Give `self`, `static` and `parent` a pass. This will actually let
    +        // some errors through, since we're not checking whether the keyword is
    +        // being used in a class scope.
    +        if (in_array(strtolower($name), array('self', 'static', 'parent'))) {
    +            return true;
    +        }
    +
    +        return class_exists($name) || $this->findInScope($name) === self::CLASS_TYPE;
    +    }
    +
    +    /**
    +     * Check whether an interface exists, or has been defined in the current code snippet.
    +     *
    +     * @param string $name
    +     *
    +     * @return bool
    +     */
    +    protected function interfaceExists($name)
    +    {
    +        return interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE;
    +    }
    +
    +    /**
    +     * Check whether a trait exists, or has been defined in the current code snippet.
    +     *
    +     * @param string $name
    +     *
    +     * @return bool
    +     */
    +    protected function traitExists($name)
    +    {
    +        return $this->checkTraits && (trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE);
    +    }
    +
    +    /**
    +     * Find a symbol in the current code snippet scope.
    +     *
    +     * @param string $name
    +     *
    +     * @return string|null
    +     */
    +    protected function findInScope($name)
    +    {
    +        $name = strtolower($name);
    +        if (isset($this->currentScope[$name])) {
    +            return $this->currentScope[$name];
    +        }
    +    }
    +
    +    /**
    +     * Error creation factory.
    +     *
    +     * @param string $msg
    +     * @param Stmt   $stmt
    +     *
    +     * @return FatalErrorException
    +     */
    +    protected function createError($msg, $stmt)
    +    {
    +        return new FatalErrorException($msg, 0, 1, null, $stmt->getLine());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidConstantPass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidConstantPass.php
    new file mode 100644
    index 0000000..da54f42
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidConstantPass.php
    @@ -0,0 +1,85 @@
    +name->parts) > 1) {
    +            $name = $this->getFullyQualifiedName($node->name);
    +            if (!defined($name)) {
    +                throw new FatalErrorException(sprintf('Undefined constant %s', $name), 0, 1, null, $node->getLine());
    +            }
    +        } elseif ($node instanceof ClassConstFetch) {
    +            $this->validateClassConstFetchExpression($node);
    +        }
    +    }
    +
    +    /**
    +     * Validate a class constant fetch expression.
    +     *
    +     * @throws FatalErrorException if a class constant is not defined.
    +     *
    +     * @param ClassConstFetch $stmt
    +     */
    +    protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
    +    {
    +        // give the `class` pseudo-constant a pass
    +        if ($stmt->name === 'class') {
    +            return;
    +        }
    +
    +        // if class name is an expression, give it a pass for now
    +        if (!$stmt->class instanceof Expr) {
    +            $className = $this->getFullyQualifiedName($stmt->class);
    +
    +            // if the class doesn't exist, don't throw an exception… it might be
    +            // defined in the same line it's used or something stupid like that.
    +            if (class_exists($className) || interface_exists($className)) {
    +                $constName = sprintf('%s::%s', $className, $stmt->name);
    +                if (!defined($constName)) {
    +                    $constType = class_exists($className) ? 'Class' : 'Interface';
    +                    $msg = sprintf('%s constant \'%s\' not found', $constType, $constName);
    +                    throw new FatalErrorException($msg, 0, 1, null, $stmt->getLine());
    +                }
    +            }
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidFunctionNamePass.php b/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidFunctionNamePass.php
    new file mode 100644
    index 0000000..70eea91
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/CodeCleaner/ValidFunctionNamePass.php
    @@ -0,0 +1,73 @@
    +getFullyQualifiedName($node->name);
    +
    +            if (function_exists($name) || isset($this->currentScope[strtolower($name)])) {
    +                throw new FatalErrorException(sprintf('Cannot redeclare %s()', $name), 0, 1, null, $node->getLine());
    +            }
    +
    +            $this->currentScope[strtolower($name)] = true;
    +        }
    +    }
    +
    +    /**
    +     * Validate that function calls will succeed.
    +     *
    +     * @throws FatalErrorException if a function is redefined.
    +     * @throws FatalErrorException if the function name is a string (not an expression) and is not defined.
    +     *
    +     * @param Node $node
    +     */
    +    public function leaveNode(Node $node)
    +    {
    +        if ($node instanceof FuncCall) {
    +            // if function name is an expression or a variable, give it a pass for now.
    +            $name = $node->name;
    +            if (!$name instanceof Expr && !$name instanceof Variable) {
    +                $shortName = implode('\\', $name->parts);
    +                $fullName  = $this->getFullyQualifiedName($name);
    +                $inScope = isset($this->currentScope[strtolower($fullName)]);
    +                if (!$inScope && !function_exists($shortName) && !function_exists($fullName)) {
    +                    $message = sprintf('Call to undefined function %s()', $name);
    +                    throw new FatalErrorException($message, 0, 1, null, $node->getLine());
    +                }
    +            }
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/BufferCommand.php b/core/vendor/psy/psysh/src/Psy/Command/BufferCommand.php
    new file mode 100644
    index 0000000..bfa1da1
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/BufferCommand.php
    @@ -0,0 +1,77 @@
    +setName('buffer')
    +            ->setAliases(array('buf'))
    +            ->setDefinition(array(
    +                new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the current buffer.'),
    +            ))
    +            ->setDescription('Show (or clear) the contents of the code input buffer.')
    +            ->setHelp(
    +                <<<'HELP'
    +Show the contents of the code buffer for the current multi-line expression.
    +
    +Optionally, clear the buffer by passing the --clear option.
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $buf = $this->getApplication()->getCodeBuffer();
    +        if ($input->getOption('clear')) {
    +            $this->getApplication()->resetCodeBuffer();
    +            $output->writeln($this->formatLines($buf, 'urgent'), ShellOutput::NUMBER_LINES);
    +        } else {
    +            $output->writeln($this->formatLines($buf), ShellOutput::NUMBER_LINES);
    +        }
    +    }
    +
    +    /**
    +     * A helper method for wrapping buffer lines in `` and `` formatter strings.
    +     *
    +     * @param array  $lines
    +     * @param string $type  (default: 'return')
    +     *
    +     * @return array Formatted strings
    +     */
    +    protected function formatLines(array $lines, $type = 'return')
    +    {
    +        $template = sprintf('<%s>%%s', $type, $type);
    +
    +        return array_map(function ($line) use ($template) {
    +            return sprintf($template, $line);
    +        }, $lines);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ClearCommand.php b/core/vendor/psy/psysh/src/Psy/Command/ClearCommand.php
    new file mode 100644
    index 0000000..56b422b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ClearCommand.php
    @@ -0,0 +1,49 @@
    +setName('clear')
    +            ->setDefinition(array())
    +            ->setDescription('Clear the Psy Shell screen.')
    +            ->setHelp(
    +                <<<'HELP'
    +Clear the Psy Shell screen.
    +
    +Pro Tip: If your PHP has readline support, you should be able to use ctrl+l too!
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $output->write(sprintf('%c[2J%c[0;0f', 27, 27));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/Command.php b/core/vendor/psy/psysh/src/Psy/Command/Command.php
    new file mode 100644
    index 0000000..50e20cd
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/Command.php
    @@ -0,0 +1,282 @@
    +Usage:',
    +            ' ' . $this->getSynopsis(),
    +            '',
    +        );
    +
    +        if ($this->getAliases()) {
    +            $messages[] = $this->aliasesAsText();
    +        }
    +
    +        if ($this->getArguments()) {
    +            $messages[] = $this->argumentsAsText();
    +        }
    +
    +        if ($this->getOptions()) {
    +            $messages[] = $this->optionsAsText();
    +        }
    +
    +        if ($help = $this->getProcessedHelp()) {
    +            $messages[] = 'Help:';
    +            $messages[] = ' ' . str_replace("\n", "\n ", $help) . "\n";
    +        }
    +
    +        return implode("\n", $messages);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    private function getArguments()
    +    {
    +        $hidden = $this->getHiddenArguments();
    +
    +        return array_filter($this->getNativeDefinition()->getArguments(), function ($argument) use ($hidden) {
    +            return !in_array($argument->getName(), $hidden);
    +        });
    +    }
    +
    +    /**
    +     * These arguments will be excluded from help output.
    +     *
    +     * @return array
    +     */
    +    protected function getHiddenArguments()
    +    {
    +        return array('command');
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    private function getOptions()
    +    {
    +        $hidden = $this->getHiddenOptions();
    +
    +        return array_filter($this->getNativeDefinition()->getOptions(), function ($option) use ($hidden) {
    +            return !in_array($option->getName(), $hidden);
    +        });
    +    }
    +
    +    /**
    +     * These options will be excluded from help output.
    +     *
    +     * @return array
    +     */
    +    protected function getHiddenOptions()
    +    {
    +        return array('verbose');
    +    }
    +
    +    /**
    +     * Format command aliases as text..
    +     *
    +     * @return string
    +     */
    +    private function aliasesAsText()
    +    {
    +        return 'Aliases: ' . implode(', ', $this->getAliases()) . '' . PHP_EOL;
    +    }
    +
    +    /**
    +     * Format command arguments as text.
    +     *
    +     * @return string
    +     */
    +    private function argumentsAsText()
    +    {
    +        $max = $this->getMaxWidth();
    +        $messages = array();
    +
    +        $arguments = $this->getArguments();
    +        if (!empty($arguments)) {
    +            $messages[] = 'Arguments:';
    +            foreach ($arguments as $argument) {
    +                if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) {
    +                    $default = sprintf(' (default: %s)', $this->formatDefaultValue($argument->getDefault()));
    +                } else {
    +                    $default = '';
    +                }
    +
    +                $description = str_replace("\n", "\n" . str_pad('', $max + 2, ' '), $argument->getDescription());
    +
    +                $messages[] = sprintf(" %-${max}s %s%s", $argument->getName(), $description, $default);
    +            }
    +
    +            $messages[] = '';
    +        }
    +
    +        return implode(PHP_EOL, $messages);
    +    }
    +
    +    /**
    +     * Format options as text.
    +     *
    +     * @return string
    +     */
    +    private function optionsAsText()
    +    {
    +        $max = $this->getMaxWidth();
    +        $messages = array();
    +
    +        $options = $this->getOptions();
    +        if ($options) {
    +            $messages[] = 'Options:';
    +
    +            foreach ($options as $option) {
    +                if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) {
    +                    $default = sprintf(' (default: %s)', $this->formatDefaultValue($option->getDefault()));
    +                } else {
    +                    $default = '';
    +                }
    +
    +                $multiple = $option->isArray() ? ' (multiple values allowed)' : '';
    +                $description = str_replace("\n", "\n" . str_pad('', $max + 2, ' '), $option->getDescription());
    +
    +                $optionMax = $max - strlen($option->getName()) - 2;
    +                $messages[] = sprintf(
    +                    " %s %-${optionMax}s%s%s%s",
    +                    '--' . $option->getName(),
    +                    $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '',
    +                    $description,
    +                    $default,
    +                    $multiple
    +                );
    +            }
    +
    +            $messages[] = '';
    +        }
    +
    +        return implode(PHP_EOL, $messages);
    +    }
    +
    +    /**
    +     * Calculate the maximum padding width for a set of lines.
    +     *
    +     * @return int
    +     */
    +    private function getMaxWidth()
    +    {
    +        $max = 0;
    +
    +        foreach ($this->getOptions() as $option) {
    +            $nameLength = strlen($option->getName()) + 2;
    +            if ($option->getShortcut()) {
    +                $nameLength += strlen($option->getShortcut()) + 3;
    +            }
    +
    +            $max = max($max, $nameLength);
    +        }
    +
    +        foreach ($this->getArguments() as $argument) {
    +            $max = max($max, strlen($argument->getName()));
    +        }
    +
    +        return ++$max;
    +    }
    +
    +    /**
    +     * Format an option default as text.
    +     *
    +     * @param mixed $default
    +     *
    +     * @return string
    +     */
    +    private function formatDefaultValue($default)
    +    {
    +        if (is_array($default) && $default === array_values($default)) {
    +            return sprintf("array('%s')", implode("', '", $default));
    +        }
    +
    +        return str_replace("\n", '', var_export($default, true));
    +    }
    +
    +    /**
    +     * Get a Table instance.
    +     *
    +     * Falls back to legacy TableHelper.
    +     *
    +     * @return Table|TableHelper
    +     */
    +    protected function getTable(OutputInterface $output)
    +    {
    +        if (!class_exists('Symfony\Component\Console\Helper\Table')) {
    +            return $this->getTableHelper();
    +        }
    +
    +        $style = new TableStyle();
    +        $style
    +            ->setVerticalBorderChar(' ')
    +            ->setHorizontalBorderChar('')
    +            ->setCrossingChar('');
    +
    +        $table = new Table($output);
    +
    +        return $table
    +            ->setRows(array())
    +            ->setStyle($style);
    +    }
    +
    +    /**
    +     * Legacy fallback for getTable.
    +     *
    +     * @return TableHelper
    +     */
    +    protected function getTableHelper()
    +    {
    +        $table = $this->getApplication()->getHelperSet()->get('table');
    +
    +        return $table
    +            ->setRows(array())
    +            ->setLayout(TableHelper::LAYOUT_BORDERLESS)
    +            ->setHorizontalBorderChar('')
    +            ->setCrossingChar('');
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/DocCommand.php b/core/vendor/psy/psysh/src/Psy/Command/DocCommand.php
    new file mode 100644
    index 0000000..9f4a675
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/DocCommand.php
    @@ -0,0 +1,98 @@
    +setName('doc')
    +            ->setAliases(array('rtfm', 'man'))
    +            ->setDefinition(array(
    +                new InputArgument('value', InputArgument::REQUIRED, 'Function, class, instance, constant, method or property to document.'),
    +            ))
    +            ->setDescription('Read the documentation for an object, class, constant, method or property.')
    +            ->setHelp(
    +                <<>>> doc preg_replace
    +>>> doc Psy\Shell
    +>>> doc Psy\Shell::debug
    +>>> \$s = new Psy\Shell
    +>>> doc \$s->run
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        list($value, $reflector) = $this->getTargetAndReflector($input->getArgument('value'));
    +
    +        $doc = $this->getManualDoc($reflector) ?: DocblockFormatter::format($reflector);
    +        $db  = $this->getApplication()->getManualDb();
    +
    +        $output->page(function ($output) use ($reflector, $doc, $db) {
    +            $output->writeln(SignatureFormatter::format($reflector));
    +            if (empty($doc) && !$db) {
    +                $output->writeln('');
    +                $output->writeln('PHP manual not found');
    +                $output->writeln('    To document core PHP functionality, download the PHP reference manual:');
    +                $output->writeln('    https://github.com/bobthecow/psysh#downloading-the-manual');
    +            } else {
    +                $output->writeln('');
    +                $output->writeln($doc);
    +            }
    +        });
    +    }
    +
    +    private function getManualDoc($reflector)
    +    {
    +        switch (get_class($reflector)) {
    +            case 'ReflectionFunction':
    +                $id = $reflector->name;
    +                break;
    +
    +            case 'ReflectionMethod':
    +                $id = $reflector->class . '::' . $reflector->name;
    +                break;
    +
    +            default:
    +                return false;
    +        }
    +
    +        if ($db = $this->getApplication()->getManualDb()) {
    +            return $db
    +                ->query(sprintf('SELECT doc FROM php_manual WHERE id = %s', $db->quote($id)))
    +                ->fetchColumn(0);
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/DumpCommand.php b/core/vendor/psy/psysh/src/Psy/Command/DumpCommand.php
    new file mode 100644
    index 0000000..747b77d
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/DumpCommand.php
    @@ -0,0 +1,95 @@
    +presenter = $presenter;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function configure()
    +    {
    +        $this
    +            ->setName('dump')
    +            ->setDefinition(array(
    +                new InputArgument('target', InputArgument::REQUIRED, 'A target object or primitive to dump.', null),
    +                new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse', 10),
    +                new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'),
    +            ))
    +            ->setDescription('Dump an object or primitive.')
    +            ->setHelp(
    +                <<<'HELP'
    +Dump an object or primitive.
    +
    +This is like var_dump but way awesomer.
    +
    +e.g.
    +>>> dump $_
    +>>> dump $someVar
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $depth  = $input->getOption('depth');
    +        $target = $this->resolveTarget($input->getArgument('target'));
    +        $output->page($this->presenter->present($target, $depth, $input->getOption('all') ? Presenter::VERBOSE : 0));
    +    }
    +
    +    /**
    +     * Resolve dump target name.
    +     *
    +     * @throws RuntimeException if target name does not exist in the current scope.
    +     *
    +     * @param string $target
    +     *
    +     * @return mixed
    +     */
    +    protected function resolveTarget($target)
    +    {
    +        $matches = array();
    +        if (preg_match(self::INSTANCE, $target, $matches)) {
    +            return $this->getScopeVariable($matches[1]);
    +        } else {
    +            throw new RuntimeException('Unknown target: ' . $target);
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ExitCommand.php b/core/vendor/psy/psysh/src/Psy/Command/ExitCommand.php
    new file mode 100644
    index 0000000..59e8719
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ExitCommand.php
    @@ -0,0 +1,52 @@
    +setName('exit')
    +            ->setAliases(array('quit', 'q'))
    +            ->setDefinition(array())
    +            ->setDescription('End the current session and return to caller.')
    +            ->setHelp(
    +                <<<'HELP'
    +End the current session and return to caller.
    +
    +e.g.
    +>>> exit
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        throw new BreakException('Goodbye.');
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/HelpCommand.php b/core/vendor/psy/psysh/src/Psy/Command/HelpCommand.php
    new file mode 100644
    index 0000000..46d86d9
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/HelpCommand.php
    @@ -0,0 +1,98 @@
    +setName('help')
    +            ->setAliases(array('?'))
    +            ->setDefinition(array(
    +                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', null),
    +            ))
    +            ->setDescription('Show a list of commands. Type `help [foo]` for information about [foo].')
    +            ->setHelp('My. How meta.');
    +    }
    +
    +    /**
    +     * Helper for setting a subcommand to retrieve help for.
    +     *
    +     * @param Command $command
    +     */
    +    public function setCommand($command)
    +    {
    +        $this->command = $command;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        if ($this->command !== null) {
    +            // help for an individual command
    +            $output->page($this->command->asText());
    +            $this->command = null;
    +        } elseif ($name = $input->getArgument('command_name')) {
    +            // help for an individual command
    +            $output->page($this->getApplication()->get($name)->asText());
    +        } else {
    +            // list available commands
    +            $commands = $this->getApplication()->all();
    +
    +            $table = $this->getTable($output);
    +
    +            foreach ($commands as $name => $command) {
    +                if ($name !== $command->getName()) {
    +                    continue;
    +                }
    +
    +                if ($command->getAliases()) {
    +                    $aliases = sprintf('Aliases: %s', implode(', ', $command->getAliases()));
    +                } else {
    +                    $aliases = '';
    +                }
    +
    +                $table->addRow(array(
    +                    sprintf('%s', $name),
    +                    $command->getDescription(),
    +                    $aliases,
    +                ));
    +            }
    +
    +            $output->startPaging();
    +            if ($table instanceof TableHelper) {
    +                $table->render($output);
    +            } else {
    +                $table->render();
    +            }
    +            $output->stopPaging();
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/HistoryCommand.php b/core/vendor/psy/psysh/src/Psy/Command/HistoryCommand.php
    new file mode 100644
    index 0000000..d97a69a
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/HistoryCommand.php
    @@ -0,0 +1,260 @@
    +readline = $readline;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function configure()
    +    {
    +        $this
    +            ->setName('history')
    +            ->setAliases(array('hist'))
    +            ->setDefinition(array(
    +                new InputOption('show',        's', InputOption::VALUE_REQUIRED, 'Show the given range of lines'),
    +                new InputOption('head',        'H', InputOption::VALUE_REQUIRED, 'Display the first N items.'),
    +                new InputOption('tail',        'T', InputOption::VALUE_REQUIRED, 'Display the last N items.'),
    +
    +                new InputOption('grep',        'G', InputOption::VALUE_REQUIRED, 'Show lines matching the given pattern (string or regex).'),
    +                new InputOption('insensitive', 'i', InputOption::VALUE_NONE,     'Case insensitive search (requires --grep).'),
    +                new InputOption('invert',      'v', InputOption::VALUE_NONE,     'Inverted search (requires --grep).'),
    +
    +                new InputOption('no-numbers',  'N', InputOption::VALUE_NONE,     'Omit line numbers.'),
    +
    +                new InputOption('save',        '',  InputOption::VALUE_REQUIRED, 'Save history to a file.'),
    +                new InputOption('replay',      '',  InputOption::VALUE_NONE,     'Replay'),
    +                new InputOption('clear',       '',  InputOption::VALUE_NONE,     'Clear the history.'),
    +            ))
    +            ->setDescription('Show the Psy Shell history.')
    +            ->setHelp(
    +                <<<'HELP'
    +Show, search, save or replay the Psy Shell history.
    +
    +e.g.
    +>>> history --grep /[bB]acon/
    +>>> history --show 0..10 --replay
    +>>> history --clear
    +>>> history --tail 1000 --save somefile.txt
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $this->validateOnlyOne($input, array('show', 'head', 'tail'));
    +        $this->validateOnlyOne($input, array('save', 'replay', 'clear'));
    +
    +        $history = $this->getHistorySlice(
    +            $input->getOption('show'),
    +            $input->getOption('head'),
    +            $input->getOption('tail')
    +        );
    +        $highlighted = false;
    +
    +        $invert      = $input->getOption('invert');
    +        $insensitive = $input->getOption('insensitive');
    +        if ($pattern = $input->getOption('grep')) {
    +            if (substr($pattern, 0, 1) !== '/' || substr($pattern, -1) !== '/' || strlen($pattern) < 3) {
    +                $pattern = '/' . preg_quote($pattern, '/') . '/';
    +            }
    +
    +            if ($insensitive) {
    +                $pattern .= 'i';
    +            }
    +
    +            $this->validateRegex($pattern);
    +
    +            $matches     = array();
    +            $highlighted = array();
    +            foreach ($history as $i => $line) {
    +                if (preg_match($pattern, $line, $matches) xor $invert) {
    +                    if (!$invert) {
    +                        $chunks = explode($matches[0], $history[$i]);
    +                        $chunks = array_map(array(__CLASS__, 'escape'), $chunks);
    +                        $glue   = sprintf('%s', self::escape($matches[0]));
    +
    +                        $highlighted[$i] = implode($glue, $chunks);
    +                    }
    +                } else {
    +                    unset($history[$i]);
    +                }
    +            }
    +        } elseif ($invert) {
    +            throw new \InvalidArgumentException('Cannot use -v without --grep.');
    +        } elseif ($insensitive) {
    +            throw new \InvalidArgumentException('Cannot use -i without --grep.');
    +        }
    +
    +        if ($save = $input->getOption('save')) {
    +            $output->writeln(sprintf('Saving history in %s...', $save));
    +            file_put_contents($save, implode(PHP_EOL, $history) . PHP_EOL);
    +            $output->writeln('History saved.');
    +        } elseif ($input->getOption('replay')) {
    +            if (!($input->getOption('show') || $input->getOption('head') || $input->getOption('tail'))) {
    +                throw new \InvalidArgumentException('You must limit history via --head, --tail or --show before replaying.');
    +            }
    +
    +            $count = count($history);
    +            $output->writeln(sprintf('Replaying %d line%s of history', $count, ($count !== 1) ? 's' : ''));
    +            $this->getApplication()->addInput($history);
    +        } elseif ($input->getOption('clear')) {
    +            $this->clearHistory();
    +            $output->writeln('History cleared.');
    +        } else {
    +            $type = $input->getOption('no-numbers') ? 0 : ShellOutput::NUMBER_LINES;
    +            if (!$highlighted) {
    +                $type = $type | ShellOutput::OUTPUT_RAW;
    +            }
    +
    +            $output->page($highlighted ?: $history, $type);
    +        }
    +    }
    +
    +    /**
    +     * Extract a range from a string.
    +     *
    +     * @param string $range
    +     *
    +     * @return array [ start, end ]
    +     */
    +    private function extractRange($range)
    +    {
    +        if (preg_match('/^\d+$/', $range)) {
    +            return array($range, $range + 1);
    +        }
    +
    +        $matches = array();
    +        if ($range !== '..' && preg_match('/^(\d*)\.\.(\d*)$/', $range, $matches)) {
    +            $start = $matches[1] ? intval($matches[1]) : 0;
    +            $end   = $matches[2] ? intval($matches[2]) + 1 : PHP_INT_MAX;
    +
    +            return array($start, $end);
    +        }
    +
    +        throw new \InvalidArgumentException('Unexpected range: ' . $range);
    +    }
    +
    +    /**
    +     * Retrieve a slice of the readline history.
    +     *
    +     * @param string $show
    +     * @param string $head
    +     * @param string $tail
    +     *
    +     * @return array A slilce of history.
    +     */
    +    private function getHistorySlice($show, $head, $tail)
    +    {
    +        $history = $this->readline->listHistory();
    +
    +        if ($show) {
    +            list($start, $end) = $this->extractRange($show);
    +            $length = $end - $start;
    +        } elseif ($head) {
    +            if (!preg_match('/^\d+$/', $head)) {
    +                throw new \InvalidArgumentException('Please specify an integer argument for --head.');
    +            }
    +
    +            $start  = 0;
    +            $length = intval($head);
    +        } elseif ($tail) {
    +            if (!preg_match('/^\d+$/', $tail)) {
    +                throw new \InvalidArgumentException('Please specify an integer argument for --tail.');
    +            }
    +
    +            $start  = count($history) - $tail;
    +            $length = intval($tail) + 1;
    +        } else {
    +            return $history;
    +        }
    +
    +        return array_slice($history, $start, $length, true);
    +    }
    +
    +    /**
    +     * Validate that $pattern is a valid regular expression.
    +     *
    +     * @param string $pattern
    +     *
    +     * @return bool
    +     */
    +    private function validateRegex($pattern)
    +    {
    +        set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
    +        try {
    +            preg_match($pattern, '');
    +        } catch (ErrorException $e) {
    +            throw new RuntimeException(str_replace('preg_match(): ', 'Invalid regular expression: ', $e->getRawMessage()));
    +        }
    +        restore_error_handler();
    +    }
    +
    +    /**
    +     * Validate that only one of the given $options is set.
    +     *
    +     * @param InputInterface $input
    +     * @param array          $options
    +     */
    +    private function validateOnlyOne(InputInterface $input, array $options)
    +    {
    +        $count = 0;
    +        foreach ($options as $opt) {
    +            if ($input->getOption($opt)) {
    +                $count++;
    +            }
    +        }
    +
    +        if ($count > 1) {
    +            throw new \InvalidArgumentException('Please specify only one of --' . implode(', --', $options));
    +        }
    +    }
    +
    +    /**
    +     * Clear the readline history.
    +     */
    +    private function clearHistory()
    +    {
    +        $this->readline->clearHistory();
    +    }
    +
    +    public static function escape($string)
    +    {
    +        return OutputFormatter::escape($string);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand.php
    new file mode 100644
    index 0000000..7177a9d
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand.php
    @@ -0,0 +1,278 @@
    +presenter = $presenter;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function configure()
    +    {
    +        $this
    +            ->setName('ls')
    +            ->setAliases(array('list', 'dir'))
    +            ->setDefinition(array(
    +                new InputArgument('target', InputArgument::OPTIONAL, 'A target class or object to list.', null),
    +
    +                new InputOption('vars',        '',  InputOption::VALUE_NONE,     'Display variables.'),
    +                new InputOption('constants',   'c', InputOption::VALUE_NONE,     'Display defined constants.'),
    +                new InputOption('functions',   'f', InputOption::VALUE_NONE,     'Display defined functions.'),
    +                new InputOption('classes',     'k', InputOption::VALUE_NONE,     'Display declared classes.'),
    +                new InputOption('interfaces',  'I', InputOption::VALUE_NONE,     'Display declared interfaces.'),
    +                new InputOption('traits',      't', InputOption::VALUE_NONE,     'Display declared traits.'),
    +
    +                new InputOption('properties',  'p', InputOption::VALUE_NONE,     'Display class or object properties (public properties by default).'),
    +                new InputOption('methods',     'm', InputOption::VALUE_NONE,     'Display class or object methods (public methods by default).'),
    +
    +                new InputOption('grep',        'G', InputOption::VALUE_REQUIRED, 'Limit to items matching the given pattern (string or regex).'),
    +                new InputOption('insensitive', 'i', InputOption::VALUE_NONE,     'Case-insensitive search (requires --grep).'),
    +                new InputOption('invert',      'v', InputOption::VALUE_NONE,     'Inverted search (requires --grep).'),
    +
    +                new InputOption('globals',     'g', InputOption::VALUE_NONE,     'Include global variables.'),
    +                new InputOption('internal',    'n', InputOption::VALUE_NONE,     'Limit to internal functions and classes.'),
    +                new InputOption('user',        'u', InputOption::VALUE_NONE,     'Limit to user-defined constants, functions and classes.'),
    +                new InputOption('category',    'C', InputOption::VALUE_REQUIRED, 'Limit to constants in a specific category (e.g. "date").'),
    +
    +                new InputOption('all',         'a', InputOption::VALUE_NONE,     'Include private and protected methods and properties.'),
    +                new InputOption('long',        'l', InputOption::VALUE_NONE,     'List in long format: includes class names and method signatures.'),
    +            ))
    +            ->setDescription('List local, instance or class variables, methods and constants.')
    +            ->setHelp(
    +                <<<'HELP'
    +List variables, constants, classes, interfaces, traits, functions, methods,
    +and properties.
    +
    +Called without options, this will return a list of variables currently in scope.
    +
    +If a target object is provided, list properties, constants and methods of that
    +target. If a class, interface or trait name is passed instead, list constants
    +and methods on that class.
    +
    +e.g.
    +>>> ls
    +>>> ls $foo
    +>>> ls -k --grep mongo -i
    +>>> ls -al ReflectionClass
    +>>> ls --constants --category date
    +>>> ls -l --functions --grep /^array_.*/
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $this->validateInput($input);
    +        $this->initEnumerators();
    +
    +        $method = $input->getOption('long') ? 'writeLong' : 'write';
    +
    +        if ($target = $input->getArgument('target')) {
    +            list($target, $reflector) = $this->getTargetAndReflector($target, true);
    +        } else {
    +            $reflector = null;
    +        }
    +
    +        // TODO: something cleaner than this :-/
    +        if ($input->getOption('long')) {
    +            $output->startPaging();
    +        }
    +
    +        foreach ($this->enumerators as $enumerator) {
    +            $this->$method($output, $enumerator->enumerate($input, $reflector, $target));
    +        }
    +
    +        if ($input->getOption('long')) {
    +            $output->stopPaging();
    +        }
    +    }
    +
    +    /**
    +     * Initialize Enumerators.
    +     */
    +    protected function initEnumerators()
    +    {
    +        if (!isset($this->enumerators)) {
    +            $mgr = $this->presenter;
    +
    +            $this->enumerators = array(
    +                new ClassConstantEnumerator($mgr),
    +                new ClassEnumerator($mgr),
    +                new ConstantEnumerator($mgr),
    +                new FunctionEnumerator($mgr),
    +                new GlobalVariableEnumerator($mgr),
    +                new InterfaceEnumerator($mgr),
    +                new PropertyEnumerator($mgr),
    +                new MethodEnumerator($mgr),
    +                new TraitEnumerator($mgr),
    +                new VariableEnumerator($mgr, $this->context),
    +            );
    +        }
    +    }
    +
    +    /**
    +     * Write the list items to $output.
    +     *
    +     * @param OutputInterface $output
    +     * @param null|array      $result List of enumerated items.
    +     */
    +    protected function write(OutputInterface $output, array $result = null)
    +    {
    +        if ($result === null) {
    +            return;
    +        }
    +
    +        foreach ($result as $label => $items) {
    +            $names = array_map(array($this, 'formatItemName'), $items);
    +            $output->writeln(sprintf('%s: %s', $label, implode(', ', $names)));
    +        }
    +    }
    +
    +    /**
    +     * Write the list items to $output.
    +     *
    +     * Items are listed one per line, and include the item signature.
    +     *
    +     * @param OutputInterface $output
    +     * @param null|array      $result List of enumerated items.
    +     */
    +    protected function writeLong(OutputInterface $output, array $result = null)
    +    {
    +        if ($result === null) {
    +            return;
    +        }
    +
    +        $table = $this->getTable($output);
    +
    +        foreach ($result as $label => $items) {
    +            $output->writeln('');
    +            $output->writeln(sprintf('%s:', $label));
    +
    +            $table->setRows(array());
    +            foreach ($items as $item) {
    +                $table->addRow(array($this->formatItemName($item), $item['value']));
    +            }
    +
    +            if ($table instanceof TableHelper) {
    +                $table->render($output);
    +            } else {
    +                $table->render();
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Format an item name given its visibility.
    +     *
    +     * @param array $item
    +     *
    +     * @return string
    +     */
    +    private function formatItemName($item)
    +    {
    +        return sprintf('<%s>%s', $item['style'], OutputFormatter::escape($item['name']), $item['style']);
    +    }
    +
    +    /**
    +     * Validate that input options make sense, provide defaults when called without options.
    +     *
    +     * @throws RuntimeException if options are inconsistent.
    +     *
    +     * @param InputInterface $input
    +     */
    +    private function validateInput(InputInterface $input)
    +    {
    +        // grep, invert and insensitive
    +        if (!$input->getOption('grep')) {
    +            foreach (array('invert', 'insensitive') as $option) {
    +                if ($input->getOption($option)) {
    +                    throw new RuntimeException('--' . $option . ' does not make sense without --grep');
    +                }
    +            }
    +        }
    +
    +        if (!$input->getArgument('target')) {
    +            // if no target is passed, there can be no properties or methods
    +            foreach (array('properties', 'methods') as $option) {
    +                if ($input->getOption($option)) {
    +                    throw new RuntimeException('--' . $option . ' does not make sense without a specified target.');
    +                }
    +            }
    +
    +            foreach (array('globals', 'vars', 'constants', 'functions', 'classes', 'interfaces', 'traits') as $option) {
    +                if ($input->getOption($option)) {
    +                    return;
    +                }
    +            }
    +
    +            // default to --vars if no other options are passed
    +            $input->setOption('vars', true);
    +        } else {
    +            // if a target is passed, classes, functions, etc don't make sense
    +            foreach (array('vars', 'globals', 'functions', 'classes', 'interfaces', 'traits') as $option) {
    +                if ($input->getOption($option)) {
    +                    throw new RuntimeException('--' . $option . ' does not make sense with a specified target.');
    +                }
    +            }
    +
    +            foreach (array('constants', 'properties', 'methods') as $option) {
    +                if ($input->getOption($option)) {
    +                    return;
    +                }
    +            }
    +
    +            // default to --constants --properties --methods if no other options are passed
    +            $input->setOption('constants',  true);
    +            $input->setOption('properties', true);
    +            $input->setOption('methods',    true);
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ClassConstantEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ClassConstantEnumerator.php
    new file mode 100644
    index 0000000..98593e8
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ClassConstantEnumerator.php
    @@ -0,0 +1,118 @@
    +getOption('constants')) {
    +            return;
    +        }
    +
    +        $constants = $this->prepareConstants($this->getConstants($reflector));
    +
    +        if (empty($constants)) {
    +            return;
    +        }
    +
    +        $ret = array();
    +        $ret[$this->getKindLabel($reflector)] = $constants;
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Get defined constants for the given class or object Reflector.
    +     *
    +     * @param \Reflector $reflector
    +     *
    +     * @return array
    +     */
    +    protected function getConstants(\Reflector $reflector)
    +    {
    +        $constants = array();
    +        foreach ($reflector->getConstants() as $name => $constant) {
    +            $constants[$name] = new ReflectionConstant($reflector, $name);
    +        }
    +
    +        // TODO: this should be natcasesort
    +        ksort($constants);
    +
    +        return $constants;
    +    }
    +
    +    /**
    +     * Prepare formatted constant array.
    +     *
    +     * @param array $constants
    +     *
    +     * @return array
    +     */
    +    protected function prepareConstants(array $constants)
    +    {
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        foreach ($constants as $name => $constant) {
    +            if ($this->showItem($name)) {
    +                $ret[$name] = array(
    +                    'name'  => $name,
    +                    'style' => self::IS_CONSTANT,
    +                    'value' => $this->presentRef($constant->getValue()),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Get a label for the particular kind of "class" represented.
    +     *
    +     * @param \ReflectionClass $reflector
    +     *
    +     * @return string
    +     */
    +    protected function getKindLabel(\ReflectionClass $reflector)
    +    {
    +        if ($reflector->isInterface()) {
    +            return 'Interface Constants';
    +        } elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
    +            return 'Trait Constants';
    +        } else {
    +            return 'Class Constants';
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ClassEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ClassEnumerator.php
    new file mode 100644
    index 0000000..ba3babf
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ClassEnumerator.php
    @@ -0,0 +1,80 @@
    +getOption('classes')) {
    +            return;
    +        }
    +
    +        $classes = $this->prepareClasses(get_declared_classes());
    +
    +        if (empty($classes)) {
    +            return;
    +        }
    +
    +        return array(
    +            'Classes' => $classes,
    +        );
    +    }
    +
    +    /**
    +     * Prepare formatted class array.
    +     *
    +     * @param array $class
    +     *
    +     * @return array
    +     */
    +    protected function prepareClasses(array $classes)
    +    {
    +        natcasesort($classes);
    +
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        foreach ($classes as $name) {
    +            if ($this->showItem($name)) {
    +                $ret[$name] = array(
    +                    'name'  => $name,
    +                    'style' => self::IS_CLASS,
    +                    'value' => $this->presentSignature($name),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ConstantEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ConstantEnumerator.php
    new file mode 100644
    index 0000000..c24be25
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/ConstantEnumerator.php
    @@ -0,0 +1,103 @@
    +getOption('constants')) {
    +            return;
    +        }
    +
    +        $category  = $input->getOption('user') ? 'user' : $input->getOption('category');
    +        $label     = $category ? ucfirst($category) . ' Constants' : 'Constants';
    +        $constants = $this->prepareConstants($this->getConstants($category));
    +
    +        if (empty($constants)) {
    +            return;
    +        }
    +
    +        $ret = array();
    +        $ret[$label] = $constants;
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Get defined constants.
    +     *
    +     * Optionally restrict constants to a given category, e.g. "date".
    +     *
    +     * @param string $category
    +     *
    +     * @return array
    +     */
    +    protected function getConstants($category = null)
    +    {
    +        if (!$category) {
    +            return get_defined_constants();
    +        }
    +
    +        $consts = get_defined_constants(true);
    +
    +        return isset($consts[$category]) ? $consts[$category] : array();
    +    }
    +
    +    /**
    +     * Prepare formatted constant array.
    +     *
    +     * @param array $constants
    +     *
    +     * @return array
    +     */
    +    protected function prepareConstants(array $constants)
    +    {
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        $names = array_keys($constants);
    +        natcasesort($names);
    +
    +        foreach ($names as $name) {
    +            if ($this->showItem($name)) {
    +                $ret[$name] = array(
    +                    'name'  => $name,
    +                    'style' => self::IS_CONSTANT,
    +                    'value' => $this->presentRef($constants[$name]),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/Enumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/Enumerator.php
    new file mode 100644
    index 0000000..094ca31
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/Enumerator.php
    @@ -0,0 +1,146 @@
    +presenter = $presenter;
    +    }
    +
    +    /**
    +     * Return a list of categorized things with the given input options and target.
    +     *
    +     * @param InputInterface $input
    +     * @param Reflector      $reflector
    +     * @param mixed          $target
    +     *
    +     * @return array
    +     */
    +    public function enumerate(InputInterface $input, \Reflector $reflector = null, $target = null)
    +    {
    +        $this->setFilter($input);
    +
    +        return $this->listItems($input, $reflector, $target);
    +    }
    +
    +    /**
    +     * Enumerate specific items with the given input options and target.
    +     *
    +     * Implementing classes should return an array of arrays:
    +     *
    +     *     [
    +     *         'Constants' => [
    +     *             'FOO' => [
    +     *                 'name'  => 'FOO',
    +     *                 'style' => 'public',
    +     *                 'value' => '123',
    +     *             ],
    +     *         ],
    +     *     ]
    +     *
    +     * @param InputInterface $input
    +     * @param Reflector      $reflector
    +     * @param mixed          $target
    +     *
    +     * @return array
    +     */
    +    abstract protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null);
    +
    +    protected function presentRef($value)
    +    {
    +        return $this->presenter->presentRef($value);
    +    }
    +
    +    protected function showItem($name)
    +    {
    +        return $this->filter === false || (preg_match($this->pattern, $name) xor $this->invertFilter);
    +    }
    +
    +    private function setFilter(InputInterface $input)
    +    {
    +        if ($pattern = $input->getOption('grep')) {
    +            if (substr($pattern, 0, 1) !== '/' || substr($pattern, -1) !== '/' || strlen($pattern) < 3) {
    +                $pattern = '/' . preg_quote($pattern, '/') . '/';
    +            }
    +
    +            if ($input->getOption('insensitive')) {
    +                $pattern .= 'i';
    +            }
    +
    +            $this->validateRegex($pattern);
    +
    +            $this->filter       = true;
    +            $this->pattern      = $pattern;
    +            $this->invertFilter = $input->getOption('invert');
    +        } else {
    +            $this->filter = false;
    +        }
    +    }
    +
    +    /**
    +     * Validate that $pattern is a valid regular expression.
    +     *
    +     * @param string $pattern
    +     *
    +     * @return bool
    +     */
    +    private function validateRegex($pattern)
    +    {
    +        set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
    +        try {
    +            preg_match($pattern, '');
    +        } catch (ErrorException $e) {
    +            throw new RuntimeException(str_replace('preg_match(): ', 'Invalid regular expression: ', $e->getRawMessage()));
    +        }
    +        restore_error_handler();
    +    }
    +
    +    protected function presentSignature($target)
    +    {
    +        // This might get weird if the signature is actually for a reflector. Hrm.
    +        if (!$target instanceof \Reflector) {
    +            $target = Mirror::get($target);
    +        }
    +
    +        return SignatureFormatter::format($target);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/FunctionEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/FunctionEnumerator.php
    new file mode 100644
    index 0000000..712f1ce
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/FunctionEnumerator.php
    @@ -0,0 +1,112 @@
    +getOption('functions')) {
    +            return;
    +        }
    +
    +        if ($input->getOption('user')) {
    +            $label     = 'User Functions';
    +            $functions = $this->getFunctions('user');
    +        } elseif ($input->getOption('internal')) {
    +            $label     = 'Internal Functions';
    +            $functions = $this->getFunctions('internal');
    +        } else {
    +            $label     = 'Functions';
    +            $functions = $this->getFunctions();
    +        }
    +
    +        $functions = $this->prepareFunctions($functions);
    +
    +        if (empty($functions)) {
    +            return;
    +        }
    +
    +        $ret = array();
    +        $ret[$label] = $functions;
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Get defined functions.
    +     *
    +     * Optionally limit functions to "user" or "internal" functions.
    +     *
    +     * @param null|string $type "user" or "internal" (default: both)
    +     *
    +     * @return array
    +     */
    +    protected function getFunctions($type = null)
    +    {
    +        $funcs = get_defined_functions();
    +
    +        if ($type) {
    +            return $funcs[$type];
    +        } else {
    +            return array_merge($funcs['internal'], $funcs['user']);
    +        }
    +    }
    +
    +    /**
    +     * Prepare formatted function array.
    +     *
    +     * @param array $functions
    +     *
    +     * @return array
    +     */
    +    protected function prepareFunctions(array $functions)
    +    {
    +        natcasesort($functions);
    +
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        foreach ($functions as $name) {
    +            if ($this->showItem($name)) {
    +                $ret[$name] = array(
    +                    'name'  => $name,
    +                    'style' => self::IS_FUNCTION,
    +                    'value' => $this->presentSignature($name),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/GlobalVariableEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/GlobalVariableEnumerator.php
    new file mode 100644
    index 0000000..754af3d
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/GlobalVariableEnumerator.php
    @@ -0,0 +1,92 @@
    +getOption('globals')) {
    +            return;
    +        }
    +
    +        $globals = $this->prepareGlobals($this->getGlobals());
    +
    +        if (empty($globals)) {
    +            return;
    +        }
    +
    +        return array(
    +            'Global Variables' => $globals,
    +        );
    +    }
    +
    +    /**
    +     * Get defined global variables.
    +     *
    +     * @return array
    +     */
    +    protected function getGlobals()
    +    {
    +        global $GLOBALS;
    +
    +        $names = array_keys($GLOBALS);
    +        natcasesort($names);
    +
    +        $ret = array();
    +        foreach ($names as $name) {
    +            $ret[$name] = $GLOBALS[$name];
    +        }
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Prepare formatted global variable array.
    +     *
    +     * @param array $globals
    +     *
    +     * @return array
    +     */
    +    protected function prepareGlobals($globals)
    +    {
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        foreach ($globals as $name => $value) {
    +            if ($this->showItem($name)) {
    +                $fname = '$' . $name;
    +                $ret[$fname] = array(
    +                    'name'  => $fname,
    +                    'style' => self::IS_GLOBAL,
    +                    'value' => $this->presentRef($value),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/InterfaceEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/InterfaceEnumerator.php
    new file mode 100644
    index 0000000..1522660
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/InterfaceEnumerator.php
    @@ -0,0 +1,80 @@
    +getOption('interfaces')) {
    +            return;
    +        }
    +
    +        $interfaces = $this->prepareInterfaces(get_declared_interfaces());
    +
    +        if (empty($interfaces)) {
    +            return;
    +        }
    +
    +        return array(
    +            'Interfaces' => $interfaces,
    +        );
    +    }
    +
    +    /**
    +     * Prepare formatted interface array.
    +     *
    +     * @param array $interfaces
    +     *
    +     * @return array
    +     */
    +    protected function prepareInterfaces(array $interfaces)
    +    {
    +        natcasesort($interfaces);
    +
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        foreach ($interfaces as $name) {
    +            if ($this->showItem($name)) {
    +                $ret[$name] = array(
    +                    'name'  => $name,
    +                    'style' => self::IS_CLASS,
    +                    'value' => $this->presentSignature($name),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/MethodEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/MethodEnumerator.php
    new file mode 100644
    index 0000000..852d734
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/MethodEnumerator.php
    @@ -0,0 +1,138 @@
    +getOption('methods')) {
    +            return;
    +        }
    +
    +        $showAll = $input->getOption('all');
    +        $methods = $this->prepareMethods($this->getMethods($showAll, $reflector));
    +
    +        if (empty($methods)) {
    +            return;
    +        }
    +
    +        $ret = array();
    +        $ret[$this->getKindLabel($reflector)] = $methods;
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Get defined methods for the given class or object Reflector.
    +     *
    +     * @param bool       $showAll   Include private and protected methods.
    +     * @param \Reflector $reflector
    +     *
    +     * @return array
    +     */
    +    protected function getMethods($showAll, \Reflector $reflector)
    +    {
    +        $methods = array();
    +        foreach ($reflector->getMethods() as $name => $method) {
    +            if ($showAll || $method->isPublic()) {
    +                $methods[$method->getName()] = $method;
    +            }
    +        }
    +
    +        // TODO: this should be natcasesort
    +        ksort($methods);
    +
    +        return $methods;
    +    }
    +
    +    /**
    +     * Prepare formatted method array.
    +     *
    +     * @param array $methods
    +     *
    +     * @return array
    +     */
    +    protected function prepareMethods(array $methods)
    +    {
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        foreach ($methods as $name => $method) {
    +            if ($this->showItem($name)) {
    +                $ret[$name] = array(
    +                    'name'  => $name,
    +                    'style' => $this->getVisibilityStyle($method),
    +                    'value' => $this->presentSignature($method),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Get a label for the particular kind of "class" represented.
    +     *
    +     * @param \ReflectionClass $reflector
    +     *
    +     * @return string
    +     */
    +    protected function getKindLabel(\ReflectionClass $reflector)
    +    {
    +        if ($reflector->isInterface()) {
    +            return 'Interface Methods';
    +        } elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
    +            return 'Trait Methods';
    +        } else {
    +            return 'Class Methods';
    +        }
    +    }
    +
    +    /**
    +     * Get output style for the given method's visibility.
    +     *
    +     * @param \ReflectionMethod $method
    +     *
    +     * @return string
    +     */
    +    private function getVisibilityStyle(\ReflectionMethod $method)
    +    {
    +        if ($method->isPublic()) {
    +            return self::IS_PUBLIC;
    +        } elseif ($method->isProtected()) {
    +            return self::IS_PROTECTED;
    +        } else {
    +            return self::IS_PRIVATE;
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/PropertyEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/PropertyEnumerator.php
    new file mode 100644
    index 0000000..97b9c06
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/PropertyEnumerator.php
    @@ -0,0 +1,161 @@
    +getOption('properties')) {
    +            return;
    +        }
    +
    +        $showAll    = $input->getOption('all');
    +        $properties = $this->prepareProperties($this->getProperties($showAll, $reflector), $target);
    +
    +        if (empty($properties)) {
    +            return;
    +        }
    +
    +        $ret = array();
    +        $ret[$this->getKindLabel($reflector)] = $properties;
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Get defined properties for the given class or object Reflector.
    +     *
    +     * @param bool       $showAll   Include private and protected properties.
    +     * @param \Reflector $reflector
    +     *
    +     * @return array
    +     */
    +    protected function getProperties($showAll, \Reflector $reflector)
    +    {
    +        $properties = array();
    +        foreach ($reflector->getProperties() as $property) {
    +            if ($showAll || $property->isPublic()) {
    +                $properties[$property->getName()] = $property;
    +            }
    +        }
    +
    +        // TODO: this should be natcasesort
    +        ksort($properties);
    +
    +        return $properties;
    +    }
    +
    +    /**
    +     * Prepare formatted property array.
    +     *
    +     * @param array $properties
    +     *
    +     * @return array
    +     */
    +    protected function prepareProperties(array $properties, $target = null)
    +    {
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        foreach ($properties as $name => $property) {
    +            if ($this->showItem($name)) {
    +                $fname = '$' . $name;
    +                $ret[$fname] = array(
    +                    'name'  => $fname,
    +                    'style' => $this->getVisibilityStyle($property),
    +                    'value' => $this->presentValue($property, $target),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Get a label for the particular kind of "class" represented.
    +     *
    +     * @param \ReflectionClass $reflector
    +     *
    +     * @return string
    +     */
    +    protected function getKindLabel(\ReflectionClass $reflector)
    +    {
    +        if ($reflector->isInterface()) {
    +            return 'Interface Properties';
    +        } elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
    +            return 'Trait Properties';
    +        } else {
    +            return 'Class Properties';
    +        }
    +    }
    +
    +    /**
    +     * Get output style for the given property's visibility.
    +     *
    +     * @param \ReflectionProperty $property
    +     *
    +     * @return string
    +     */
    +    private function getVisibilityStyle(\ReflectionProperty $property)
    +    {
    +        if ($property->isPublic()) {
    +            return self::IS_PUBLIC;
    +        } elseif ($property->isProtected()) {
    +            return self::IS_PROTECTED;
    +        } else {
    +            return self::IS_PRIVATE;
    +        }
    +    }
    +
    +    /**
    +     * Present the $target's current value for a reflection property.
    +     *
    +     * @param \ReflectionProperty $property
    +     * @param mixed               $target
    +     *
    +     * @return string
    +     */
    +    protected function presentValue(\ReflectionProperty $property, $target)
    +    {
    +        if (!is_object($target)) {
    +            // TODO: figure out if there's a way to return defaults when target
    +            // is a class/interface/trait rather than an object.
    +            return '';
    +        }
    +
    +        $property->setAccessible(true);
    +        $value = $property->getValue($target);
    +
    +        return $this->presentRef($value);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/TraitEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/TraitEnumerator.php
    new file mode 100644
    index 0000000..d77acce
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/TraitEnumerator.php
    @@ -0,0 +1,85 @@
    +getOption('traits')) {
    +            return;
    +        }
    +
    +        $traits = $this->prepareTraits(get_declared_traits());
    +
    +        if (empty($traits)) {
    +            return;
    +        }
    +
    +        return array(
    +            'Traits' => $traits,
    +        );
    +    }
    +
    +    /**
    +     * Prepare formatted trait array.
    +     *
    +     * @param array $traits
    +     *
    +     * @return array
    +     */
    +    protected function prepareTraits(array $traits)
    +    {
    +        natcasesort($traits);
    +
    +        // My kingdom for a generator.
    +        $ret = array();
    +
    +        foreach ($traits as $name) {
    +            if ($this->showItem($name)) {
    +                $ret[$name] = array(
    +                    'name'  => $name,
    +                    'style' => self::IS_CLASS,
    +                    'value' => $this->presentSignature($name),
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ListCommand/VariableEnumerator.php b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/VariableEnumerator.php
    new file mode 100644
    index 0000000..be61564
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ListCommand/VariableEnumerator.php
    @@ -0,0 +1,129 @@
    +context = $context;
    +        parent::__construct($presenter);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    +    {
    +        // only list variables when no Reflector is present.
    +        if ($reflector !== null || $target !== null) {
    +            return;
    +        }
    +
    +        // only list variables if we are specifically asked
    +        if (!$input->getOption('vars')) {
    +            return;
    +        }
    +
    +        $showAll   = $input->getOption('all');
    +        $variables = $this->prepareVariables($this->getVariables($showAll));
    +
    +        if (empty($variables)) {
    +            return;
    +        }
    +
    +        return array(
    +            'Variables' => $variables,
    +        );
    +    }
    +
    +    /**
    +     * Get scope variables.
    +     *
    +     * @param bool $showAll Include special variables (e.g. $_).
    +     *
    +     * @return array
    +     */
    +    protected function getVariables($showAll)
    +    {
    +        $scopeVars = $this->context->getAll();
    +        uksort($scopeVars, function ($a, $b) {
    +            if ($a === '_e') {
    +                return 1;
    +            } elseif ($b === '_e') {
    +                return -1;
    +            } elseif ($a === '_') {
    +                return 1;
    +            } elseif ($b === '_') {
    +                return -1;
    +            } else {
    +                // TODO: this should be natcasesort
    +                return strcasecmp($a, $b);
    +            }
    +        });
    +
    +        $ret = array();
    +        foreach ($scopeVars as $name => $val) {
    +            if (!$showAll && in_array($name, self::$specialVars)) {
    +                continue;
    +            }
    +
    +            $ret[$name] = $val;
    +        }
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Prepare formatted variable array.
    +     *
    +     * @param array $variables
    +     *
    +     * @return array
    +     */
    +    protected function prepareVariables(array $variables)
    +    {
    +        // My kingdom for a generator.
    +        $ret = array();
    +        foreach ($variables as $name => $val) {
    +            if ($this->showItem($name)) {
    +                $fname = '$' . $name;
    +                $ret[$fname] = array(
    +                    'name'  => $fname,
    +                    'style' => in_array($name, self::$specialVars) ? self::IS_PRIVATE : self::IS_PUBLIC,
    +                    'value' => $this->presentRef($val), // TODO: add types to variable signatures
    +                );
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ParseCommand.php b/core/vendor/psy/psysh/src/Psy/Command/ParseCommand.php
    new file mode 100644
    index 0000000..53b87c4
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ParseCommand.php
    @@ -0,0 +1,159 @@
    +parserFactory = new ParserFactory();
    +        $this->parsers = array();
    +
    +        parent::__construct($name);
    +    }
    +
    +    /**
    +     * PresenterAware interface.
    +     *
    +     * @param Presenter $presenter
    +     */
    +    public function setPresenter(Presenter $presenter)
    +    {
    +        $this->presenter = clone $presenter;
    +        $this->presenter->addCasters(array(
    +            'PhpParser\Node' => function (Node $node, array $a) {
    +                $a = array(
    +                    Caster::PREFIX_VIRTUAL . 'type'       => $node->getType(),
    +                    Caster::PREFIX_VIRTUAL . 'attributes' => $node->getAttributes(),
    +                );
    +
    +                foreach ($node->getSubNodeNames() as $name) {
    +                    $a[Caster::PREFIX_VIRTUAL . $name] = $node->$name;
    +                }
    +
    +                return $a;
    +            },
    +        ));
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function configure()
    +    {
    +        $definition = array(
    +            new InputArgument('code', InputArgument::REQUIRED, 'PHP code to parse.'),
    +            new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse', 10),
    +        );
    +
    +        if ($this->parserFactory->hasKindsSupport()) {
    +            $msg = 'One of PhpParser\\ParserFactory constants: '
    +                . implode(', ', ParserFactory::getPossibleKinds())
    +                . " (default is based on current interpreter's version)";
    +            $defaultKind = $this->parserFactory->getDefaultKind();
    +
    +            $definition[] = new InputOption('kind', '', InputOption::VALUE_REQUIRED, $msg, $defaultKind);
    +        }
    +
    +        $this
    +            ->setName('parse')
    +            ->setDefinition($definition)
    +            ->setDescription('Parse PHP code and show the abstract syntax tree.')
    +            ->setHelp(
    +                <<<'HELP'
    +Parse PHP code and show the abstract syntax tree.
    +
    +This command is used in the development of PsySH. Given a string of PHP code,
    +it pretty-prints the PHP Parser parse tree.
    +
    +See https://github.com/nikic/PHP-Parser
    +
    +It prolly won't be super useful for most of you, but it's here if you want to play.
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $code = $input->getArgument('code');
    +        if (strpos('getOption('kind');
    +        $depth      = $input->getOption('depth');
    +        $nodes      = $this->parse($this->getParser($parserKind), $code);
    +        $output->page($this->presenter->present($nodes, $depth));
    +    }
    +
    +    /**
    +     * Lex and parse a string of code into statements.
    +     *
    +     * @param Parser $parser
    +     * @param string $code
    +     *
    +     * @return array Statements
    +     */
    +    private function parse(Parser $parser, $code)
    +    {
    +        try {
    +            return $parser->parse($code);
    +        } catch (\PhpParser\Error $e) {
    +            if (strpos($e->getMessage(), 'unexpected EOF') === false) {
    +                throw $e;
    +            }
    +
    +            // If we got an unexpected EOF, let's try it again with a semicolon.
    +            return $parser->parse($code . ';');
    +        }
    +    }
    +
    +    /**
    +     * Get (or create) the Parser instance.
    +     *
    +     * @param string|null $kind One of Psy\ParserFactory constants (only for PHP parser 2.0 and above).
    +     *
    +     * @return Parser
    +     */
    +    private function getParser($kind = null)
    +    {
    +        if (!array_key_exists($kind, $this->parsers)) {
    +            $this->parsers[$kind] = $this->parserFactory->createParser($kind);
    +        }
    +
    +        return $this->parsers[$kind];
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/PsyVersionCommand.php b/core/vendor/psy/psysh/src/Psy/Command/PsyVersionCommand.php
    new file mode 100644
    index 0000000..ff84c3b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/PsyVersionCommand.php
    @@ -0,0 +1,41 @@
    +setName('version')
    +            ->setDefinition(array())
    +            ->setDescription('Show Psy Shell version.')
    +            ->setHelp('Show Psy Shell version.');
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $output->writeln($this->getApplication()->getVersion());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ReflectingCommand.php b/core/vendor/psy/psysh/src/Psy/Command/ReflectingCommand.php
    new file mode 100644
    index 0000000..197cf35
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ReflectingCommand.php
    @@ -0,0 +1,172 @@
    +)(\w+)$/';
    +    const INSTANCE_STATIC = '/^\$(\w+)::\$(\w+)$/';
    +
    +    /**
    +     * Context instance (for ContextAware interface).
    +     *
    +     * @var Context
    +     */
    +    protected $context;
    +
    +    /**
    +     * ContextAware interface.
    +     *
    +     * @param Context $context
    +     */
    +    public function setContext(Context $context)
    +    {
    +        $this->context = $context;
    +    }
    +
    +    /**
    +     * Get the target for a value.
    +     *
    +     * @throws \InvalidArgumentException when the value specified can't be resolved.
    +     *
    +     * @param string $valueName Function, class, variable, constant, method or property name.
    +     * @param bool   $classOnly True if the name should only refer to a class, function or instance
    +     *
    +     * @return array (class or instance name, member name, kind)
    +     */
    +    protected function getTarget($valueName, $classOnly = false)
    +    {
    +        $valueName = trim($valueName);
    +        $matches   = array();
    +        switch (true) {
    +            case preg_match(self::CLASS_OR_FUNC, $valueName, $matches):
    +                return array($this->resolveName($matches[0], true), null, 0);
    +
    +            case preg_match(self::INSTANCE, $valueName, $matches):
    +                return array($this->resolveInstance($matches[1]), null, 0);
    +
    +            case !$classOnly && preg_match(self::CLASS_MEMBER, $valueName, $matches):
    +                return array($this->resolveName($matches[1]), $matches[2], Mirror::CONSTANT | Mirror::METHOD);
    +
    +            case !$classOnly && preg_match(self::CLASS_STATIC, $valueName, $matches):
    +                return array($this->resolveName($matches[1]), $matches[2], Mirror::STATIC_PROPERTY | Mirror::PROPERTY);
    +
    +            case !$classOnly && preg_match(self::INSTANCE_MEMBER, $valueName, $matches):
    +                if ($matches[2] === '->') {
    +                    $kind = Mirror::METHOD | Mirror::PROPERTY;
    +                } else {
    +                    $kind = Mirror::CONSTANT | Mirror::METHOD;
    +                }
    +
    +                return array($this->resolveInstance($matches[1]), $matches[3], $kind);
    +
    +            case !$classOnly && preg_match(self::INSTANCE_STATIC, $valueName, $matches):
    +                return array($this->resolveInstance($matches[1]), $matches[2], Mirror::STATIC_PROPERTY);
    +
    +            default:
    +                throw new RuntimeException('Unknown target: ' . $valueName);
    +        }
    +    }
    +
    +    /**
    +     * Resolve a class or function name (with the current shell namespace).
    +     *
    +     * @param string $name
    +     * @param bool   $includeFunctions (default: false)
    +     *
    +     * @return string
    +     */
    +    protected function resolveName($name, $includeFunctions = false)
    +    {
    +        if (substr($name, 0, 1) === '\\') {
    +            return $name;
    +        }
    +
    +        if ($namespace = $this->getApplication()->getNamespace()) {
    +            $fullName = $namespace . '\\' . $name;
    +
    +            if (class_exists($fullName) || interface_exists($fullName) || ($includeFunctions && function_exists($fullName))) {
    +                return $fullName;
    +            }
    +        }
    +
    +        return $name;
    +    }
    +
    +    /**
    +     * Get a Reflector and documentation for a function, class or instance, constant, method or property.
    +     *
    +     * @param string $valueName Function, class, variable, constant, method or property name.
    +     * @param bool   $classOnly True if the name should only refer to a class, function or instance
    +     *
    +     * @return array (value, Reflector)
    +     */
    +    protected function getTargetAndReflector($valueName, $classOnly = false)
    +    {
    +        list($value, $member, $kind) = $this->getTarget($valueName, $classOnly);
    +
    +        return array($value, Mirror::get($value, $member, $kind));
    +    }
    +
    +    /**
    +     * Return a variable instance from the current scope.
    +     *
    +     * @throws \InvalidArgumentException when the requested variable does not exist in the current scope.
    +     *
    +     * @param string $name
    +     *
    +     * @return mixed Variable instance.
    +     */
    +    protected function resolveInstance($name)
    +    {
    +        $value = $this->getScopeVariable($name);
    +        if (!is_object($value)) {
    +            throw new RuntimeException('Unable to inspect a non-object');
    +        }
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * Get a variable from the current shell scope.
    +     *
    +     * @param string $name
    +     *
    +     * @return mixed
    +     */
    +    protected function getScopeVariable($name)
    +    {
    +        return $this->context->get($name);
    +    }
    +
    +    /**
    +     * Get all scope variables from the current shell scope.
    +     *
    +     * @return array
    +     */
    +    protected function getScopeVariables()
    +    {
    +        return $this->context->getAll();
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ShowCommand.php b/core/vendor/psy/psysh/src/Psy/Command/ShowCommand.php
    new file mode 100644
    index 0000000..3a85d67
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ShowCommand.php
    @@ -0,0 +1,76 @@
    +colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;
    +
    +        return parent::__construct();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function configure()
    +    {
    +        $this
    +            ->setName('show')
    +            ->setDefinition(array(
    +                new InputArgument('value', InputArgument::REQUIRED, 'Function, class, instance, constant, method or property to show.'),
    +            ))
    +            ->setDescription('Show the code for an object, class, constant, method or property.')
    +            ->setHelp(
    +                <<>>> show \$myObject
    +>>> show Psy\Shell::debug
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        list($value, $reflector) = $this->getTargetAndReflector($input->getArgument('value'));
    +
    +        try {
    +            $output->page(CodeFormatter::format($reflector, $this->colorMode), ShellOutput::OUTPUT_RAW);
    +        } catch (RuntimeException $e) {
    +            $output->writeln(SignatureFormatter::format($reflector));
    +            throw $e;
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/ThrowUpCommand.php b/core/vendor/psy/psysh/src/Psy/Command/ThrowUpCommand.php
    new file mode 100644
    index 0000000..5bfc9bc
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/ThrowUpCommand.php
    @@ -0,0 +1,87 @@
    +context = $context;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function configure()
    +    {
    +        $this
    +            ->setName('throw-up')
    +            ->setDefinition(array(
    +                new InputArgument('exception', InputArgument::OPTIONAL, 'Exception to throw'),
    +            ))
    +            ->setDescription('Throw an exception out of the Psy Shell.')
    +            ->setHelp(
    +                <<<'HELP'
    +Throws an exception out of the current the Psy Shell instance.
    +
    +By default it throws the most recent exception.
    +
    +e.g.
    +>>> throw-up
    +>>> throw-up $e
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     *
    +     * @throws InvalidArgumentException if there is no exception to throw.
    +     * @throws ThrowUpException         because what else do you expect it to do?
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        if ($name = $input->getArgument('exception')) {
    +            $orig = $this->context->get(preg_replace('/^\$/', '', $name));
    +        } else {
    +            $orig = $this->context->getLastException();
    +        }
    +
    +        if (!$orig instanceof \Exception) {
    +            throw new \InvalidArgumentException('throw-up can only throw Exceptions');
    +        }
    +
    +        throw new ThrowUpException($orig);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/TraceCommand.php b/core/vendor/psy/psysh/src/Psy/Command/TraceCommand.php
    new file mode 100644
    index 0000000..e54a5f4
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/TraceCommand.php
    @@ -0,0 +1,137 @@
    +setName('trace')
    +            ->setDefinition(array(
    +                new InputOption('include-psy', 'p', InputOption::VALUE_NONE,     'Include Psy in the call stack.'),
    +                new InputOption('num',         'n', InputOption::VALUE_REQUIRED, 'Only include NUM lines.'),
    +            ))
    +            ->setDescription('Show the current call stack.')
    +            ->setHelp(
    +                <<<'HELP'
    +Show the current call stack.
    +
    +Optionally, include PsySH in the call stack by passing the --include-psy option.
    +
    +e.g.
    +> trace -n10
    +> trace --include-psy
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $trace = $this->getBacktrace(new \Exception(), $input->getOption('num'), $input->getOption('include-psy'));
    +        $output->page($trace, ShellOutput::NUMBER_LINES);
    +    }
    +
    +    /**
    +     * Get a backtrace for an exception.
    +     *
    +     * Optionally limit the number of rows to include with $count, and exclude
    +     * Psy from the trace.
    +     *
    +     * @param \Exception $e          The exception with a backtrace.
    +     * @param int        $count      (default: PHP_INT_MAX)
    +     * @param bool       $includePsy (default: true)
    +     *
    +     * @return array Formatted stacktrace lines.
    +     */
    +    protected function getBacktrace(\Exception $e, $count = null, $includePsy = true)
    +    {
    +        if ($cwd = getcwd()) {
    +            $cwd = rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    +        }
    +
    +        if ($count === null) {
    +            $count = PHP_INT_MAX;
    +        }
    +
    +        $lines = array();
    +
    +        $trace = $e->getTrace();
    +        array_unshift($trace, array(
    +            'function' => '',
    +            'file'     => $e->getFile() !== null ? $e->getFile() : 'n/a',
    +            'line'     => $e->getLine() !== null ? $e->getLine() : 'n/a',
    +            'args'     => array(),
    +        ));
    +
    +        if (!$includePsy) {
    +            for ($i = count($trace) - 1; $i >= 0; $i--) {
    +                $thing = isset($trace[$i]['class']) ? $trace[$i]['class'] : $trace[$i]['function'];
    +                if (preg_match('/\\\\?Psy\\\\/', $thing)) {
    +                    $trace = array_slice($trace, $i + 1);
    +                    break;
    +                }
    +            }
    +        }
    +
    +        for ($i = 0, $count = min($count, count($trace)); $i < $count; $i++) {
    +            $class    = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
    +            $type     = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
    +            $function = $trace[$i]['function'];
    +            $file     = isset($trace[$i]['file']) ? $this->replaceCwd($cwd, $trace[$i]['file']) : 'n/a';
    +            $line     = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
    +
    +            $lines[] = sprintf(
    +                ' %s%s%s() at %s:%s',
    +                OutputFormatter::escape($class),
    +                OutputFormatter::escape($type),
    +                OutputFormatter::escape($function),
    +                OutputFormatter::escape($file),
    +                OutputFormatter::escape($line)
    +            );
    +        }
    +
    +        return $lines;
    +    }
    +
    +    /**
    +     * Replace the given directory from the start of a filepath.
    +     *
    +     * @param string $cwd
    +     * @param string $file
    +     *
    +     * @return string
    +     */
    +    private function replaceCwd($cwd, $file)
    +    {
    +        if ($cwd === false) {
    +            return $file;
    +        } else {
    +            return preg_replace('/^' . preg_quote($cwd, '/') . '/', '', $file);
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/WhereamiCommand.php b/core/vendor/psy/psysh/src/Psy/Command/WhereamiCommand.php
    new file mode 100644
    index 0000000..04598da
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/WhereamiCommand.php
    @@ -0,0 +1,123 @@
    +colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;
    +
    +        if (version_compare(PHP_VERSION, '5.3.6', '>=')) {
    +            $this->backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
    +        } else {
    +            $this->backtrace = debug_backtrace();
    +        }
    +
    +        return parent::__construct();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function configure()
    +    {
    +        $this
    +            ->setName('whereami')
    +            ->setDefinition(array(
    +                new InputOption('num', 'n', InputOption::VALUE_OPTIONAL, 'Number of lines before and after.', '5'),
    +            ))
    +            ->setDescription('Show where you are in the code.')
    +            ->setHelp(
    +                <<<'HELP'
    +Show where you are in the code.
    +
    +Optionally, include how many lines before and after you want to display.
    +
    +e.g.
    +> whereami 
    +> whereami -n10
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * Obtains the correct trace in the full backtrace.
    +     *
    +     * @return array
    +     */
    +    protected function trace()
    +    {
    +        foreach ($this->backtrace as $i => $backtrace) {
    +            if (!isset($backtrace['class'], $backtrace['function'])) {
    +                continue;
    +            }
    +            $correctClass = $backtrace['class'] === 'Psy\Shell';
    +            $correctFunction = $backtrace['function'] === 'debug';
    +            if ($correctClass && $correctFunction) {
    +                return $backtrace;
    +            }
    +        }
    +
    +        return end($this->backtrace);
    +    }
    +
    +    /**
    +     * Determine the file and line based on the specific backtrace.
    +     *
    +     * @return array
    +     */
    +    protected function fileInfo()
    +    {
    +        $backtrace = $this->trace();
    +        if (preg_match('/eval\(/', $backtrace['file'])) {
    +            preg_match_all('/([^\(]+)\((\d+)/', $backtrace['file'], $matches);
    +            $file = $matches[1][0];
    +            $line = (int) $matches[2][0];
    +        } else {
    +            $file = $backtrace['file'];
    +            $line = $backtrace['line'];
    +        }
    +
    +        return compact('file', 'line');
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $info = $this->fileInfo();
    +        $num = $input->getOption('num');
    +        $factory = new ConsoleColorFactory($this->colorMode);
    +        $colors = $factory->getConsoleColor();
    +        $highlighter = new Highlighter($colors);
    +        $contents = file_get_contents($info['file']);
    +        $output->page($highlighter->getCodeSnippet($contents, $info['line'], $num, $num), ShellOutput::OUTPUT_RAW);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Command/WtfCommand.php b/core/vendor/psy/psysh/src/Psy/Command/WtfCommand.php
    new file mode 100644
    index 0000000..afcbc6e
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Command/WtfCommand.php
    @@ -0,0 +1,111 @@
    +context = $context;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function configure()
    +    {
    +        $this
    +            ->setName('wtf')
    +            ->setAliases(array('last-exception', 'wtf?'))
    +            ->setDefinition(array(
    +                new InputArgument('incredulity', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Number of lines to show'),
    +                new InputOption('verbose', 'v',  InputOption::VALUE_NONE, 'Show entire backtrace.'),
    +            ))
    +            ->setDescription('Show the backtrace of the most recent exception.')
    +            ->setHelp(
    +                <<<'HELP'
    +Shows a few lines of the backtrace of the most recent exception.
    +
    +If you want to see more lines, add more question marks or exclamation marks:
    +
    +e.g.
    +>>> wtf ?
    +>>> wtf ?!???!?!?
    +
    +To see the entire backtrace, pass the -v/--verbose flag:
    +
    +e.g.
    +>>> wtf -v
    +HELP
    +            );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     *
    +     * --verbose is not hidden for this option :)
    +     *
    +     * @return array
    +     */
    +    protected function getHiddenOptions()
    +    {
    +        $options = parent::getHiddenOptions();
    +        unset($options[array_search('verbose', $options)]);
    +
    +        return $options;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function execute(InputInterface $input, OutputInterface $output)
    +    {
    +        $incredulity = implode('', $input->getArgument('incredulity'));
    +        if (strlen(preg_replace('/[\\?!]/', '', $incredulity))) {
    +            throw new \InvalidArgumentException('Incredulity must include only "?" and "!".');
    +        }
    +
    +        $exception = $this->context->getLastException();
    +        $count     = $input->getOption('verbose') ? PHP_INT_MAX : pow(2, max(0, (strlen($incredulity) - 1)));
    +        $trace     = $this->getBacktrace($exception, $count);
    +
    +        $shell = $this->getApplication();
    +        $output->page(function ($output) use ($exception, $trace, $shell) {
    +            $shell->renderException($exception, $output);
    +            $output->writeln('--');
    +            $output->write($trace, true, ShellOutput::NUMBER_LINES);
    +        });
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Compiler.php b/core/vendor/psy/psysh/src/Psy/Compiler.php
    new file mode 100644
    index 0000000..a9b7745
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Compiler.php
    @@ -0,0 +1,157 @@
    +version = Shell::VERSION;
    +
    +        $phar = new \Phar($pharFile, 0, 'psysh.phar');
    +        $phar->setSignatureAlgorithm(\Phar::SHA1);
    +
    +        $phar->startBuffering();
    +
    +        $finder = Finder::create()
    +            ->files()
    +            ->ignoreVCS(true)
    +            ->name('*.php')
    +            ->notName('Compiler.php')
    +            ->notName('Autoloader.php')
    +            ->in(__DIR__ . '/..');
    +
    +        foreach ($finder as $file) {
    +            $this->addFile($phar, $file);
    +        }
    +
    +        $finder = Finder::create()
    +            ->files()
    +            ->ignoreVCS(true)
    +            ->name('*.php')
    +            ->exclude('Tests')
    +            ->in(__DIR__ . '/../../build-vendor');
    +
    +        foreach ($finder as $file) {
    +            $this->addFile($phar, $file);
    +        }
    +
    +        // Stubs
    +        $phar->setStub($this->getStub());
    +
    +        $phar->stopBuffering();
    +
    +        unset($phar);
    +    }
    +
    +    /**
    +     * Add a file to the psysh Phar.
    +     *
    +     * @param Phar        $phar
    +     * @param SplFileInfo $file
    +     * @param bool        $strip (default: true)
    +     */
    +    private function addFile($phar, $file, $strip = true)
    +    {
    +        $path = str_replace(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR, '', $file->getRealPath());
    +
    +        $content = file_get_contents($file);
    +        if ($strip) {
    +            $content = $this->stripWhitespace($content);
    +        } elseif ('LICENSE' === basename($file)) {
    +            $content = "\n" . $content . "\n";
    +        }
    +
    +        $phar->addFromString($path, $content);
    +    }
    +
    +    /**
    +     * Removes whitespace from a PHP source string while preserving line numbers.
    +     *
    +     * @param string $source A PHP string
    +     *
    +     * @return string The PHP string with the whitespace removed
    +     */
    +    private function stripWhitespace($source)
    +    {
    +        if (!function_exists('token_get_all')) {
    +            return $source;
    +        }
    +
    +        $output = '';
    +        foreach (token_get_all($source) as $token) {
    +            if (is_string($token)) {
    +                $output .= $token;
    +            } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
    +                $output .= str_repeat("\n", substr_count($token[1], "\n"));
    +            } elseif (T_WHITESPACE === $token[0]) {
    +                // reduce wide spaces
    +                $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
    +                // normalize newlines to \n
    +                $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
    +                // trim leading spaces
    +                $whitespace = preg_replace('{\n +}', "\n", $whitespace);
    +                $output .= $whitespace;
    +            } else {
    +                $output .= $token[1];
    +            }
    +        }
    +
    +        return $output;
    +    }
    +
    +    private static function getStubLicense()
    +    {
    +        $license = file_get_contents(__DIR__ . '/../../LICENSE');
    +        $license = str_replace('The MIT License (MIT)', '', $license);
    +        $license = str_replace("\n", "\n * ", trim($license));
    +
    +        return $license;
    +    }
    +
    +    const STUB_AUTOLOAD = <<<'EOS'
    +    Phar::mapPhar('psysh.phar');
    +    require 'phar://psysh.phar/build-vendor/autoload.php';
    +EOS;
    +
    +    /**
    +     * Get a Phar stub for psysh.
    +     *
    +     * This is basically the psysh bin, with the autoload require statements swapped out.
    +     *
    +     * @return string
    +     */
    +    private function getStub()
    +    {
    +        $content = file_get_contents(__DIR__ . '/../../bin/psysh');
    +        $content = preg_replace('{/\* <<<.*?>>> \*/}sm', self::STUB_AUTOLOAD, $content);
    +        $content = preg_replace('/\\(c\\) .*?with this source code./sm', self::getStubLicense(), $content);
    +
    +        $content .= '__HALT_COMPILER();';
    +
    +        return $content;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/ConfigPaths.php b/core/vendor/psy/psysh/src/Psy/ConfigPaths.php
    new file mode 100644
    index 0000000..578a25b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/ConfigPaths.php
    @@ -0,0 +1,183 @@
    +getConfigDirs());
    +    }
    +
    +    /**
    +     * Get potential home config directory paths.
    +     *
    +     * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and the
    +     * XDG Base Directory home config directory:
    +     *
    +     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
    +     *
    +     * @return string[]
    +     */
    +    public static function getHomeConfigDirs()
    +    {
    +        $xdg = new Xdg();
    +
    +        return self::getDirNames(array($xdg->getHomeConfigDir()));
    +    }
    +
    +    /**
    +     * Get the current home config directory.
    +     *
    +     * Returns the highest precedence home config directory which actually
    +     * exists. If none of them exists, returns the highest precedence home
    +     * config directory (`%APPDATA%/PsySH` on Windows, `~/.config/psysh`
    +     * everywhere else).
    +     *
    +     * @see self::getHomeConfigDirs
    +     *
    +     * @return string
    +     */
    +    public static function getCurrentConfigDir()
    +    {
    +        $configDirs = self::getHomeConfigDirs();
    +        foreach ($configDirs as $configDir) {
    +            if (@is_dir($configDir)) {
    +                return $configDir;
    +            }
    +        }
    +
    +        return $configDirs[0];
    +    }
    +
    +    /**
    +     * Find real config files in config directories.
    +     *
    +     * @param string[] $names     Config file names
    +     * @param string   $configDir Optionally use a specific config directory
    +     *
    +     * @return string[]
    +     */
    +    public static function getConfigFiles(array $names, $configDir = null)
    +    {
    +        $dirs = ($configDir === null) ? self::getConfigDirs() : array($configDir);
    +
    +        return self::getRealFiles($dirs, $names);
    +    }
    +
    +    /**
    +     * Get potential data directory paths.
    +     *
    +     * If a `dataDir` option was explicitly set, returns an array containing
    +     * just that directory.
    +     *
    +     * Otherwise, it returns `~/.psysh` and all XDG Base Directory data directories:
    +     *
    +     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
    +     *
    +     * @return string[]
    +     */
    +    public static function getDataDirs()
    +    {
    +        $xdg = new Xdg();
    +
    +        return self::getDirNames($xdg->getDataDirs());
    +    }
    +
    +    /**
    +     * Find real data files in config directories.
    +     *
    +     * @param string[] $names   Config file names
    +     * @param string   $dataDir Optionally use a specific config directory
    +     *
    +     * @return string[]
    +     */
    +    public static function getDataFiles(array $names, $dataDir = null)
    +    {
    +        $dirs = ($dataDir === null) ? self::getDataDirs() : array($dataDir);
    +
    +        return self::getRealFiles($dirs, $names);
    +    }
    +
    +    /**
    +     * Get a runtime directory.
    +     *
    +     * Defaults to  `/psysh` inside the system's temp dir.
    +     *
    +     * @return string
    +     */
    +    public static function getRuntimeDir()
    +    {
    +        $xdg = new Xdg();
    +
    +        return $xdg->getRuntimeDir(false) . '/psysh';
    +    }
    +
    +    private static function getDirNames(array $baseDirs)
    +    {
    +        $dirs = array_map(function ($dir) {
    +            return strtr($dir, '\\', '/') . '/psysh';
    +        }, $baseDirs);
    +
    +        // Add ~/.psysh
    +        if ($home = getenv('HOME')) {
    +            $dirs[] = strtr($home, '\\', '/') . '/.psysh';
    +        }
    +
    +        // Add some Windows specific ones :)
    +        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
    +            if ($appData = getenv('APPDATA')) {
    +                // AppData gets preference
    +                array_unshift($dirs, strtr($appData, '\\', '/') . '/PsySH');
    +            }
    +
    +            $dir = strtr(getenv('HOMEDRIVE') . '/' . getenv('HOMEPATH'), '\\', '/') . '/.psysh';
    +            if (!in_array($dir, $dirs)) {
    +                $dirs[] = $dir;
    +            }
    +        }
    +
    +        return $dirs;
    +    }
    +
    +    private static function getRealFiles(array $dirNames, array $fileNames)
    +    {
    +        $files = array();
    +        foreach ($dirNames as $dir) {
    +            foreach ($fileNames as $name) {
    +                $file = $dir . '/' . $name;
    +                if (@is_file($file)) {
    +                    $files[] = $file;
    +                }
    +            }
    +        }
    +
    +        return $files;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Configuration.php b/core/vendor/psy/psysh/src/Psy/Configuration.php
    new file mode 100644
    index 0000000..926472a
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Configuration.php
    @@ -0,0 +1,1074 @@
    +setColorMode(self::COLOR_MODE_AUTO);
    +
    +        // explicit configFile option
    +        if (isset($config['configFile'])) {
    +            $this->configFile = $config['configFile'];
    +        } elseif ($configFile = getenv('PSYSH_CONFIG')) {
    +            $this->configFile = $configFile;
    +        }
    +
    +        // legacy baseDir option
    +        if (isset($config['baseDir'])) {
    +            $msg = "The 'baseDir' configuration option is deprecated. " .
    +                "Please specify 'configDir' and 'dataDir' options instead.";
    +            throw new DeprecatedException($msg);
    +        }
    +
    +        unset($config['configFile'], $config['baseDir']);
    +
    +        // go go gadget, config!
    +        $this->loadConfig($config);
    +        $this->init();
    +    }
    +
    +    /**
    +     * Initialize the configuration.
    +     *
    +     * This checks for the presence of Readline and Pcntl extensions.
    +     *
    +     * If a config file is available, it will be loaded and merged with the current config.
    +     *
    +     * If no custom config file was specified and a local project config file
    +     * is available, it will be loaded and merged with the current config.
    +     */
    +    public function init()
    +    {
    +        // feature detection
    +        $this->hasReadline = function_exists('readline');
    +        $this->hasPcntl    = function_exists('pcntl_signal') && function_exists('posix_getpid');
    +
    +        if ($configFile = $this->getConfigFile()) {
    +            $this->loadConfigFile($configFile);
    +        }
    +
    +        if (!$this->configFile && $localConfig = $this->getLocalConfigFile()) {
    +            $this->loadConfigFile($localConfig);
    +        }
    +    }
    +
    +    /**
    +     * Get the current PsySH config file.
    +     *
    +     * If a `configFile` option was passed to the Configuration constructor,
    +     * this file will be returned. If not, all possible config directories will
    +     * be searched, and the first `config.php` or `rc.php` file which exists
    +     * will be returned.
    +     *
    +     * If you're trying to decide where to put your config file, pick
    +     *
    +     *     ~/.config/psysh/config.php
    +     *
    +     * @return string
    +     */
    +    public function getConfigFile()
    +    {
    +        if (isset($this->configFile)) {
    +            return $this->configFile;
    +        }
    +
    +        $files = ConfigPaths::getConfigFiles(array('config.php', 'rc.php'), $this->configDir);
    +
    +        if (!empty($files)) {
    +            if ($this->warnOnMultipleConfigs && count($files) > 1) {
    +                $msg = sprintf('Multiple configuration files found: %s. Using %s', implode($files, ', '), $files[0]);
    +                trigger_error($msg, E_USER_NOTICE);
    +            }
    +
    +            return $files[0];
    +        }
    +    }
    +
    +    /**
    +     * Get the local PsySH config file.
    +     *
    +     * Searches for a project specific config file `.psysh.php` in the current
    +     * working directory.
    +     *
    +     * @return string
    +     */
    +    public function getLocalConfigFile()
    +    {
    +        $localConfig = getenv('PWD') . '/.psysh.php';
    +
    +        if (@is_file($localConfig)) {
    +            return $localConfig;
    +        }
    +    }
    +
    +    /**
    +     * Load configuration values from an array of options.
    +     *
    +     * @param array $options
    +     */
    +    public function loadConfig(array $options)
    +    {
    +        foreach (self::$AVAILABLE_OPTIONS as $option) {
    +            if (isset($options[$option])) {
    +                $method = 'set' . ucfirst($option);
    +                $this->$method($options[$option]);
    +            }
    +        }
    +
    +        foreach (array('commands', 'tabCompletionMatchers', 'casters') as $option) {
    +            if (isset($options[$option])) {
    +                $method = 'add' . ucfirst($option);
    +                $this->$method($options[$option]);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Load a configuration file (default: `$HOME/.config/psysh/config.php`).
    +     *
    +     * This configuration instance will be available to the config file as $config.
    +     * The config file may directly manipulate the configuration, or may return
    +     * an array of options which will be merged with the current configuration.
    +     *
    +     * @throws \InvalidArgumentException if the config file returns a non-array result.
    +     *
    +     * @param string $file
    +     */
    +    public function loadConfigFile($file)
    +    {
    +        $__psysh_config_file__ = $file;
    +        $load = function ($config) use ($__psysh_config_file__) {
    +            $result = require $__psysh_config_file__;
    +            if ($result !== 1) {
    +                return $result;
    +            }
    +        };
    +        $result = $load($this);
    +
    +        if (!empty($result)) {
    +            if (is_array($result)) {
    +                $this->loadConfig($result);
    +            } else {
    +                throw new \InvalidArgumentException('Psy Shell configuration must return an array of options');
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Set files to be included by default at the start of each shell session.
    +     *
    +     * @param array $includes
    +     */
    +    public function setDefaultIncludes(array $includes = array())
    +    {
    +        $this->defaultIncludes = $includes;
    +    }
    +
    +    /**
    +     * Get files to be included by default at the start of each shell session.
    +     *
    +     * @return array
    +     */
    +    public function getDefaultIncludes()
    +    {
    +        return $this->defaultIncludes ?: array();
    +    }
    +
    +    /**
    +     * Set the shell's config directory location.
    +     *
    +     * @param string $dir
    +     */
    +    public function setConfigDir($dir)
    +    {
    +        $this->configDir = (string) $dir;
    +    }
    +
    +    /**
    +     * Get the current configuration directory, if any is explicitly set.
    +     *
    +     * @return string
    +     */
    +    public function getConfigDir()
    +    {
    +        return $this->configDir;
    +    }
    +
    +    /**
    +     * Set the shell's data directory location.
    +     *
    +     * @param string $dir
    +     */
    +    public function setDataDir($dir)
    +    {
    +        $this->dataDir = (string) $dir;
    +    }
    +
    +    /**
    +     * Get the current data directory, if any is explicitly set.
    +     *
    +     * @return string
    +     */
    +    public function getDataDir()
    +    {
    +        return $this->dataDir;
    +    }
    +
    +    /**
    +     * Set the shell's temporary directory location.
    +     *
    +     * @param string $dir
    +     */
    +    public function setRuntimeDir($dir)
    +    {
    +        $this->runtimeDir = (string) $dir;
    +    }
    +
    +    /**
    +     * Get the shell's temporary directory location.
    +     *
    +     * Defaults to  `/psysh` inside the system's temp dir unless explicitly
    +     * overridden.
    +     *
    +     * @return string
    +     */
    +    public function getRuntimeDir()
    +    {
    +        if (!isset($this->runtimeDir)) {
    +            $this->runtimeDir = ConfigPaths::getRuntimeDir();
    +        }
    +
    +        if (!is_dir($this->runtimeDir)) {
    +            mkdir($this->runtimeDir, 0700, true);
    +        }
    +
    +        return $this->runtimeDir;
    +    }
    +
    +    /**
    +     * Set the readline history file path.
    +     *
    +     * @param string $file
    +     */
    +    public function setHistoryFile($file)
    +    {
    +        $this->historyFile = (string) $file;
    +    }
    +
    +    /**
    +     * Get the readline history file path.
    +     *
    +     * Defaults to `/history` inside the shell's base config dir unless
    +     * explicitly overridden.
    +     *
    +     * @return string
    +     */
    +    public function getHistoryFile()
    +    {
    +        if (isset($this->historyFile)) {
    +            return $this->historyFile;
    +        }
    +
    +        // Deprecation warning for incorrect psysh_history path.
    +        // TODO: remove this before v0.8.0
    +        $xdg = new Xdg();
    +        $oldHistory = $xdg->getHomeConfigDir() . '/psysh_history';
    +        if (@is_file($oldHistory)) {
    +            $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();
    +            $newHistory = $dir . '/psysh_history';
    +
    +            $msg = sprintf(
    +                "PsySH history file found at '%s'. Please delete it or move it to '%s'.",
    +                strtr($oldHistory, '\\', '/'),
    +                $newHistory
    +            );
    +            trigger_error($msg, E_USER_DEPRECATED);
    +
    +            return $this->historyFile = $oldHistory;
    +        }
    +
    +        $files = ConfigPaths::getConfigFiles(array('psysh_history', 'history'), $this->configDir);
    +
    +        if (!empty($files)) {
    +            if ($this->warnOnMultipleConfigs && count($files) > 1) {
    +                $msg = sprintf('Multiple history files found: %s. Using %s', implode($files, ', '), $files[0]);
    +                trigger_error($msg, E_USER_NOTICE);
    +            }
    +
    +            return $this->historyFile = $files[0];
    +        }
    +
    +        // fallback: create our own history file
    +        $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();
    +        if (!is_dir($dir)) {
    +            mkdir($dir, 0700, true);
    +        }
    +
    +        return $this->historyFile = $dir . '/psysh_history';
    +    }
    +
    +    /**
    +     * Set the readline max history size.
    +     *
    +     * @param int $value
    +     */
    +    public function setHistorySize($value)
    +    {
    +        $this->historySize = (int) $value;
    +    }
    +
    +    /**
    +     * Get the readline max history size.
    +     *
    +     * @return int
    +     */
    +    public function getHistorySize()
    +    {
    +        return $this->historySize;
    +    }
    +
    +    /**
    +     * Sets whether readline erases old duplicate history entries.
    +     *
    +     * @param bool $value
    +     */
    +    public function setEraseDuplicates($value)
    +    {
    +        $this->eraseDuplicates = (bool) $value;
    +    }
    +
    +    /**
    +     * Get whether readline erases old duplicate history entries.
    +     *
    +     * @return bool
    +     */
    +    public function getEraseDuplicates()
    +    {
    +        return $this->eraseDuplicates;
    +    }
    +
    +    /**
    +     * Get a temporary file of type $type for process $pid.
    +     *
    +     * The file will be created inside the current temporary directory.
    +     *
    +     * @see self::getRuntimeDir
    +     *
    +     * @param string $type
    +     * @param int    $pid
    +     *
    +     * @return string Temporary file name
    +     */
    +    public function getTempFile($type, $pid)
    +    {
    +        return tempnam($this->getRuntimeDir(), $type . '_' . $pid . '_');
    +    }
    +
    +    /**
    +     * Get a filename suitable for a FIFO pipe of $type for process $pid.
    +     *
    +     * The pipe will be created inside the current temporary directory.
    +     *
    +     * @param string $type
    +     * @param id     $pid
    +     *
    +     * @return string Pipe name
    +     */
    +    public function getPipe($type, $pid)
    +    {
    +        return sprintf('%s/%s_%s', $this->getRuntimeDir(), $type, $pid);
    +    }
    +
    +    /**
    +     * Check whether this PHP instance has Readline available.
    +     *
    +     * @return bool True if Readline is available.
    +     */
    +    public function hasReadline()
    +    {
    +        return $this->hasReadline;
    +    }
    +
    +    /**
    +     * Enable or disable Readline usage.
    +     *
    +     * @param bool $useReadline
    +     */
    +    public function setUseReadline($useReadline)
    +    {
    +        $this->useReadline = (bool) $useReadline;
    +    }
    +
    +    /**
    +     * Check whether to use Readline.
    +     *
    +     * If `setUseReadline` as been set to true, but Readline is not actually
    +     * available, this will return false.
    +     *
    +     * @return bool True if the current Shell should use Readline.
    +     */
    +    public function useReadline()
    +    {
    +        return isset($this->useReadline) ? ($this->hasReadline && $this->useReadline) : $this->hasReadline;
    +    }
    +
    +    /**
    +     * Set the Psy Shell readline service.
    +     *
    +     * @param Readline $readline
    +     */
    +    public function setReadline(Readline $readline)
    +    {
    +        $this->readline = $readline;
    +    }
    +
    +    /**
    +     * Get the Psy Shell readline service.
    +     *
    +     * By default, this service uses (in order of preference):
    +     *
    +     *  * GNU Readline
    +     *  * Libedit
    +     *  * A transient array-based readline emulation.
    +     *
    +     * @return Readline
    +     */
    +    public function getReadline()
    +    {
    +        if (!isset($this->readline)) {
    +            $className = $this->getReadlineClass();
    +            $this->readline = new $className(
    +                $this->getHistoryFile(),
    +                $this->getHistorySize(),
    +                $this->getEraseDuplicates()
    +            );
    +        }
    +
    +        return $this->readline;
    +    }
    +
    +    /**
    +     * Get the appropriate Readline implementation class name.
    +     *
    +     * @see self::getReadline
    +     *
    +     * @return string
    +     */
    +    private function getReadlineClass()
    +    {
    +        if ($this->useReadline()) {
    +            if (GNUReadline::isSupported()) {
    +                return 'Psy\Readline\GNUReadline';
    +            } elseif (Libedit::isSupported()) {
    +                return 'Psy\Readline\Libedit';
    +            }
    +        }
    +
    +        return 'Psy\Readline\Transient';
    +    }
    +
    +    /**
    +     * Check whether this PHP instance has Pcntl available.
    +     *
    +     * @return bool True if Pcntl is available.
    +     */
    +    public function hasPcntl()
    +    {
    +        return $this->hasPcntl;
    +    }
    +
    +    /**
    +     * Enable or disable Pcntl usage.
    +     *
    +     * @param bool $usePcntl
    +     */
    +    public function setUsePcntl($usePcntl)
    +    {
    +        $this->usePcntl = (bool) $usePcntl;
    +    }
    +
    +    /**
    +     * Check whether to use Pcntl.
    +     *
    +     * If `setUsePcntl` has been set to true, but Pcntl is not actually
    +     * available, this will return false.
    +     *
    +     * @return bool True if the current Shell should use Pcntl.
    +     */
    +    public function usePcntl()
    +    {
    +        return isset($this->usePcntl) ? ($this->hasPcntl && $this->usePcntl) : $this->hasPcntl;
    +    }
    +
    +    /**
    +     * Enable or disable strict requirement of semicolons.
    +     *
    +     * @see self::requireSemicolons()
    +     *
    +     * @param bool $requireSemicolons
    +     */
    +    public function setRequireSemicolons($requireSemicolons)
    +    {
    +        $this->requireSemicolons = (bool) $requireSemicolons;
    +    }
    +
    +    /**
    +     * Check whether to require semicolons on all statements.
    +     *
    +     * By default, PsySH will automatically insert semicolons at the end of
    +     * statements if they're missing. To strictly require semicolons, set
    +     * `requireSemicolons` to true.
    +     *
    +     * @return bool
    +     */
    +    public function requireSemicolons()
    +    {
    +        return $this->requireSemicolons;
    +    }
    +
    +    /**
    +     * Enable or disable Unicode in PsySH specific output.
    +     *
    +     * Note that this does not disable Unicode output in general, it just makes
    +     * it so PsySH won't output any itself.
    +     *
    +     * @param bool $useUnicode
    +     */
    +    public function setUseUnicode($useUnicode)
    +    {
    +        $this->useUnicode = (bool) $useUnicode;
    +    }
    +
    +    /**
    +     * Check whether to use Unicode in PsySH specific output.
    +     *
    +     * Note that this does not disable Unicode output in general, it just makes
    +     * it so PsySH won't output any itself.
    +     *
    +     * @return bool
    +     */
    +    public function useUnicode()
    +    {
    +        if (isset($this->useUnicode)) {
    +            return $this->useUnicode;
    +        }
    +
    +        // TODO: detect `chsh` != 65001 on Windows and return false
    +        return true;
    +    }
    +
    +    /**
    +     * Set the error logging level.
    +     *
    +     * @see self::errorLoggingLevel
    +     *
    +     * @param bool $errorLoggingLevel
    +     */
    +    public function setErrorLoggingLevel($errorLoggingLevel)
    +    {
    +        $this->errorLoggingLevel = (E_ALL | E_STRICT) & $errorLoggingLevel;
    +    }
    +
    +    /**
    +     * Get the current error logging level.
    +     *
    +     * By default, PsySH will automatically log all errors, regardless of the
    +     * current `error_reporting` level. Additionally, if the `error_reporting`
    +     * level warrants, an ErrorException will be thrown.
    +     *
    +     * Set `errorLoggingLevel` to 0 to prevent logging non-thrown errors. Set it
    +     * to any valid error_reporting value to log only errors which match that
    +     * level.
    +     *
    +     *     http://php.net/manual/en/function.error-reporting.php
    +     *
    +     * @return int
    +     */
    +    public function errorLoggingLevel()
    +    {
    +        return $this->errorLoggingLevel;
    +    }
    +
    +    /**
    +     * Set a CodeCleaner service instance.
    +     *
    +     * @param CodeCleaner $cleaner
    +     */
    +    public function setCodeCleaner(CodeCleaner $cleaner)
    +    {
    +        $this->cleaner = $cleaner;
    +    }
    +
    +    /**
    +     * Get a CodeCleaner service instance.
    +     *
    +     * If none has been explicitly defined, this will create a new instance.
    +     *
    +     * @return CodeCleaner
    +     */
    +    public function getCodeCleaner()
    +    {
    +        if (!isset($this->cleaner)) {
    +            $this->cleaner = new CodeCleaner();
    +        }
    +
    +        return $this->cleaner;
    +    }
    +
    +    /**
    +     * Enable or disable tab completion.
    +     *
    +     * @param bool $tabCompletion
    +     */
    +    public function setTabCompletion($tabCompletion)
    +    {
    +        $this->tabCompletion = (bool) $tabCompletion;
    +    }
    +
    +    /**
    +     * Check whether to use tab completion.
    +     *
    +     * If `setTabCompletion` has been set to true, but readline is not actually
    +     * available, this will return false.
    +     *
    +     * @return bool True if the current Shell should use tab completion.
    +     */
    +    public function getTabCompletion()
    +    {
    +        return isset($this->tabCompletion) ? ($this->hasReadline && $this->tabCompletion) : $this->hasReadline;
    +    }
    +
    +    /**
    +     * Set the Shell Output service.
    +     *
    +     * @param ShellOutput $output
    +     */
    +    public function setOutput(ShellOutput $output)
    +    {
    +        $this->output = $output;
    +    }
    +
    +    /**
    +     * Get a Shell Output service instance.
    +     *
    +     * If none has been explicitly provided, this will create a new instance
    +     * with VERBOSITY_NORMAL and the output page supplied by self::getPager
    +     *
    +     * @see self::getPager
    +     *
    +     * @return ShellOutput
    +     */
    +    public function getOutput()
    +    {
    +        if (!isset($this->output)) {
    +            $this->output = new ShellOutput(
    +                ShellOutput::VERBOSITY_NORMAL,
    +                $this->getOutputDecorated(),
    +                null,
    +                $this->getPager()
    +            );
    +        }
    +
    +        return $this->output;
    +    }
    +
    +    /**
    +     * Get the decoration (i.e. color) setting for the Shell Output service.
    +     *
    +     * @return null|bool 3-state boolean corresponding to the current color mode
    +     */
    +    public function getOutputDecorated()
    +    {
    +        if ($this->colorMode() === self::COLOR_MODE_AUTO) {
    +            return;
    +        } elseif ($this->colorMode() === self::COLOR_MODE_FORCED) {
    +            return true;
    +        } elseif ($this->colorMode() === self::COLOR_MODE_DISABLED) {
    +            return false;
    +        }
    +    }
    +
    +    /**
    +     * Set the OutputPager service.
    +     *
    +     * If a string is supplied, a ProcOutputPager will be used which shells out
    +     * to the specified command.
    +     *
    +     * @throws \InvalidArgumentException if $pager is not a string or OutputPager instance.
    +     *
    +     * @param string|OutputPager $pager
    +     */
    +    public function setPager($pager)
    +    {
    +        if ($pager && !is_string($pager) && !$pager instanceof OutputPager) {
    +            throw new \InvalidArgumentException('Unexpected pager instance.');
    +        }
    +
    +        $this->pager = $pager;
    +    }
    +
    +    /**
    +     * Get an OutputPager instance or a command for an external Proc pager.
    +     *
    +     * If no Pager has been explicitly provided, and Pcntl is available, this
    +     * will default to `cli.pager` ini value, falling back to `which less`.
    +     *
    +     * @return string|OutputPager
    +     */
    +    public function getPager()
    +    {
    +        if (!isset($this->pager) && $this->usePcntl()) {
    +            if ($pager = ini_get('cli.pager')) {
    +                // use the default pager (5.4+)
    +                $this->pager = $pager;
    +            } elseif ($less = exec('which less 2>/dev/null')) {
    +                // check for the presence of less...
    +                $this->pager = $less . ' -R -S -F -X';
    +            }
    +        }
    +
    +        return $this->pager;
    +    }
    +
    +    /**
    +     * Set the Shell evaluation Loop service.
    +     *
    +     * @param Loop $loop
    +     */
    +    public function setLoop(Loop $loop)
    +    {
    +        $this->loop = $loop;
    +    }
    +
    +    /**
    +     * Get a Shell evaluation Loop service instance.
    +     *
    +     * If none has been explicitly defined, this will create a new instance.
    +     * If Pcntl is available and enabled, the new instance will be a ForkingLoop.
    +     *
    +     * @return Loop
    +     */
    +    public function getLoop()
    +    {
    +        if (!isset($this->loop)) {
    +            if ($this->usePcntl()) {
    +                $this->loop = new ForkingLoop($this);
    +            } else {
    +                $this->loop = new Loop($this);
    +            }
    +        }
    +
    +        return $this->loop;
    +    }
    +
    +    /**
    +     * Set the Shell autocompleter service.
    +     *
    +     * @param AutoCompleter $completer
    +     */
    +    public function setAutoCompleter(AutoCompleter $completer)
    +    {
    +        $this->completer = $completer;
    +    }
    +
    +    /**
    +     * Get an AutoCompleter service instance.
    +     *
    +     * @return AutoCompleter
    +     */
    +    public function getAutoCompleter()
    +    {
    +        if (!isset($this->completer)) {
    +            $this->completer = new AutoCompleter();
    +        }
    +
    +        return $this->completer;
    +    }
    +
    +    /**
    +     * Get user specified tab completion matchers for the AutoCompleter.
    +     *
    +     * @return array
    +     */
    +    public function getTabCompletionMatchers()
    +    {
    +        return $this->tabCompletionMatchers;
    +    }
    +
    +    /**
    +     * Add additional tab completion matchers to the AutoCompleter.
    +     *
    +     * @param array $matchers
    +     */
    +    public function addTabCompletionMatchers(array $matchers)
    +    {
    +        $this->tabCompletionMatchers = array_merge($this->tabCompletionMatchers, $matchers);
    +        if (isset($this->shell)) {
    +            $this->shell->addTabCompletionMatchers($this->tabCompletionMatchers);
    +        }
    +    }
    +
    +    /**
    +     * Add commands to the Shell.
    +     *
    +     * This will buffer new commands in the event that the Shell has not yet
    +     * been instantiated. This allows the user to specify commands in their
    +     * config rc file, despite the fact that their file is needed in the Shell
    +     * constructor.
    +     *
    +     * @param array $commands
    +     */
    +    public function addCommands(array $commands)
    +    {
    +        $this->newCommands = array_merge($this->newCommands, $commands);
    +        if (isset($this->shell)) {
    +            $this->doAddCommands();
    +        }
    +    }
    +
    +    /**
    +     * Internal method for adding commands. This will set any new commands once
    +     * a Shell is available.
    +     */
    +    private function doAddCommands()
    +    {
    +        if (!empty($this->newCommands)) {
    +            $this->shell->addCommands($this->newCommands);
    +            $this->newCommands = array();
    +        }
    +    }
    +
    +    /**
    +     * Set the Shell backreference and add any new commands to the Shell.
    +     *
    +     * @param Shell $shell
    +     */
    +    public function setShell(Shell $shell)
    +    {
    +        $this->shell = $shell;
    +        $this->doAddCommands();
    +    }
    +
    +    /**
    +     * Set the PHP manual database file.
    +     *
    +     * This file should be an SQLite database generated from the phpdoc source
    +     * with the `bin/build_manual` script.
    +     *
    +     * @param string $filename
    +     */
    +    public function setManualDbFile($filename)
    +    {
    +        $this->manualDbFile = (string) $filename;
    +    }
    +
    +    /**
    +     * Get the current PHP manual database file.
    +     *
    +     * @return string Default: '~/.local/share/psysh/php_manual.sqlite'
    +     */
    +    public function getManualDbFile()
    +    {
    +        if (isset($this->manualDbFile)) {
    +            return $this->manualDbFile;
    +        }
    +
    +        $files = ConfigPaths::getDataFiles(array('php_manual.sqlite'), $this->dataDir);
    +        if (!empty($files)) {
    +            if ($this->warnOnMultipleConfigs && count($files) > 1) {
    +                $msg = sprintf('Multiple manual database files found: %s. Using %s', implode($files, ', '), $files[0]);
    +                trigger_error($msg, E_USER_NOTICE);
    +            }
    +
    +            return $this->manualDbFile = $files[0];
    +        }
    +    }
    +
    +    /**
    +     * Get a PHP manual database connection.
    +     *
    +     * @return PDO
    +     */
    +    public function getManualDb()
    +    {
    +        if (!isset($this->manualDb)) {
    +            $dbFile = $this->getManualDbFile();
    +            if (is_file($dbFile)) {
    +                try {
    +                    $this->manualDb = new \PDO('sqlite:' . $dbFile);
    +                } catch (\PDOException $e) {
    +                    if ($e->getMessage() === 'could not find driver') {
    +                        throw new RuntimeException('SQLite PDO driver not found', 0, $e);
    +                    } else {
    +                        throw $e;
    +                    }
    +                }
    +            }
    +        }
    +
    +        return $this->manualDb;
    +    }
    +
    +    /**
    +     * Add an array of casters definitions.
    +     *
    +     * @param array $casters
    +     */
    +    public function addCasters(array $casters)
    +    {
    +        $this->getPresenter()->addCasters($casters);
    +    }
    +
    +    /**
    +     * Get the Presenter service.
    +     *
    +     * @return Presenter
    +     */
    +    public function getPresenter()
    +    {
    +        if (!isset($this->presenter)) {
    +            $this->presenter = new Presenter($this->getOutput()->getFormatter());
    +        }
    +
    +        return $this->presenter;
    +    }
    +
    +    /**
    +     * Enable or disable warnings on multiple configuration or data files.
    +     *
    +     * @see self::warnOnMultipleConfigs()
    +     *
    +     * @param bool $warnOnMultipleConfigs
    +     */
    +    public function setWarnOnMultipleConfigs($warnOnMultipleConfigs)
    +    {
    +        $this->warnOnMultipleConfigs = (bool) $warnOnMultipleConfigs;
    +    }
    +
    +    /**
    +     * Check whether to warn on multiple configuration or data files.
    +     *
    +     * By default, PsySH will use the file with highest precedence, and will
    +     * silently ignore all others. With this enabled, a warning will be emitted
    +     * (but not an exception thrown) if multiple configuration or data files
    +     * are found.
    +     *
    +     * This will default to true in a future release, but is false for now.
    +     *
    +     * @return bool
    +     */
    +    public function warnOnMultipleConfigs()
    +    {
    +        return $this->warnOnMultipleConfigs;
    +    }
    +
    +    /**
    +     * Set the current color mode.
    +     *
    +     * @param string $colorMode
    +     */
    +    public function setColorMode($colorMode)
    +    {
    +        $validColorModes = array(
    +            self::COLOR_MODE_AUTO,
    +            self::COLOR_MODE_FORCED,
    +            self::COLOR_MODE_DISABLED,
    +        );
    +
    +        if (in_array($colorMode, $validColorModes)) {
    +            $this->colorMode = $colorMode;
    +        } else {
    +            throw new \InvalidArgumentException('invalid color mode: ' . $colorMode);
    +        }
    +    }
    +
    +    /**
    +     * Get the current color mode.
    +     *
    +     * @return string
    +     */
    +    public function colorMode()
    +    {
    +        return $this->colorMode;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/ConsoleColorFactory.php b/core/vendor/psy/psysh/src/Psy/ConsoleColorFactory.php
    new file mode 100644
    index 0000000..5808771
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/ConsoleColorFactory.php
    @@ -0,0 +1,79 @@
    +colorMode = $colorMode;
    +    }
    +
    +    /**
    +     * Get a `ConsoleColor` instance configured according to the given color
    +     * mode.
    +     *
    +     * @return ConsoleColor
    +     */
    +    public function getConsoleColor()
    +    {
    +        if ($this->colorMode === Configuration::COLOR_MODE_AUTO) {
    +            return $this->getDefaultConsoleColor();
    +        } elseif ($this->colorMode === Configuration::COLOR_MODE_FORCED) {
    +            return $this->getForcedConsoleColor();
    +        } elseif ($this->colorMode === Configuration::COLOR_MODE_DISABLED) {
    +            return $this->getDisabledConsoleColor();
    +        }
    +    }
    +
    +    private function getDefaultConsoleColor()
    +    {
    +        $color = new ConsoleColor();
    +        $color->addTheme(Highlighter::LINE_NUMBER, array('blue'));
    +
    +        return $color;
    +    }
    +
    +    private function getForcedConsoleColor()
    +    {
    +        $color = $this->getDefaultConsoleColor();
    +        $color->setForceStyle(true);
    +
    +        return $color;
    +    }
    +
    +    private function getDisabledConsoleColor()
    +    {
    +        $color = new ConsoleColor();
    +
    +        $color->addTheme(Highlighter::TOKEN_STRING, array('none'));
    +        $color->addTheme(Highlighter::TOKEN_COMMENT, array('none'));
    +        $color->addTheme(Highlighter::TOKEN_KEYWORD, array('none'));
    +        $color->addTheme(Highlighter::TOKEN_DEFAULT, array('none'));
    +        $color->addTheme(Highlighter::TOKEN_HTML, array('none'));
    +        $color->addTheme(Highlighter::ACTUAL_LINE_MARK, array('none'));
    +        $color->addTheme(Highlighter::LINE_NUMBER, array('none'));
    +
    +        return $color;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Context.php b/core/vendor/psy/psysh/src/Psy/Context.php
    new file mode 100644
    index 0000000..6018891
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Context.php
    @@ -0,0 +1,136 @@
    +returnValue;
    +
    +            case '_e':
    +                if (!isset($this->lastException)) {
    +                    throw new \InvalidArgumentException('Unknown variable: $' . $name);
    +                }
    +
    +                return $this->lastException;
    +
    +            default:
    +                if (!array_key_exists($name, $this->scopeVariables)) {
    +                    throw new \InvalidArgumentException('Unknown variable: $' . $name);
    +                }
    +
    +                return $this->scopeVariables[$name];
    +        }
    +    }
    +
    +    /**
    +     * Get all defined variables.
    +     *
    +     * @return array
    +     */
    +    public function getAll()
    +    {
    +        $vars = $this->scopeVariables;
    +        $vars['_'] = $this->returnValue;
    +
    +        if (isset($this->lastException)) {
    +            $vars['_e'] = $this->lastException;
    +        }
    +
    +        return $vars;
    +    }
    +
    +    /**
    +     * Set all scope variables.
    +     *
    +     * This method does *not* set the magic $_ and $_e variables.
    +     *
    +     * @param array $vars
    +     */
    +    public function setAll(array $vars)
    +    {
    +        foreach (self::$specialVars as $key) {
    +            unset($vars[$key]);
    +        }
    +
    +        $this->scopeVariables = $vars;
    +    }
    +
    +    /**
    +     * Set the most recent return value.
    +     *
    +     * @param mixed $value
    +     */
    +    public function setReturnValue($value)
    +    {
    +        $this->returnValue = $value;
    +    }
    +
    +    /**
    +     * Get the most recent return value.
    +     *
    +     * @return mixed
    +     */
    +    public function getReturnValue()
    +    {
    +        return $this->returnValue;
    +    }
    +
    +    /**
    +     * Set the most recent Exception.
    +     *
    +     * @param \Exception $e
    +     */
    +    public function setLastException(\Exception $e)
    +    {
    +        $this->lastException = $e;
    +    }
    +
    +    /**
    +     * Get the most recent Exception.
    +     *
    +     * @throws InvalidArgumentException If no Exception has been caught.
    +     *
    +     * @return null|Exception
    +     */
    +    public function getLastException()
    +    {
    +        if (!isset($this->lastException)) {
    +            throw new \InvalidArgumentException('No most-recent exception');
    +        }
    +
    +        return $this->lastException;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/ContextAware.php b/core/vendor/psy/psysh/src/Psy/ContextAware.php
    new file mode 100644
    index 0000000..8c9d61a
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/ContextAware.php
    @@ -0,0 +1,28 @@
    +rawMessage = $message;
    +        parent::__construct(sprintf('Exit:  %s', $message), $code, $previous);
    +    }
    +
    +    /**
    +     * Return a raw (unformatted) version of the error message.
    +     *
    +     * @return string
    +     */
    +    public function getRawMessage()
    +    {
    +        return $this->rawMessage;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Exception/DeprecatedException.php b/core/vendor/psy/psysh/src/Psy/Exception/DeprecatedException.php
    new file mode 100644
    index 0000000..91bc14e
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Exception/DeprecatedException.php
    @@ -0,0 +1,20 @@
    +rawMessage = $message;
    +
    +        if (!empty($filename) && preg_match('{Psy[/\\\\]ExecutionLoop}', $filename)) {
    +            $filename = '';
    +        }
    +
    +        switch ($severity) {
    +            case E_WARNING:
    +            case E_CORE_WARNING:
    +            case E_COMPILE_WARNING:
    +            case E_USER_WARNING:
    +                $type = 'warning';
    +                break;
    +
    +            case E_STRICT:
    +                $type = 'Strict error';
    +                break;
    +
    +            default:
    +                $type = 'error';
    +                break;
    +        }
    +
    +        $message = sprintf('PHP %s:  %s%s on line %d', $type, $message, $filename ? ' in ' . $filename : '', $lineno);
    +        parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
    +    }
    +
    +    /**
    +     * Get the raw (unformatted) message for this error.
    +     *
    +     * @return string
    +     */
    +    public function getRawMessage()
    +    {
    +        return $this->rawMessage;
    +    }
    +
    +    /**
    +     * Helper for throwing an ErrorException.
    +     *
    +     * This allows us to:
    +     *
    +     *     set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
    +     *
    +     * @throws ErrorException
    +     *
    +     * @param int    $errno   Error type
    +     * @param string $errstr  Message
    +     * @param string $errfile Filename
    +     * @param int    $errline Line number
    +     */
    +    public static function throwException($errno, $errstr, $errfile, $errline)
    +    {
    +        throw new self($errstr, 0, $errno, $errfile, $errline);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Exception/Exception.php b/core/vendor/psy/psysh/src/Psy/Exception/Exception.php
    new file mode 100644
    index 0000000..4d9345f
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Exception/Exception.php
    @@ -0,0 +1,27 @@
    +rawMessage = $message;
    +        $message = sprintf('PHP Fatal error:  %s in %s on line %d', $message, $filename ?: "eval()'d code", $lineno);
    +        parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
    +    }
    +
    +    /**
    +     * Return a raw (unformatted) version of the error message.
    +     *
    +     * @return string
    +     */
    +    public function getRawMessage()
    +    {
    +        return $this->rawMessage;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Exception/ParseErrorException.php b/core/vendor/psy/psysh/src/Psy/Exception/ParseErrorException.php
    new file mode 100644
    index 0000000..25b8145
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Exception/ParseErrorException.php
    @@ -0,0 +1,42 @@
    +getRawMessage(), $e->getRawLine());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Exception/RuntimeException.php b/core/vendor/psy/psysh/src/Psy/Exception/RuntimeException.php
    new file mode 100644
    index 0000000..fdeb5f6
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Exception/RuntimeException.php
    @@ -0,0 +1,43 @@
    +rawMessage = $message;
    +        parent::__construct($message, $code, $previous);
    +    }
    +
    +    /**
    +     * Return a raw (unformatted) version of the error message.
    +     *
    +     * @return string
    +     */
    +    public function getRawMessage()
    +    {
    +        return $this->rawMessage;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Exception/ThrowUpException.php b/core/vendor/psy/psysh/src/Psy/Exception/ThrowUpException.php
    new file mode 100644
    index 0000000..c289026
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Exception/ThrowUpException.php
    @@ -0,0 +1,37 @@
    +getMessage());
    +        parent::__construct($message, $exception->getCode(), $exception);
    +    }
    +
    +    /**
    +     * Return a raw (unformatted) version of the error message.
    +     *
    +     * @return string
    +     */
    +    public function getRawMessage()
    +    {
    +        return $this->getPrevious()->getMessage();
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Exception/TypeErrorException.php b/core/vendor/psy/psysh/src/Psy/Exception/TypeErrorException.php
    new file mode 100644
    index 0000000..cf26bd8
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Exception/TypeErrorException.php
    @@ -0,0 +1,55 @@
    +rawMessage = $message;
    +        $message = preg_replace('/, called in .*?: eval\\(\\)\'d code/', '', $message);
    +        parent::__construct(sprintf('TypeError: %s', $message), $code);
    +    }
    +
    +    /**
    +     * Get the raw (unformatted) message for this error.
    +     *
    +     * @return string
    +     */
    +    public function getRawMessage()
    +    {
    +        return $this->rawMessage;
    +    }
    +
    +    /**
    +     * Create a TypeErrorException from a TypeError.
    +     *
    +     * @param \TypeError $e
    +     *
    +     * @return TypeErrorException
    +     */
    +    public static function fromTypeError(\TypeError $e)
    +    {
    +        return new self($e->getMessage(), $e->getLine());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/ExecutionLoop/ForkingLoop.php b/core/vendor/psy/psysh/src/Psy/ExecutionLoop/ForkingLoop.php
    new file mode 100644
    index 0000000..f695519
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/ExecutionLoop/ForkingLoop.php
    @@ -0,0 +1,174 @@
    + 0) {
    +            // This is the main thread. We'll just wait for a while.
    +
    +            // We won't be needing this one.
    +            fclose($up);
    +
    +            // Wait for a return value from the loop process.
    +            $read   = array($down);
    +            $write  = null;
    +            $except = null;
    +            if (stream_select($read, $write, $except, null) === false) {
    +                throw new \RuntimeException('Error waiting for execution loop.');
    +            }
    +
    +            $content = stream_get_contents($down);
    +            fclose($down);
    +
    +            if ($content) {
    +                $shell->setScopeVariables(@unserialize($content));
    +            }
    +
    +            return;
    +        }
    +
    +        // This is the child process. It's going to do all the work.
    +        if (function_exists('setproctitle')) {
    +            setproctitle('psysh (loop)');
    +        }
    +
    +        // We won't be needing this one.
    +        fclose($down);
    +
    +        // Let's do some processing.
    +        parent::run($shell);
    +
    +        // Send the scope variables back up to the main thread
    +        fwrite($up, $this->serializeReturn($shell->getScopeVariables()));
    +        fclose($up);
    +
    +        exit;
    +    }
    +
    +    /**
    +     * Create a savegame at the start of each loop iteration.
    +     */
    +    public function beforeLoop()
    +    {
    +        $this->createSavegame();
    +    }
    +
    +    /**
    +     * Clean up old savegames at the end of each loop iteration.
    +     */
    +    public function afterLoop()
    +    {
    +        // if there's an old savegame hanging around, let's kill it.
    +        if (isset($this->savegame)) {
    +            posix_kill($this->savegame, SIGKILL);
    +            pcntl_signal_dispatch();
    +        }
    +    }
    +
    +    /**
    +     * Create a savegame fork.
    +     *
    +     * The savegame contains the current execution state, and can be resumed in
    +     * the event that the worker dies unexpectedly (for example, by encountering
    +     * a PHP fatal error).
    +     */
    +    private function createSavegame()
    +    {
    +        // the current process will become the savegame
    +        $this->savegame = posix_getpid();
    +
    +        $pid = pcntl_fork();
    +        if ($pid < 0) {
    +            throw new \RuntimeException('Unable to create savegame fork.');
    +        } elseif ($pid > 0) {
    +            // we're the savegame now... let's wait and see what happens
    +            pcntl_waitpid($pid, $status);
    +
    +            // worker exited cleanly, let's bail
    +            if (!pcntl_wexitstatus($status)) {
    +                posix_kill(posix_getpid(), SIGKILL);
    +            }
    +
    +            // worker didn't exit cleanly, we'll need to have another go
    +            $this->createSavegame();
    +        }
    +    }
    +
    +    /**
    +     * Serialize all serializable return values.
    +     *
    +     * A naïve serialization will run into issues if there is a Closure or
    +     * SimpleXMLElement (among other things) in scope when exiting the execution
    +     * loop. We'll just ignore these unserializable classes, and serialize what
    +     * we can.
    +     *
    +     * @param array $return
    +     *
    +     * @return string
    +     */
    +    private function serializeReturn(array $return)
    +    {
    +        $serializable = array();
    +
    +        foreach ($return as $key => $value) {
    +            // No need to return magic variables
    +            if ($key === '_' || $key === '_e') {
    +                continue;
    +            }
    +
    +            // Resources don't error, but they don't serialize well either.
    +            if (is_resource($value) || $value instanceof \Closure) {
    +                continue;
    +            }
    +
    +            try {
    +                @serialize($value);
    +                $serializable[$key] = $value;
    +            } catch (\Exception $e) {
    +                // we'll just ignore this one...
    +            }
    +        }
    +
    +        return @serialize($serializable);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/ExecutionLoop/Loop.php b/core/vendor/psy/psysh/src/Psy/ExecutionLoop/Loop.php
    new file mode 100644
    index 0000000..45ff1b1
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/ExecutionLoop/Loop.php
    @@ -0,0 +1,175 @@
    +getIncludes() as $__psysh_include__) {
    +                    include $__psysh_include__;
    +                }
    +            } catch (\Exception $_e) {
    +                $__psysh__->writeException($_e);
    +            }
    +            restore_error_handler();
    +            unset($__psysh_include__);
    +
    +            extract($__psysh__->getScopeVariables());
    +
    +            do {
    +                $__psysh__->beforeLoop();
    +                $__psysh__->setScopeVariables(get_defined_vars());
    +
    +                try {
    +                    // read a line, see if we should eval
    +                    $__psysh__->getInput();
    +
    +                    // evaluate the current code buffer
    +                    ob_start(
    +                        array($__psysh__, 'writeStdout'),
    +                        version_compare(PHP_VERSION, '5.4', '>=') ? 1 : 2
    +                    );
    +
    +                    set_error_handler(array($__psysh__, 'handleError'));
    +                    $_ = eval($__psysh__->flushCode() ?: Loop::NOOP_INPUT);
    +                    restore_error_handler();
    +
    +                    ob_end_flush();
    +
    +                    $__psysh__->writeReturnValue($_);
    +                } catch (BreakException $_e) {
    +                    restore_error_handler();
    +                    if (ob_get_level() > 0) {
    +                        ob_end_clean();
    +                    }
    +                    $__psysh__->writeException($_e);
    +
    +                    return;
    +                } catch (ThrowUpException $_e) {
    +                    restore_error_handler();
    +                    if (ob_get_level() > 0) {
    +                        ob_end_clean();
    +                    }
    +                    $__psysh__->writeException($_e);
    +
    +                    throw $_e;
    +                } catch (\TypeError $_e) {
    +                    restore_error_handler();
    +                    if (ob_get_level() > 0) {
    +                        ob_end_clean();
    +                    }
    +                    $__psysh__->writeException(TypeErrorException::fromTypeError($_e));
    +                } catch (\Exception $_e) {
    +                    restore_error_handler();
    +                    if (ob_get_level() > 0) {
    +                        ob_end_clean();
    +                    }
    +                    $__psysh__->writeException($_e);
    +                }
    +
    +                $__psysh__->afterLoop();
    +            } while (true);
    +        };
    +
    +        // bind the closure to $this from the shell scope variables...
    +        if (self::bindLoop()) {
    +            $that = null;
    +            try {
    +                $that = $shell->getScopeVariable('this');
    +            } catch (\InvalidArgumentException $e) {
    +                // well, it was worth a shot
    +            }
    +
    +            if (is_object($that)) {
    +                $loop = $loop->bindTo($that, get_class($that));
    +            } else {
    +                $loop = $loop->bindTo(null, null);
    +            }
    +        }
    +
    +        $loop($shell);
    +    }
    +
    +    /**
    +     * A beforeLoop callback.
    +     *
    +     * This is executed at the start of each loop iteration. In the default
    +     * (non-forking) loop implementation, this is a no-op.
    +     */
    +    public function beforeLoop()
    +    {
    +        // no-op
    +    }
    +
    +    /**
    +     * A afterLoop callback.
    +     *
    +     * This is executed at the end of each loop iteration. In the default
    +     * (non-forking) loop implementation, this is a no-op.
    +     */
    +    public function afterLoop()
    +    {
    +        // no-op
    +    }
    +
    +    /**
    +     * Decide whether to bind the execution loop.
    +     *
    +     * @return bool
    +     */
    +    protected static function bindLoop()
    +    {
    +        // skip binding on HHVM <= 3.5.0
    +        // see https://github.com/facebook/hhvm/issues/1203
    +        if (defined('HHVM_VERSION')) {
    +            return version_compare(HHVM_VERSION, '3.5.0', '>=');
    +        }
    +
    +        return version_compare(PHP_VERSION, '5.4', '>=');
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Formatter/CodeFormatter.php b/core/vendor/psy/psysh/src/Psy/Formatter/CodeFormatter.php
    new file mode 100644
    index 0000000..d992c48
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Formatter/CodeFormatter.php
    @@ -0,0 +1,58 @@
    +getFileName()) {
    +            if (!is_file($fileName)) {
    +                throw new RuntimeException('Source code unavailable.');
    +            }
    +
    +            $file  = file_get_contents($fileName);
    +            $start = $reflector->getStartLine();
    +            $end   = $reflector->getEndLine() - $start;
    +
    +            $factory = new ConsoleColorFactory($colorMode);
    +            $colors = $factory->getConsoleColor();
    +            $highlighter = new Highlighter($colors);
    +
    +            return $highlighter->getCodeSnippet($file, $start, 0, $end);
    +
    +            // no need to escape this bad boy, since (for now) it's being output raw.
    +            // return OutputFormatter::escape(implode(PHP_EOL, $code));
    +            return implode(PHP_EOL, $code);
    +        } else {
    +            throw new RuntimeException('Source code unavailable.');
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Formatter/DocblockFormatter.php b/core/vendor/psy/psysh/src/Psy/Formatter/DocblockFormatter.php
    new file mode 100644
    index 0000000..71d93ed
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Formatter/DocblockFormatter.php
    @@ -0,0 +1,168 @@
    + 'info',
    +        'var'  => 'strong',
    +    );
    +
    +    /**
    +     * Format a docblock.
    +     *
    +     * @param \Reflector $reflector
    +     *
    +     * @return string Formatted docblock
    +     */
    +    public static function format(\Reflector $reflector)
    +    {
    +        $docblock = new Docblock($reflector);
    +        $chunks   = array();
    +
    +        if (!empty($docblock->desc)) {
    +            $chunks[] = 'Description:';
    +            $chunks[] = self::indent(OutputFormatter::escape($docblock->desc), '  ');
    +            $chunks[] = '';
    +        }
    +
    +        if (!empty($docblock->tags)) {
    +            foreach ($docblock::$vectors as $name => $vector) {
    +                if (isset($docblock->tags[$name])) {
    +                    $chunks[] = sprintf('%s:', self::inflect($name));
    +                    $chunks[] = self::formatVector($vector, $docblock->tags[$name]);
    +                    $chunks[] = '';
    +                }
    +            }
    +
    +            $tags = self::formatTags(array_keys($docblock::$vectors), $docblock->tags);
    +            if (!empty($tags)) {
    +                $chunks[] = $tags;
    +                $chunks[] = '';
    +            }
    +        }
    +
    +        return rtrim(implode("\n", $chunks));
    +    }
    +
    +    /**
    +     * Format a docblock vector, for example, `@throws`, `@param`, or `@return`.
    +     *
    +     * @see DocBlock::$vectors
    +     *
    +     * @param array $vector
    +     * @param array $lines
    +     *
    +     * @return string
    +     */
    +    private static function formatVector(array $vector, array $lines)
    +    {
    +        $template = array(' ');
    +        foreach ($vector as $type) {
    +            $max = 0;
    +            foreach ($lines as $line) {
    +                $chunk = $line[$type];
    +                $cur = empty($chunk) ? 0 : strlen($chunk) + 1;
    +                if ($cur > $max) {
    +                    $max = $cur;
    +                }
    +            }
    +
    +            $template[] = self::getVectorParamTemplate($type, $max);
    +        }
    +        $template = implode(' ', $template);
    +
    +        return implode("\n", array_map(function ($line) use ($template) {
    +            $escaped = array_map(array('Symfony\Component\Console\Formatter\OutputFormatter', 'escape'), $line);
    +
    +            return rtrim(vsprintf($template, $escaped));
    +        }, $lines));
    +    }
    +
    +    /**
    +     * Format docblock tags.
    +     *
    +     * @param array $skip Tags to exclude
    +     * @param array $tags Tags to format
    +     *
    +     * @return string formatted tags
    +     */
    +    private static function formatTags(array $skip, array $tags)
    +    {
    +        $chunks = array();
    +
    +        foreach ($tags as $name => $values) {
    +            if (in_array($name, $skip)) {
    +                continue;
    +            }
    +
    +            foreach ($values as $value) {
    +                $chunks[] = sprintf('%s%s %s', self::inflect($name), empty($value) ? '' : ':', OutputFormatter::escape($value));
    +            }
    +
    +            $chunks[] = '';
    +        }
    +
    +        return implode("\n", $chunks);
    +    }
    +
    +    /**
    +     * Get a docblock vector template.
    +     *
    +     * @param string $type Vector type
    +     * @param int    $max  Pad width
    +     *
    +     * @return string
    +     */
    +    private static function getVectorParamTemplate($type, $max)
    +    {
    +        if (!isset(self::$vectorParamTemplates[$type])) {
    +            return sprintf('%%-%ds', $max);
    +        }
    +
    +        return sprintf('<%s>%%-%ds', self::$vectorParamTemplates[$type], $max, self::$vectorParamTemplates[$type]);
    +    }
    +
    +    /**
    +     * Indent a string.
    +     *
    +     * @param string $text   String to indent
    +     * @param string $indent (default: '  ')
    +     *
    +     * @return string
    +     */
    +    private static function indent($text, $indent = '  ')
    +    {
    +        return $indent . str_replace("\n", "\n" . $indent, $text);
    +    }
    +
    +    /**
    +     * Convert underscored or whitespace separated words into sentence case.
    +     *
    +     * @param string $text
    +     *
    +     * @return string
    +     */
    +    private static function inflect($text)
    +    {
    +        $words = trim(preg_replace('/[\s_-]+/', ' ', preg_replace('/([a-z])([A-Z])/', '$1 $2', $text)));
    +
    +        return implode(' ', array_map('ucfirst', explode(' ', $words)));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Formatter/Formatter.php b/core/vendor/psy/psysh/src/Psy/Formatter/Formatter.php
    new file mode 100644
    index 0000000..b80f890
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Formatter/Formatter.php
    @@ -0,0 +1,25 @@
    +getName();
    +    }
    +
    +    /**
    +     * Print the method, property or class modifiers.
    +     *
    +     * Technically this should be a trait. Can't wait for 5.4 :)
    +     *
    +     * @param \Reflector $reflector
    +     *
    +     * @return string Formatted modifiers.
    +     */
    +    private static function formatModifiers(\Reflector $reflector)
    +    {
    +        return implode(' ', array_map(function ($modifier) {
    +            return sprintf('%s', $modifier);
    +        }, \Reflection::getModifierNames($reflector->getModifiers())));
    +    }
    +
    +    /**
    +     * Format a class signature.
    +     *
    +     * @param \ReflectionClass $reflector
    +     *
    +     * @return string Formatted signature.
    +     */
    +    private static function formatClass(\ReflectionClass $reflector)
    +    {
    +        $chunks = array();
    +
    +        if ($modifiers = self::formatModifiers($reflector)) {
    +            $chunks[] = $modifiers;
    +        }
    +
    +        if (version_compare(PHP_VERSION, '5.4', '>=') && $reflector->isTrait()) {
    +            $chunks[] = 'trait';
    +        } else {
    +            $chunks[] = $reflector->isInterface() ? 'interface' : 'class';
    +        }
    +
    +        $chunks[] = sprintf('%s', self::formatName($reflector));
    +
    +        if ($parent = $reflector->getParentClass()) {
    +            $chunks[] = 'extends';
    +            $chunks[] = sprintf('%s', $parent->getName());
    +        }
    +
    +        $interfaces = $reflector->getInterfaceNames();
    +        if (!empty($interfaces)) {
    +            sort($interfaces);
    +
    +            $chunks[] = 'implements';
    +            $chunks[] = implode(', ', array_map(function ($name) {
    +                return sprintf('%s', $name);
    +            }, $interfaces));
    +        }
    +
    +        return implode(' ', $chunks);
    +    }
    +
    +    /**
    +     * Format a constant signature.
    +     *
    +     * @param ReflectionConstant $reflector
    +     *
    +     * @return string Formatted signature.
    +     */
    +    private static function formatConstant(ReflectionConstant $reflector)
    +    {
    +        $value = $reflector->getValue();
    +        $style = self::getTypeStyle($value);
    +
    +        return sprintf(
    +            'const %s = <%s>%s',
    +            self::formatName($reflector),
    +            $style,
    +            OutputFormatter::escape(Json::encode($value)),
    +            $style
    +        );
    +    }
    +
    +    /**
    +     * Helper for getting output style for a given value's type.
    +     *
    +     * @param mixed $value
    +     *
    +     * @return string
    +     */
    +    private static function getTypeStyle($value)
    +    {
    +        if (is_int($value) || is_float($value)) {
    +            return 'number';
    +        } elseif (is_string($value)) {
    +            return 'string';
    +        } elseif (is_bool($value) || is_null($value)) {
    +            return 'bool';
    +        } else {
    +            return 'strong';
    +        }
    +    }
    +
    +    /**
    +     * Format a property signature.
    +     *
    +     * @param \ReflectionProperty $reflector
    +     *
    +     * @return string Formatted signature.
    +     */
    +    private static function formatProperty(\ReflectionProperty $reflector)
    +    {
    +        return sprintf(
    +            '%s $%s',
    +            self::formatModifiers($reflector),
    +            $reflector->getName()
    +        );
    +    }
    +
    +    /**
    +     * Format a function signature.
    +     *
    +     * @param \ReflectionFunction $reflector
    +     *
    +     * @return string Formatted signature.
    +     */
    +    private static function formatFunction(\ReflectionFunctionAbstract $reflector)
    +    {
    +        return sprintf(
    +            'function %s%s(%s)',
    +            $reflector->returnsReference() ? '&' : '',
    +            self::formatName($reflector),
    +            implode(', ', self::formatFunctionParams($reflector))
    +        );
    +    }
    +
    +    /**
    +     * Format a method signature.
    +     *
    +     * @param \ReflectionMethod $reflector
    +     *
    +     * @return string Formatted signature.
    +     */
    +    private static function formatMethod(\ReflectionMethod $reflector)
    +    {
    +        return sprintf(
    +            '%s %s',
    +            self::formatModifiers($reflector),
    +            self::formatFunction($reflector)
    +        );
    +    }
    +
    +    /**
    +     * Print the function params.
    +     *
    +     * @param \ReflectionFunctionAbstract $reflector
    +     *
    +     * @return string
    +     */
    +    private static function formatFunctionParams(\ReflectionFunctionAbstract $reflector)
    +    {
    +        $params = array();
    +        foreach ($reflector->getParameters() as $param) {
    +            $hint = '';
    +            try {
    +                if ($param->isArray()) {
    +                    $hint = 'array ';
    +                } elseif ($class = $param->getClass()) {
    +                    $hint = sprintf('%s ', $class->getName());
    +                }
    +            } catch (\Exception $e) {
    +                // sometimes we just don't know...
    +                // bad class names, or autoloaded classes that haven't been loaded yet, or whathaveyou.
    +                // come to think of it, the only time I've seen this is with the intl extension.
    +
    +                // Hax: we'll try to extract it :P
    +                $chunks = explode('$' . $param->getName(), (string) $param);
    +                $chunks = explode(' ', trim($chunks[0]));
    +                $guess  = end($chunks);
    +
    +                $hint = sprintf('%s ', $guess);
    +            }
    +
    +            if ($param->isOptional()) {
    +                if (!$param->isDefaultValueAvailable()) {
    +                    $value     = 'unknown';
    +                    $typeStyle = 'urgent';
    +                } else {
    +                    $value     = $param->getDefaultValue();
    +                    $typeStyle = self::getTypeStyle($value);
    +                    $value     = is_array($value) ? 'array()' : is_null($value) ? 'null' : var_export($value, true);
    +                }
    +                $default   = sprintf(' = <%s>%s', $typeStyle, OutputFormatter::escape($value), $typeStyle);
    +            } else {
    +                $default = '';
    +            }
    +
    +            $params[] = sprintf(
    +                '%s%s$%s%s',
    +                $param->isPassedByReference() ? '&' : '',
    +                $hint,
    +                $param->getName(),
    +                $default
    +            );
    +        }
    +
    +        return $params;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Output/OutputPager.php b/core/vendor/psy/psysh/src/Psy/Output/OutputPager.php
    new file mode 100644
    index 0000000..9d8f5bf
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Output/OutputPager.php
    @@ -0,0 +1,26 @@
    +getStream());
    +    }
    +
    +    /**
    +     * Close the current pager process.
    +     */
    +    public function close()
    +    {
    +        // nothing to do here
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Output/ProcOutputPager.php b/core/vendor/psy/psysh/src/Psy/Output/ProcOutputPager.php
    new file mode 100644
    index 0000000..3fedd5f
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Output/ProcOutputPager.php
    @@ -0,0 +1,103 @@
    +stream = $output->getStream();
    +        $this->cmd    = $cmd;
    +    }
    +
    +    /**
    +     * Writes a message to the output.
    +     *
    +     * @param string $message A message to write to the output
    +     * @param bool   $newline Whether to add a newline or not
    +     *
    +     * @throws \RuntimeException When unable to write output (should never happen)
    +     */
    +    public function doWrite($message, $newline)
    +    {
    +        $pipe = $this->getPipe();
    +        if (false === @fwrite($pipe, $message . ($newline ? PHP_EOL : ''))) {
    +            // @codeCoverageIgnoreStart
    +            // should never happen
    +            throw new \RuntimeException('Unable to write output.');
    +            // @codeCoverageIgnoreEnd
    +        }
    +
    +        fflush($pipe);
    +    }
    +
    +    /**
    +     * Close the current pager process.
    +     */
    +    public function close()
    +    {
    +        if (isset($this->pipe)) {
    +            fclose($this->pipe);
    +        }
    +
    +        if (isset($this->proc)) {
    +            $exit = proc_close($this->proc);
    +            if ($exit !== 0) {
    +                throw new \RuntimeException('Error closing output stream');
    +            }
    +        }
    +
    +        unset($this->pipe, $this->proc);
    +    }
    +
    +    /**
    +     * Get a pipe for paging output.
    +     *
    +     * If no active pager process exists, fork one and return its input pipe.
    +     */
    +    private function getPipe()
    +    {
    +        if (!isset($this->pipe) || !isset($this->proc)) {
    +            $desc = array(array('pipe', 'r'), $this->stream, fopen('php://stderr', 'w'));
    +            $this->proc = proc_open($this->cmd, $desc, $pipes);
    +
    +            if (!is_resource($this->proc)) {
    +                throw new \RuntimeException('Error opening output stream');
    +            }
    +
    +            $this->pipe = $pipes[0];
    +        }
    +
    +        return $this->pipe;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Output/ShellOutput.php b/core/vendor/psy/psysh/src/Psy/Output/ShellOutput.php
    new file mode 100644
    index 0000000..b60200e
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Output/ShellOutput.php
    @@ -0,0 +1,203 @@
    +initFormatters();
    +
    +        if ($pager === null) {
    +            $this->pager = new PassthruPager($this);
    +        } elseif (is_string($pager)) {
    +            $this->pager = new ProcOutputPager($this, $pager);
    +        } elseif ($pager instanceof OutputPager) {
    +            $this->pager = $pager;
    +        } else {
    +            throw new \InvalidArgumentException('Unexpected pager parameter: ' . $pager);
    +        }
    +    }
    +
    +    /**
    +     * Page multiple lines of output.
    +     *
    +     * The output pager is started
    +     *
    +     * If $messages is callable, it will be called, passing this output instance
    +     * for rendering. Otherwise, all passed $messages are paged to output.
    +     *
    +     * Upon completion, the output pager is flushed.
    +     *
    +     * @param string|array|Closure $messages A string, array of strings or a callback.
    +     * @param int                  $type     (default: 0)
    +     */
    +    public function page($messages, $type = 0)
    +    {
    +        if (is_string($messages)) {
    +            $messages = (array) $messages;
    +        }
    +
    +        if (!is_array($messages) && !is_callable($messages)) {
    +            throw new \InvalidArgumentException('Paged output requires a string, array or callback.');
    +        }
    +
    +        $this->startPaging();
    +
    +        if (is_callable($messages)) {
    +            $messages($this);
    +        } else {
    +            $this->write($messages, true, $type);
    +        }
    +
    +        $this->stopPaging();
    +    }
    +
    +    /**
    +     * Start sending output to the output pager.
    +     */
    +    public function startPaging()
    +    {
    +        $this->paging++;
    +    }
    +
    +    /**
    +     * Stop paging output and flush the output pager.
    +     */
    +    public function stopPaging()
    +    {
    +        $this->paging--;
    +        $this->closePager();
    +    }
    +
    +    /**
    +     * Writes a message to the output.
    +     *
    +     * Optionally, pass `$type | self::NUMBER_LINES` as the $type parameter to
    +     * number the lines of output.
    +     *
    +     * @throws \InvalidArgumentException When unknown output type is given
    +     *
    +     * @param string|array $messages The message as an array of lines or a single string
    +     * @param bool         $newline  Whether to add a newline or not
    +     * @param int          $type     The type of output
    +     */
    +    public function write($messages, $newline = false, $type = 0)
    +    {
    +        if ($this->getVerbosity() === self::VERBOSITY_QUIET) {
    +            return;
    +        }
    +
    +        $messages = (array) $messages;
    +
    +        if ($type & self::NUMBER_LINES) {
    +            $pad = strlen((string) count($messages));
    +            $template = $this->isDecorated() ? ": %s" : "%{$pad}s: %s";
    +
    +            if ($type & self::OUTPUT_RAW) {
    +                $messages = array_map(array('Symfony\Component\Console\Formatter\OutputFormatter', 'escape'), $messages);
    +            }
    +
    +            foreach ($messages as $i => $line) {
    +                $messages[$i] = sprintf($template, $i, $line);
    +            }
    +
    +            // clean this up for super.
    +            $type = $type & ~self::NUMBER_LINES & ~self::OUTPUT_RAW;
    +        }
    +
    +        parent::write($messages, $newline, $type);
    +    }
    +
    +    /**
    +     * Writes a message to the output.
    +     *
    +     * Handles paged output, or writes directly to the output stream.
    +     *
    +     * @param string $message A message to write to the output
    +     * @param bool   $newline Whether to add a newline or not
    +     */
    +    public function doWrite($message, $newline)
    +    {
    +        if ($this->paging > 0) {
    +            $this->pager->doWrite($message, $newline);
    +        } else {
    +            parent::doWrite($message, $newline);
    +        }
    +    }
    +
    +    /**
    +     * Flush and close the output pager.
    +     */
    +    private function closePager()
    +    {
    +        if ($this->paging <= 0) {
    +            $this->pager->close();
    +        }
    +    }
    +
    +    /**
    +     * Initialize output formatter styles.
    +     */
    +    private function initFormatters()
    +    {
    +        $formatter = $this->getFormatter();
    +
    +        $formatter->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
    +        $formatter->setStyle('aside',   new OutputFormatterStyle('blue'));
    +        $formatter->setStyle('strong',  new OutputFormatterStyle(null, null, array('bold')));
    +        $formatter->setStyle('return',  new OutputFormatterStyle('cyan'));
    +        $formatter->setStyle('urgent',  new OutputFormatterStyle('red'));
    +        $formatter->setStyle('hidden',  new OutputFormatterStyle('black'));
    +
    +        // Visibility
    +        $formatter->setStyle('public',    new OutputFormatterStyle(null, null, array('bold')));
    +        $formatter->setStyle('protected', new OutputFormatterStyle('yellow'));
    +        $formatter->setStyle('private',   new OutputFormatterStyle('red'));
    +        $formatter->setStyle('global',    new OutputFormatterStyle('cyan', null, array('bold')));
    +        $formatter->setStyle('const',     new OutputFormatterStyle('cyan'));
    +        $formatter->setStyle('class',     new OutputFormatterStyle('blue', null, array('underscore')));
    +        $formatter->setStyle('function',  new OutputFormatterStyle(null));
    +        $formatter->setStyle('default',   new OutputFormatterStyle(null));
    +
    +        // Types
    +        $formatter->setStyle('number',   new OutputFormatterStyle('magenta'));
    +        $formatter->setStyle('string',   new OutputFormatterStyle('green'));
    +        $formatter->setStyle('bool',     new OutputFormatterStyle('cyan'));
    +        $formatter->setStyle('keyword',  new OutputFormatterStyle('yellow'));
    +        $formatter->setStyle('comment',  new OutputFormatterStyle('blue'));
    +        $formatter->setStyle('object',   new OutputFormatterStyle('blue'));
    +        $formatter->setStyle('resource', new OutputFormatterStyle('yellow'));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/ParserFactory.php b/core/vendor/psy/psysh/src/Psy/ParserFactory.php
    new file mode 100644
    index 0000000..22b3b99
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/ParserFactory.php
    @@ -0,0 +1,91 @@
    += 2.0 — does.
    +     *
    +     * @return bool
    +     */
    +    public function hasKindsSupport()
    +    {
    +        return class_exists('PhpParser\ParserFactory');
    +    }
    +
    +    /**
    +     * Default kind (if supported, based on current interpreter's version).
    +     *
    +     * @return string|null
    +     */
    +    public function getDefaultKind()
    +    {
    +        if ($this->hasKindsSupport()) {
    +            return version_compare(PHP_VERSION, '7.0', '>=') ? static::ONLY_PHP7 : static::ONLY_PHP5;
    +        }
    +    }
    +
    +    /**
    +     * New parser instance with given kind.
    +     *
    +     * @param string|null $kind One of class constants (only for PHP parser 2.0 and above).
    +     *
    +     * @return Parser
    +     */
    +    public function createParser($kind = null)
    +    {
    +        if ($this->hasKindsSupport()) {
    +            $originalFactory = new OriginalParserFactory();
    +
    +            $kind = $kind ?: $this->getDefaultKind();
    +
    +            if (!in_array($kind, static::getPossibleKinds())) {
    +                throw new \InvalidArgumentException('Unknown parser kind');
    +            }
    +
    +            $parser = $originalFactory->create(constant('PhpParser\ParserFactory::' . $kind));
    +        } else {
    +            if ($kind !== null) {
    +                throw new \InvalidArgumentException('Install PHP Parser v2.x to specify parser kind');
    +            }
    +
    +            $parser = new Parser(new Lexer());
    +        }
    +
    +        return $parser;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Readline/GNUReadline.php b/core/vendor/psy/psysh/src/Psy/Readline/GNUReadline.php
    new file mode 100644
    index 0000000..0a37b73
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Readline/GNUReadline.php
    @@ -0,0 +1,155 @@
    +historyFile = $historyFile;
    +        $this->historySize = $historySize;
    +        $this->eraseDups = $eraseDups;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function addHistory($line)
    +    {
    +        if ($res = readline_add_history($line)) {
    +            $this->writeHistory();
    +        }
    +
    +        return $res;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function clearHistory()
    +    {
    +        if ($res = readline_clear_history()) {
    +            $this->writeHistory();
    +        }
    +
    +        return $res;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function listHistory()
    +    {
    +        return readline_list_history();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readHistory()
    +    {
    +        // Workaround PHP bug #69054
    +        //
    +        // If open_basedir is set, readline_read_history() segfaults. This will be fixed in 5.6.7:
    +        //
    +        //     https://github.com/php/php-src/blob/423a057023ef3c00d2ffc16a6b43ba01d0f71796/NEWS#L19-L21
    +        //
    +        // TODO: add a PHP version check after next point release
    +        if (!ini_get('open_basedir')) {
    +            readline_read_history();
    +        }
    +        readline_clear_history();
    +
    +        return readline_read_history($this->historyFile);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readline($prompt = null)
    +    {
    +        return readline($prompt);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function redisplay()
    +    {
    +        readline_redisplay();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeHistory()
    +    {
    +        // We have to write history first, since it is used
    +        // by Libedit to list history
    +        $res = readline_write_history($this->historyFile);
    +        if (!$res || !$this->eraseDups && !$this->historySize > 0) {
    +            return $res;
    +        }
    +
    +        $hist = $this->listHistory();
    +        if (!$hist) {
    +            return true;
    +        }
    +
    +        if ($this->eraseDups) {
    +            // flip-flip technique: removes duplicates, latest entries win.
    +            $hist = array_flip(array_flip($hist));
    +            // sort on keys to get the order back
    +            ksort($hist);
    +        }
    +
    +        if ($this->historySize > 0) {
    +            $histsize = count($hist);
    +            if ($histsize > $this->historySize) {
    +                $hist = array_slice($hist, $histsize - $this->historySize);
    +            }
    +        }
    +
    +        readline_clear_history();
    +        foreach ($hist as $line) {
    +            readline_add_history($line);
    +        }
    +
    +        return readline_write_history($this->historyFile);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Readline/Libedit.php b/core/vendor/psy/psysh/src/Psy/Readline/Libedit.php
    new file mode 100644
    index 0000000..6d4440f
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Readline/Libedit.php
    @@ -0,0 +1,83 @@
    +historyFile);
    +        if (!$history) {
    +            return array();
    +        }
    +
    +        // libedit doesn't seem to support non-unix line separators.
    +        $history = explode("\n", $history);
    +
    +        // shift the history signature, ensure it's valid
    +        if (array_shift($history) !== '_HiStOrY_V2_') {
    +            return array();
    +        }
    +
    +        // decode the line
    +        $history = array_map(array($this, 'parseHistoryLine'), $history);
    +        // filter empty lines & comments
    +        return array_values(array_filter($history));
    +    }
    +
    +    /**
    +     * From GNUReadline (readline/histfile.c & readline/histexpand.c):
    +     * lines starting with "\0" are comments or timestamps;
    +     * if "\0" is found in an entry,
    +     * everything from it until the next line is a comment.
    +     *
    +     * @param string $line The history line to parse.
    +     *
    +     * @return string | null
    +     */
    +    protected function parseHistoryLine($line)
    +    {
    +        // empty line, comment or timestamp
    +        if (!$line || $line[0] === "\0") {
    +            return;
    +        }
    +        // if "\0" is found in an entry, then
    +        // everything from it until the end of line is a comment.
    +        if (($pos = strpos($line, "\0")) !== false) {
    +            $line = substr($line, 0, $pos);
    +        }
    +
    +        return ($line !== '') ? Str::unvis($line) : null;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Readline/Readline.php b/core/vendor/psy/psysh/src/Psy/Readline/Readline.php
    new file mode 100644
    index 0000000..54d0491
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Readline/Readline.php
    @@ -0,0 +1,76 @@
    +history     = array();
    +        $this->historySize = $historySize;
    +        $this->eraseDups   = $eraseDups;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function addHistory($line)
    +    {
    +        if ($this->eraseDups) {
    +            if (($key = array_search($line, $this->history)) !== false) {
    +                unset($this->history[$key]);
    +            }
    +        }
    +
    +        $this->history[] = $line;
    +
    +        if ($this->historySize > 0) {
    +            $histsize = count($this->history);
    +            if ($histsize > $this->historySize) {
    +                $this->history = array_slice($this->history, $histsize - $this->historySize);
    +            }
    +        }
    +
    +        $this->history = array_values($this->history);
    +
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function clearHistory()
    +    {
    +        $this->history = array();
    +
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function listHistory()
    +    {
    +        return $this->history;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readHistory()
    +    {
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     *
    +     * @throws BreakException if user hits Ctrl+D
    +     *
    +     * @return string
    +     */
    +    public function readline($prompt = null)
    +    {
    +        echo $prompt;
    +
    +        return rtrim(fgets($this->getStdin(), 1024));
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function redisplay()
    +    {
    +        // noop
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeHistory()
    +    {
    +        return true;
    +    }
    +
    +    /**
    +     * Get a STDIN file handle.
    +     *
    +     * @throws BreakException if user hits Ctrl+D
    +     *
    +     * @return resource
    +     */
    +    private function getStdin()
    +    {
    +        if (!isset($this->stdin)) {
    +            $this->stdin = fopen('php://stdin', 'r');
    +        }
    +
    +        if (feof($this->stdin)) {
    +            throw new BreakException('Ctrl+D');
    +        }
    +
    +        return $this->stdin;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Reflection/ReflectionConstant.php b/core/vendor/psy/psysh/src/Psy/Reflection/ReflectionConstant.php
    new file mode 100644
    index 0000000..874328e
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Reflection/ReflectionConstant.php
    @@ -0,0 +1,139 @@
    +class = $class;
    +        $this->name  = $name;
    +
    +        $constants = $class->getConstants();
    +        if (!array_key_exists($name, $constants)) {
    +            throw new \InvalidArgumentException('Unknown constant: ' . $name);
    +        }
    +
    +        $this->value = $constants[$name];
    +    }
    +
    +    /**
    +     * Gets the declaring class.
    +     *
    +     * @return string
    +     */
    +    public function getDeclaringClass()
    +    {
    +        return $this->class;
    +    }
    +
    +    /**
    +     * Gets the constant name.
    +     *
    +     * @return string
    +     */
    +    public function getName()
    +    {
    +        return $this->name;
    +    }
    +
    +    /**
    +     * Gets the value of the constant.
    +     *
    +     * @return mixed
    +     */
    +    public function getValue()
    +    {
    +        return $this->value;
    +    }
    +
    +    /**
    +     * Gets the constant's file name.
    +     *
    +     * Currently returns null, because if it returns a file name the signature
    +     * formatter will barf.
    +     */
    +    public function getFileName()
    +    {
    +        return;
    +        // return $this->class->getFileName();
    +    }
    +
    +    /**
    +     * Get the code start line.
    +     *
    +     * @throws \RuntimeException
    +     */
    +    public function getStartLine()
    +    {
    +        throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
    +    }
    +
    +    /**
    +     * Get the code end line.
    +     *
    +     * @throws \RuntimeException
    +     */
    +    public function getEndLine()
    +    {
    +        return $this->getStartLine();
    +    }
    +
    +    /**
    +     * Get the constant's docblock.
    +     *
    +     * @return false
    +     */
    +    public function getDocComment()
    +    {
    +        return false;
    +    }
    +
    +    /**
    +     * Export the constant? I don't think this is possible.
    +     *
    +     * @throws \RuntimeException
    +     */
    +    public static function export()
    +    {
    +        throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
    +    }
    +
    +    /**
    +     * To string.
    +     *
    +     * @return string
    +     */
    +    public function __toString()
    +    {
    +        return $this->getName();
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Shell.php b/core/vendor/psy/psysh/src/Psy/Shell.php
    new file mode 100644
    index 0000000..1cf7041
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Shell.php
    @@ -0,0 +1,887 @@
    +run();
    + *
    + * @author Justin Hileman 
    + */
    +class Shell extends Application
    +{
    +    const VERSION = 'v0.7.2';
    +
    +    const PROMPT      = '>>> ';
    +    const BUFF_PROMPT = '... ';
    +    const REPLAY      = '--> ';
    +    const RETVAL      = '=> ';
    +
    +    private $config;
    +    private $cleaner;
    +    private $output;
    +    private $readline;
    +    private $inputBuffer;
    +    private $code;
    +    private $codeBuffer;
    +    private $codeBufferOpen;
    +    private $context;
    +    private $includes;
    +    private $loop;
    +    private $outputWantsNewline = false;
    +    private $completion;
    +    private $tabCompletionMatchers = array();
    +
    +    /**
    +     * Create a new Psy Shell.
    +     *
    +     * @param Configuration $config (default: null)
    +     */
    +    public function __construct(Configuration $config = null)
    +    {
    +        $this->config   = $config ?: new Configuration();
    +        $this->cleaner  = $this->config->getCodeCleaner();
    +        $this->loop     = $this->config->getLoop();
    +        $this->context  = new Context();
    +        $this->includes = array();
    +        $this->readline = $this->config->getReadline();
    +
    +        parent::__construct('Psy Shell', self::VERSION);
    +
    +        $this->config->setShell($this);
    +    }
    +
    +    /**
    +     * Check whether the first thing in a backtrace is an include call.
    +     *
    +     * This is used by the psysh bin to decide whether to start a shell on boot,
    +     * or to simply autoload the library.
    +     */
    +    public static function isIncluded(array $trace)
    +    {
    +        return isset($trace[0]['function']) &&
    +          in_array($trace[0]['function'], array('require', 'include', 'require_once', 'include_once'));
    +    }
    +
    +    /**
    +     * Invoke a Psy Shell from the current context.
    +     *
    +     * For example:
    +     *
    +     *     foreach ($items as $item) {
    +     *         \Psy\Shell::debug(get_defined_vars());
    +     *     }
    +     *
    +     * If you would like your shell interaction to affect the state of the
    +     * current context, you can extract() the values returned from this call:
    +     *
    +     *     foreach ($items as $item) {
    +     *         extract(\Psy\Shell::debug(get_defined_vars()));
    +     *         var_dump($item); // will be whatever you set $item to in Psy Shell
    +     *     }
    +     *
    +     * Optionally, supply an object as the `$bind` parameter. This determines
    +     * the value `$this` will have in the shell, and sets up class scope so that
    +     * private and protected members are accessible:
    +     *
    +     *     class Foo {
    +     *         function bar() {
    +     *             \Psy\Shell::debug(get_defined_vars(), $this);
    +     *         }
    +     *     }
    +     *
    +     * This only really works in PHP 5.4+ and HHVM 3.5+, so upgrade already.
    +     *
    +     * @param array  $vars Scope variables from the calling context (default: array())
    +     * @param object $bind Bound object ($this) value for the shell
    +     *
    +     * @return array Scope variables from the debugger session.
    +     */
    +    public static function debug(array $vars = array(), $bind = null)
    +    {
    +        echo PHP_EOL;
    +
    +        if ($bind !== null) {
    +            $vars['this'] = $bind;
    +        }
    +
    +        $sh = new \Psy\Shell();
    +        $sh->setScopeVariables($vars);
    +        $sh->run();
    +
    +        return $sh->getScopeVariables();
    +    }
    +
    +    /**
    +     * Adds a command object.
    +     *
    +     * {@inheritdoc}
    +     *
    +     * @param BaseCommand $command A Symfony Console Command object
    +     *
    +     * @return BaseCommand The registered command
    +     */
    +    public function add(BaseCommand $command)
    +    {
    +        if ($ret = parent::add($command)) {
    +            if ($ret instanceof ContextAware) {
    +                $ret->setContext($this->context);
    +            }
    +
    +            if ($ret instanceof PresenterAware) {
    +                $ret->setPresenter($this->config->getPresenter());
    +            }
    +        }
    +
    +        return $ret;
    +    }
    +
    +    /**
    +     * Gets the default input definition.
    +     *
    +     * @return InputDefinition An InputDefinition instance
    +     */
    +    protected function getDefaultInputDefinition()
    +    {
    +        return new InputDefinition(array(
    +            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
    +            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
    +        ));
    +    }
    +
    +    /**
    +     * Gets the default commands that should always be available.
    +     *
    +     * @return array An array of default Command instances
    +     */
    +    protected function getDefaultCommands()
    +    {
    +        $hist = new Command\HistoryCommand();
    +        $hist->setReadline($this->readline);
    +
    +        return array(
    +            new Command\HelpCommand(),
    +            new Command\ListCommand(),
    +            new Command\DumpCommand(),
    +            new Command\DocCommand(),
    +            new Command\ShowCommand($this->config->colorMode()),
    +            new Command\WtfCommand(),
    +            new Command\WhereamiCommand($this->config->colorMode()),
    +            new Command\ThrowUpCommand(),
    +            new Command\TraceCommand(),
    +            new Command\BufferCommand(),
    +            new Command\ClearCommand(),
    +            // new Command\PsyVersionCommand(),
    +            $hist,
    +            new Command\ExitCommand(),
    +        );
    +    }
    +
    +    /**
    +     * @return array
    +     */
    +    protected function getTabCompletionMatchers()
    +    {
    +        if (empty($this->tabCompletionMatchers)) {
    +            $this->tabCompletionMatchers = array(
    +                new Matcher\CommandsMatcher($this->all()),
    +                new Matcher\KeywordsMatcher(),
    +                new Matcher\VariablesMatcher(),
    +                new Matcher\ConstantsMatcher(),
    +                new Matcher\FunctionsMatcher(),
    +                new Matcher\ClassNamesMatcher(),
    +                new Matcher\ClassMethodsMatcher(),
    +                new Matcher\ClassAttributesMatcher(),
    +                new Matcher\ObjectMethodsMatcher(),
    +                new Matcher\ObjectAttributesMatcher(),
    +            );
    +        }
    +
    +        return $this->tabCompletionMatchers;
    +    }
    +
    +    /**
    +     * @param array $matchers
    +     */
    +    public function addTabCompletionMatchers(array $matchers)
    +    {
    +        $this->tabCompletionMatchers = array_merge($matchers, $this->getTabCompletionMatchers());
    +    }
    +
    +    /**
    +     * Set the Shell output.
    +     *
    +     * @param OutputInterface $output
    +     */
    +    public function setOutput(OutputInterface $output)
    +    {
    +        $this->output = $output;
    +    }
    +
    +    /**
    +     * Runs the current application.
    +     *
    +     * @param InputInterface  $input  An Input instance
    +     * @param OutputInterface $output An Output instance
    +     *
    +     * @return int 0 if everything went fine, or an error code
    +     */
    +    public function run(InputInterface $input = null, OutputInterface $output = null)
    +    {
    +        $this->initializeTabCompletion();
    +
    +        if ($input === null && !isset($_SERVER['argv'])) {
    +            $input = new ArgvInput(array());
    +        }
    +
    +        if ($output === null) {
    +            $output = $this->config->getOutput();
    +        }
    +
    +        try {
    +            return parent::run($input, $output);
    +        } catch (\Exception $e) {
    +            $this->writeException($e);
    +        }
    +    }
    +
    +    /**
    +     * Runs the current application.
    +     *
    +     * @throws Exception if thrown via the `throw-up` command.
    +     *
    +     * @param InputInterface  $input  An Input instance
    +     * @param OutputInterface $output An Output instance
    +     *
    +     * @return int 0 if everything went fine, or an error code
    +     */
    +    public function doRun(InputInterface $input, OutputInterface $output)
    +    {
    +        $this->setOutput($output);
    +
    +        $this->resetCodeBuffer();
    +
    +        $this->setAutoExit(false);
    +        $this->setCatchExceptions(false);
    +
    +        $this->readline->readHistory();
    +
    +        // if ($this->config->useReadline()) {
    +        //     readline_completion_function(array($this, 'autocomplete'));
    +        // }
    +
    +        $this->output->writeln($this->getHeader());
    +
    +        try {
    +            $this->loop->run($this);
    +        } catch (ThrowUpException $e) {
    +            throw $e->getPrevious();
    +        }
    +    }
    +
    +    /**
    +     * Read user input.
    +     *
    +     * This will continue fetching user input until the code buffer contains
    +     * valid code.
    +     *
    +     * @throws BreakException if user hits Ctrl+D
    +     */
    +    public function getInput()
    +    {
    +        $this->codeBufferOpen = false;
    +
    +        do {
    +            // reset output verbosity (in case it was altered by a subcommand)
    +            $this->output->setVerbosity(ShellOutput::VERBOSITY_VERBOSE);
    +
    +            $input = $this->readline();
    +
    +            /*
    +             * Handle Ctrl+D. It behaves differently in different cases:
    +             *
    +             *   1) In an expression, like a function or "if" block, clear the input buffer
    +             *   2) At top-level session, behave like the exit command
    +             */
    +            if ($input === false) {
    +                $this->output->writeln('');
    +
    +                if ($this->hasCode()) {
    +                    $this->resetCodeBuffer();
    +                } else {
    +                    throw new BreakException('Ctrl+D');
    +                }
    +            }
    +
    +            // handle empty input
    +            if (trim($input) === '') {
    +                continue;
    +            }
    +
    +            if ($this->hasCommand($input)) {
    +                $this->readline->addHistory($input);
    +                $this->runCommand($input);
    +                continue;
    +            }
    +
    +            $this->addCode($input);
    +        } while (!$this->hasValidCode());
    +    }
    +
    +    /**
    +     * Pass the beforeLoop callback through to the Loop instance.
    +     *
    +     * @see Loop::beforeLoop
    +     */
    +    public function beforeLoop()
    +    {
    +        $this->loop->beforeLoop();
    +    }
    +
    +    /**
    +     * Pass the afterLoop callback through to the Loop instance.
    +     *
    +     * @see Loop::afterLoop
    +     */
    +    public function afterLoop()
    +    {
    +        $this->loop->afterLoop();
    +    }
    +
    +    /**
    +     * Set the variables currently in scope.
    +     *
    +     * @param array $vars
    +     */
    +    public function setScopeVariables(array $vars)
    +    {
    +        $this->context->setAll($vars);
    +    }
    +
    +    /**
    +     * Return the set of variables currently in scope.
    +     *
    +     * @return array Associative array of scope variables.
    +     */
    +    public function getScopeVariables()
    +    {
    +        return $this->context->getAll();
    +    }
    +
    +    /**
    +     * Get the set of variable names currently in scope.
    +     *
    +     * @return array Array of variable names.
    +     */
    +    public function getScopeVariableNames()
    +    {
    +        return array_keys($this->context->getAll());
    +    }
    +
    +    /**
    +     * Get a scope variable value by name.
    +     *
    +     * @param string $name
    +     *
    +     * @return mixed
    +     */
    +    public function getScopeVariable($name)
    +    {
    +        return $this->context->get($name);
    +    }
    +
    +    /**
    +     * Add includes, to be parsed and executed before running the interactive shell.
    +     *
    +     * @param array $includes
    +     */
    +    public function setIncludes(array $includes = array())
    +    {
    +        $this->includes = $includes;
    +    }
    +
    +    /**
    +     * Get PHP files to be parsed and executed before running the interactive shell.
    +     *
    +     * @return array
    +     */
    +    public function getIncludes()
    +    {
    +        return array_merge($this->config->getDefaultIncludes(), $this->includes);
    +    }
    +
    +    /**
    +     * Check whether this shell's code buffer contains code.
    +     *
    +     * @return bool True if the code buffer contains code.
    +     */
    +    public function hasCode()
    +    {
    +        return !empty($this->codeBuffer);
    +    }
    +
    +    /**
    +     * Check whether the code in this shell's code buffer is valid.
    +     *
    +     * If the code is valid, the code buffer should be flushed and evaluated.
    +     *
    +     * @return bool True if the code buffer content is valid.
    +     */
    +    protected function hasValidCode()
    +    {
    +        return !$this->codeBufferOpen && $this->code !== false;
    +    }
    +
    +    /**
    +     * Add code to the code buffer.
    +     *
    +     * @param string $code
    +     */
    +    public function addCode($code)
    +    {
    +        try {
    +            // Code lines ending in \ keep the buffer open
    +            if (substr(rtrim($code), -1) === '\\') {
    +                $this->codeBufferOpen = true;
    +                $code = substr(rtrim($code), 0, -1);
    +            } else {
    +                $this->codeBufferOpen = false;
    +            }
    +
    +            $this->codeBuffer[] = $code;
    +            $this->code         = $this->cleaner->clean($this->codeBuffer, $this->config->requireSemicolons());
    +        } catch (\Exception $e) {
    +            // Add failed code blocks to the readline history.
    +            $this->readline->addHistory(implode("\n", $this->codeBuffer));
    +            throw $e;
    +        }
    +    }
    +
    +    /**
    +     * Get the current code buffer.
    +     *
    +     * This is useful for commands which manipulate the buffer.
    +     *
    +     * @return array
    +     */
    +    public function getCodeBuffer()
    +    {
    +        return $this->codeBuffer;
    +    }
    +
    +    /**
    +     * Run a Psy Shell command given the user input.
    +     *
    +     * @throws InvalidArgumentException if the input is not a valid command.
    +     *
    +     * @param string $input User input string
    +     *
    +     * @return mixed Who knows?
    +     */
    +    protected function runCommand($input)
    +    {
    +        $command = $this->getCommand($input);
    +
    +        if (empty($command)) {
    +            throw new \InvalidArgumentException('Command not found: ' . $input);
    +        }
    +
    +        $input = new StringInput(str_replace('\\', '\\\\', rtrim($input, " \t\n\r\0\x0B;")));
    +
    +        if ($input->hasParameterOption(array('--help', '-h'))) {
    +            $helpCommand = $this->get('help');
    +            $helpCommand->setCommand($command);
    +
    +            return $helpCommand->run($input, $this->output);
    +        }
    +
    +        return $command->run($input, $this->output);
    +    }
    +
    +    /**
    +     * Reset the current code buffer.
    +     *
    +     * This should be run after evaluating user input, catching exceptions, or
    +     * on demand by commands such as BufferCommand.
    +     */
    +    public function resetCodeBuffer()
    +    {
    +        $this->codeBuffer = array();
    +        $this->code       = false;
    +    }
    +
    +    /**
    +     * Inject input into the input buffer.
    +     *
    +     * This is useful for commands which want to replay history.
    +     *
    +     * @param string|array $input
    +     */
    +    public function addInput($input)
    +    {
    +        foreach ((array) $input as $line) {
    +            $this->inputBuffer[] = $line;
    +        }
    +    }
    +
    +    /**
    +     * Flush the current (valid) code buffer.
    +     *
    +     * If the code buffer is valid, resets the code buffer and returns the
    +     * current code.
    +     *
    +     * @return string PHP code buffer contents.
    +     */
    +    public function flushCode()
    +    {
    +        if ($this->hasValidCode()) {
    +            $this->readline->addHistory(implode("\n", $this->codeBuffer));
    +            $code = $this->code;
    +            $this->resetCodeBuffer();
    +
    +            return $code;
    +        }
    +    }
    +
    +    /**
    +     * Get the current evaluation scope namespace.
    +     *
    +     * @see CodeCleaner::getNamespace
    +     *
    +     * @return string Current code namespace.
    +     */
    +    public function getNamespace()
    +    {
    +        if ($namespace = $this->cleaner->getNamespace()) {
    +            return implode('\\', $namespace);
    +        }
    +    }
    +
    +    /**
    +     * Write a string to stdout.
    +     *
    +     * This is used by the shell loop for rendering output from evaluated code.
    +     *
    +     * @param string $out
    +     * @param int    $phase Output buffering phase
    +     */
    +    public function writeStdout($out, $phase = PHP_OUTPUT_HANDLER_END)
    +    {
    +        $isCleaning = false;
    +        if (version_compare(PHP_VERSION, '5.4', '>=')) {
    +            $isCleaning = $phase & PHP_OUTPUT_HANDLER_CLEAN;
    +        }
    +
    +        // Incremental flush
    +        if ($out !== '' && !$isCleaning) {
    +            $this->output->write($out, false, ShellOutput::OUTPUT_RAW);
    +            $this->outputWantsNewline = (substr($out, -1) !== "\n");
    +        }
    +
    +        // Output buffering is done!
    +        if ($this->outputWantsNewline && $phase & PHP_OUTPUT_HANDLER_END) {
    +            $this->output->writeln(sprintf('', $this->config->useUnicode() ? '⏎' : '\\n'));
    +            $this->outputWantsNewline = false;
    +        }
    +    }
    +
    +    /**
    +     * Write a return value to stdout.
    +     *
    +     * The return value is formatted or pretty-printed, and rendered in a
    +     * visibly distinct manner (in this case, as cyan).
    +     *
    +     * @see self::presentValue
    +     *
    +     * @param mixed $ret
    +     */
    +    public function writeReturnValue($ret)
    +    {
    +        $this->context->setReturnValue($ret);
    +        $ret    = $this->presentValue($ret);
    +        $indent = str_repeat(' ', strlen(self::RETVAL));
    +
    +        $this->output->writeln(self::RETVAL . str_replace(PHP_EOL, PHP_EOL . $indent, $ret));
    +    }
    +
    +    /**
    +     * Renders a caught Exception.
    +     *
    +     * Exceptions are formatted according to severity. ErrorExceptions which were
    +     * warnings or Strict errors aren't rendered as harshly as real errors.
    +     *
    +     * Stores $e as the last Exception in the Shell Context.
    +     *
    +     * @param \Exception      $e      An exception instance
    +     * @param OutputInterface $output An OutputInterface instance
    +     */
    +    public function writeException(\Exception $e)
    +    {
    +        $this->context->setLastException($e);
    +
    +        $message = $e->getMessage();
    +        if (!$e instanceof PsyException) {
    +            $message = sprintf('%s with message \'%s\'', get_class($e), $message);
    +        }
    +
    +        $severity = ($e instanceof \ErrorException) ? $this->getSeverity($e) : 'error';
    +        $this->output->writeln(sprintf('<%s>%s', $severity, OutputFormatter::escape($message), $severity));
    +
    +        $this->resetCodeBuffer();
    +    }
    +
    +    /**
    +     * Helper for getting an output style for the given ErrorException's level.
    +     *
    +     * @param \ErrorException $e
    +     *
    +     * @return string
    +     */
    +    protected function getSeverity(\ErrorException $e)
    +    {
    +        $severity = $e->getSeverity();
    +        if ($severity & error_reporting()) {
    +            switch ($severity) {
    +                case E_WARNING:
    +                case E_NOTICE:
    +                case E_CORE_WARNING:
    +                case E_COMPILE_WARNING:
    +                case E_USER_WARNING:
    +                case E_USER_NOTICE:
    +                case E_STRICT:
    +                    return 'warning';
    +
    +                default:
    +                    return 'error';
    +            }
    +        } else {
    +            // Since this is below the user's reporting threshold, it's always going to be a warning.
    +            return 'warning';
    +        }
    +    }
    +
    +    /**
    +     * Helper for throwing an ErrorException.
    +     *
    +     * This allows us to:
    +     *
    +     *     set_error_handler(array($psysh, 'handleError'));
    +     *
    +     * Unlike ErrorException::throwException, this error handler respects the
    +     * current error_reporting level; i.e. it logs warnings and notices, but
    +     * doesn't throw an exception unless it's above the current error_reporting
    +     * threshold. This should probably only be used in the inner execution loop
    +     * of the shell, as most of the time a thrown exception is much more useful.
    +     *
    +     * If the error type matches the `errorLoggingLevel` config, it will be
    +     * logged as well, regardless of the `error_reporting` level.
    +     *
    +     * @see \Psy\Exception\ErrorException::throwException
    +     * @see \Psy\Shell::writeException
    +     *
    +     * @throws \Psy\Exception\ErrorException depending on the current error_reporting level.
    +     *
    +     * @param int    $errno   Error type
    +     * @param string $errstr  Message
    +     * @param string $errfile Filename
    +     * @param int    $errline Line number
    +     */
    +    public function handleError($errno, $errstr, $errfile, $errline)
    +    {
    +        if ($errno & error_reporting()) {
    +            ErrorException::throwException($errno, $errstr, $errfile, $errline);
    +        } elseif ($errno & $this->config->errorLoggingLevel()) {
    +            // log it and continue...
    +            $this->writeException(new ErrorException($errstr, 0, $errno, $errfile, $errline));
    +        }
    +    }
    +
    +    /**
    +     * Format a value for display.
    +     *
    +     * @see Presenter::present
    +     *
    +     * @param mixed $val
    +     *
    +     * @return string Formatted value
    +     */
    +    protected function presentValue($val)
    +    {
    +        return $this->config->getPresenter()->present($val);
    +    }
    +
    +    /**
    +     * Get a command (if one exists) for the current input string.
    +     *
    +     * @param string $input
    +     *
    +     * @return null|Command
    +     */
    +    protected function getCommand($input)
    +    {
    +        $input = new StringInput($input);
    +        if ($name = $input->getFirstArgument()) {
    +            return $this->get($name);
    +        }
    +    }
    +
    +    /**
    +     * Check whether a command is set for the current input string.
    +     *
    +     * @param string $input
    +     *
    +     * @return bool True if the shell has a command for the given input.
    +     */
    +    protected function hasCommand($input)
    +    {
    +        $input = new StringInput($input);
    +        if ($name = $input->getFirstArgument()) {
    +            return $this->has($name);
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * Get the current input prompt.
    +     *
    +     * @return string
    +     */
    +    protected function getPrompt()
    +    {
    +        return $this->hasCode() ? self::BUFF_PROMPT : self::PROMPT;
    +    }
    +
    +    /**
    +     * Read a line of user input.
    +     *
    +     * This will return a line from the input buffer (if any exist). Otherwise,
    +     * it will ask the user for input.
    +     *
    +     * If readline is enabled, this delegates to readline. Otherwise, it's an
    +     * ugly `fgets` call.
    +     *
    +     * @return string One line of user input.
    +     */
    +    protected function readline()
    +    {
    +        if (!empty($this->inputBuffer)) {
    +            $line = array_shift($this->inputBuffer);
    +            $this->output->writeln(sprintf('', self::REPLAY, OutputFormatter::escape($line)));
    +
    +            return $line;
    +        }
    +
    +        return $this->readline->readline($this->getPrompt());
    +    }
    +
    +    /**
    +     * Get the shell output header.
    +     *
    +     * @return string
    +     */
    +    protected function getHeader()
    +    {
    +        return sprintf('', $this->getVersion());
    +    }
    +
    +    /**
    +     * Get the current version of Psy Shell.
    +     *
    +     * @return string
    +     */
    +    public function getVersion()
    +    {
    +        $separator = $this->config->useUnicode() ? '—' : '-';
    +
    +        return sprintf('Psy Shell %s (PHP %s %s %s)', self::VERSION, phpversion(), $separator, php_sapi_name());
    +    }
    +
    +    /**
    +     * Get a PHP manual database instance.
    +     *
    +     * @return PDO|null
    +     */
    +    public function getManualDb()
    +    {
    +        return $this->config->getManualDb();
    +    }
    +
    +    /**
    +     * Autocomplete variable names.
    +     *
    +     * This is used by `readline` for tab completion.
    +     *
    +     * @param string $text
    +     *
    +     * @return mixed Array possible completions for the given input, if any.
    +     */
    +    protected function autocomplete($text)
    +    {
    +        $info = readline_info();
    +        // $line = substr($info['line_buffer'], 0, $info['end']);
    +
    +        // Check whether there's a command for this
    +        // $words = explode(' ', $line);
    +        // $firstWord = reset($words);
    +
    +        // check whether this is a variable...
    +        $firstChar = substr($info['line_buffer'], max(0, $info['end'] - strlen($text) - 1), 1);
    +        if ($firstChar === '$') {
    +            return $this->getScopeVariableNames();
    +        }
    +    }
    +
    +    /**
    +     * Initialize tab completion matchers.
    +     *
    +     * If tab completion is enabled this adds tab completion matchers to the
    +     * auto completer and sets context if needed.
    +     */
    +    protected function initializeTabCompletion()
    +    {
    +        // auto completer needs shell to be linked to configuration because of the context aware matchers
    +        if ($this->config->getTabCompletion()) {
    +            $this->completion = $this->config->getAutoCompleter();
    +            $this->addTabCompletionMatchers($this->config->getTabCompletionMatchers());
    +            foreach ($this->getTabCompletionMatchers() as $matcher) {
    +                if ($matcher instanceof ContextAware) {
    +                    $matcher->setContext($this->context);
    +                }
    +                $this->completion->addMatcher($matcher);
    +            }
    +            $this->completion->activate();
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/AutoCompleter.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/AutoCompleter.php
    new file mode 100644
    index 0000000..37a2081
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/AutoCompleter.php
    @@ -0,0 +1,102 @@
    +
    + */
    +class AutoCompleter
    +{
    +    /** @var Matcher\AbstractMatcher[]  */
    +    protected $matchers;
    +
    +    /**
    +     * Register a tab completion Matcher.
    +     *
    +     * @param AbstractMatcher $matcher
    +     */
    +    public function addMatcher(AbstractMatcher $matcher)
    +    {
    +        $this->matchers[] = $matcher;
    +    }
    +
    +    /**
    +     * Activate readline tab completion.
    +     */
    +    public function activate()
    +    {
    +        readline_completion_function(array(&$this, 'callback'));
    +    }
    +
    +    /**
    +     * Handle readline completion.
    +     *
    +     * @param string $input Readline current word
    +     * @param int    $index Current word index
    +     * @param array  $info  readline_info() data
    +     *
    +     * @return array
    +     */
    +    public function processCallback($input, $index, $info = array())
    +    {
    +        $line = substr($info['line_buffer'], 0, $info['end']);
    +        $tokens = token_get_all('matchers as $matcher) {
    +            if ($matcher->hasMatched($tokens)) {
    +                $matches = array_merge($matcher->getMatches($tokens), $matches);
    +            }
    +        }
    +
    +        $matches = array_unique($matches);
    +
    +        return !empty($matches) ? $matches : array('');
    +    }
    +
    +    /**
    +     * The readline_completion_function callback handler.
    +     *
    +     * @see processCallback
    +     *
    +     * @param $input
    +     * @param $index
    +     *
    +     * @return array
    +     */
    +    public function callback($input, $index)
    +    {
    +        return $this->processCallback($input, $index, readline_info());
    +    }
    +
    +    /**
    +     * Remove readline callback handler on destruct.
    +     */
    +    public function __destruct()
    +    {
    +        // PHP didn't implement the whole readline API when they first switched
    +        // to libedit. And they still haven't.
    +        //
    +        // So this is a thing to make PsySH work on 5.3.x:
    +        if (function_exists('readline_callback_handler_remove')) {
    +            readline_callback_handler_remove();
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractContextAwareMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractContextAwareMatcher.php
    new file mode 100644
    index 0000000..0bf094f
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractContextAwareMatcher.php
    @@ -0,0 +1,65 @@
    +
    + */
    +abstract class AbstractContextAwareMatcher extends AbstractMatcher implements ContextAware
    +{
    +    /**
    +     * Context instance (for ContextAware interface).
    +     *
    +     * @var Context
    +     */
    +    protected $context;
    +
    +    /**
    +     * ContextAware interface.
    +     *
    +     * @param Context $context
    +     */
    +    public function setContext(Context $context)
    +    {
    +        $this->context = $context;
    +    }
    +
    +    /**
    +     * Get a Context variable by name.
    +     *
    +     * @param $var Variable name
    +     *
    +     * @return mixed
    +     */
    +    protected function getVariable($var)
    +    {
    +        return $this->context->get($var);
    +    }
    +
    +    /**
    +     * Get all variables in the current Context.
    +     *
    +     * @return array
    +     */
    +    protected function getVariables()
    +    {
    +        return $this->context->getAll();
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractMatcher.php
    new file mode 100644
    index 0000000..ca2a129
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractMatcher.php
    @@ -0,0 +1,186 @@
    +
    + */
    +abstract class AbstractMatcher
    +{
    +    /** Syntax types */
    +    const CONSTANT_SYNTAX = '^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$';
    +    const VAR_SYNTAX = '^\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$';
    +    const MISC_OPERATORS = '+-*/^|&';
    +    /** Token values */
    +    const T_OPEN_TAG = 'T_OPEN_TAG';
    +    const T_VARIABLE = 'T_VARIABLE';
    +    const T_OBJECT_OPERATOR = 'T_OBJECT_OPERATOR';
    +    const T_DOUBLE_COLON = 'T_DOUBLE_COLON';
    +    const T_NEW = 'T_NEW';
    +    const T_CLONE = 'T_CLONE';
    +    const T_NS_SEPARATOR = 'T_NS_SEPARATOR';
    +    const T_STRING = 'T_STRING';
    +    const T_WHITESPACE = 'T_WHITESPACE';
    +    const T_AND_EQUAL = 'T_AND_EQUAL';
    +    const T_BOOLEAN_AND = 'T_BOOLEAN_AND';
    +    const T_BOOLEAN_OR = 'T_BOOLEAN_OR';
    +
    +    const T_ENCAPSED_AND_WHITESPACE = 'T_ENCAPSED_AND_WHITESPACE';
    +    const T_REQUIRE = 'T_REQUIRE';
    +    const T_REQUIRE_ONCE = 'T_REQUIRE_ONCE';
    +    const T_INCLUDE = 'T_INCLUDE';
    +    const T_INCLUDE_ONCE = 'T_INCLUDE_ONCE';
    +
    +    /**
    +     * Check whether this matcher can provide completions for $tokens.
    +     *
    +     * @param array $tokens Tokenized readline input.
    +     *
    +     * @return bool
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        return false;
    +    }
    +
    +    /**
    +     * Get current readline input word.
    +     *
    +     * @param array $tokens Tokenized readline input (see token_get_all)
    +     *
    +     * @return string
    +     */
    +    protected function getInput(array $tokens)
    +    {
    +        $var = '';
    +        $firstToken = array_pop($tokens);
    +        if (self::tokenIs($firstToken, self::T_STRING)) {
    +            $var = $firstToken[1];
    +        }
    +
    +        return $var;
    +    }
    +
    +    /**
    +     * Get current namespace and class (if any) from readline input.
    +     *
    +     * @param array $tokens Tokenized readline input (see token_get_all)
    +     *
    +     * @return string
    +     */
    +    protected function getNamespaceAndClass($tokens)
    +    {
    +        $class = '';
    +        while (self::hasToken(
    +            array(self::T_NS_SEPARATOR, self::T_STRING),
    +            $token = array_pop($tokens)
    +        )) {
    +            $class = $token[1] . $class;
    +        }
    +
    +        return $class;
    +    }
    +
    +    /**
    +     * Provide tab completion matches for readline input.
    +     *
    +     * @param array $tokens information substracted with get_token_all
    +     * @param array $info   readline_info object
    +     *
    +     * @return array The matches resulting from the query
    +     */
    +    abstract public function getMatches(array $tokens, array $info = array());
    +
    +    /**
    +     * Check whether $word starts with $prefix.
    +     *
    +     * @param string $prefix
    +     * @param string $word
    +     *
    +     * @return bool
    +     */
    +    public static function startsWith($prefix, $word)
    +    {
    +        return preg_match(sprintf('#^%s#', $prefix), $word);
    +    }
    +
    +    /**
    +     * Check whether $token matches a given syntax pattern.
    +     *
    +     * @param mixed  $token  A PHP token (see token_get_all)
    +     * @param string $syntax A syntax pattern (default: variable pattern)
    +     *
    +     * @return bool
    +     */
    +    public static function hasSyntax($token, $syntax = self::VAR_SYNTAX)
    +    {
    +        if (!is_array($token)) {
    +            return false;
    +        }
    +
    +        $regexp = sprintf('#%s#', $syntax);
    +
    +        return (bool) preg_match($regexp, $token[1]);
    +    }
    +
    +    /**
    +     * Check whether $token type is $which.
    +     *
    +     * @param string $which A PHP token type
    +     * @param mixed  $token A PHP token (see token_get_all)
    +     *
    +     * @return bool
    +     */
    +    public static function tokenIs($token, $which)
    +    {
    +        if (!is_array($token)) {
    +            return false;
    +        }
    +
    +        return token_name($token[0]) === $which;
    +    }
    +
    +    /**
    +     * Check whether $token is an operator.
    +     *
    +     * @param mixed $token A PHP token (see token_get_all)
    +     *
    +     * @return bool
    +     */
    +    public static function isOperator($token)
    +    {
    +        if (!is_string($token)) {
    +            return false;
    +        }
    +
    +        return strpos(self::MISC_OPERATORS, $token) !== false;
    +    }
    +
    +    /**
    +     * Check whether $token type is present in $coll.
    +     *
    +     * @param array $coll  A list of token types
    +     * @param mixed $token A PHP token (see token_get_all)
    +     *
    +     * @return bool
    +     */
    +    public static function hasToken(array $coll, $token)
    +    {
    +        if (!is_array($token)) {
    +            return false;
    +        }
    +
    +        return in_array(token_name($token[0]), $coll);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassAttributesMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassAttributesMatcher.php
    new file mode 100644
    index 0000000..4d5e2f4
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassAttributesMatcher.php
    @@ -0,0 +1,79 @@
    +
    + */
    +class ClassAttributesMatcher extends AbstractMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $input = $this->getInput($tokens);
    +
    +        $firstToken = array_pop($tokens);
    +        if (self::tokenIs($firstToken, self::T_STRING)) {
    +            // second token is the nekudotayim operator
    +            array_pop($tokens);
    +        }
    +
    +        $class = $this->getNamespaceAndClass($tokens);
    +
    +        $reflection = new \ReflectionClass($class);
    +        $vars = array_merge(
    +            array_map(
    +                function ($var) {
    +                    return '$' . $var;
    +                },
    +                array_keys($reflection->getStaticProperties())
    +            ),
    +            array_keys($reflection->getConstants())
    +        );
    +
    +        return array_map(
    +            function ($name) use ($class) {
    +                return $class . '::' . $name;
    +            },
    +            array_filter(
    +                $vars,
    +                function ($var) use ($input) {
    +                    return AbstractMatcher::startsWith($input, $var);
    +                }
    +            )
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING):
    +            case self::tokenIs($token, self::T_DOUBLE_COLON):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodsMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodsMatcher.php
    new file mode 100644
    index 0000000..1791d37
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodsMatcher.php
    @@ -0,0 +1,71 @@
    +
    + */
    +class ClassMethodsMatcher extends AbstractMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $input = $this->getInput($tokens);
    +
    +        $firstToken = array_pop($tokens);
    +        if (self::tokenIs($firstToken, self::T_STRING)) {
    +            // second token is the nekudotayim operator
    +            array_pop($tokens);
    +        }
    +
    +        $class = $this->getNamespaceAndClass($tokens);
    +
    +        $reflection = new \ReflectionClass($class);
    +        $methods = $reflection->getMethods(\ReflectionMethod::IS_STATIC);
    +        $methods = array_map(function (\ReflectionMethod $method) {
    +            return $method->getName();
    +        }, $methods);
    +
    +        return array_map(
    +            function ($name) use ($class) {
    +                return $class . '::' . $name;
    +            },
    +            array_filter($methods, function ($method) use ($input) {
    +                return AbstractMatcher::startsWith($input, $method);
    +            })
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING):
    +            case self::tokenIs($token, self::T_DOUBLE_COLON):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassNamesMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassNamesMatcher.php
    new file mode 100644
    index 0000000..f79d257
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassNamesMatcher.php
    @@ -0,0 +1,77 @@
    +
    + */
    +class ClassNamesMatcher extends AbstractMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $class = $this->getNamespaceAndClass($tokens);
    +        if (strlen($class) > 0 && $class[0] === '\\') {
    +            $class = substr($class, 1, strlen($class));
    +        }
    +        $quotedClass = preg_quote($class);
    +
    +        return array_map(
    +            function ($className) use ($class) {
    +                // get the number of namespace separators
    +                $nsPos = substr_count($class, '\\');
    +                $pieces = explode('\\', $className);
    +                //$methods = Mirror::get($class);
    +                return implode('\\', array_slice($pieces, $nsPos, count($pieces)));
    +            },
    +            array_filter(
    +                get_declared_classes(),
    +                function ($className) use ($quotedClass) {
    +                    return AbstractMatcher::startsWith($quotedClass, $className);
    +                }
    +            )
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        $blacklistedTokens = array(
    +            self::T_INCLUDE, self::T_INCLUDE_ONCE, self::T_REQUIRE, self::T_REQUIRE_ONCE,
    +        );
    +
    +        switch (true) {
    +            case self::hasToken(array($blacklistedTokens), $token):
    +            case self::hasToken(array($blacklistedTokens), $prevToken):
    +            case is_string($token) && $token === '$':
    +                return false;
    +            case self::hasToken(array(self::T_NEW, self::T_OPEN_TAG, self::T_NS_SEPARATOR), $prevToken):
    +            case self::hasToken(array(self::T_NEW, self::T_OPEN_TAG, self::T_NS_SEPARATOR), $token):
    +            case self::hasToken(array(self::T_OPEN_TAG, self::T_VARIABLE), $token):
    +            case self::isOperator($token):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/CommandsMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/CommandsMatcher.php
    new file mode 100644
    index 0000000..a9a0fea
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/CommandsMatcher.php
    @@ -0,0 +1,114 @@
    +
    + */
    +class CommandsMatcher extends AbstractMatcher
    +{
    +    /** @var string[] */
    +    protected $commands = array();
    +
    +    /**
    +     * CommandsMatcher constructor.
    +     *
    +     * @param Command[] $commands
    +     */
    +    public function __construct(array $commands)
    +    {
    +        $this->setCommands($commands);
    +    }
    +
    +    /**
    +     * Set Commands for completion.
    +     *
    +     * @param Command[] $commands
    +     */
    +    public function setCommands(array $commands)
    +    {
    +        $names = array();
    +        foreach ($commands as $command) {
    +            $names = array_merge(array($command->getName()), $names);
    +            $names = array_merge($command->getAliases(), $names);
    +        }
    +        $this->commands = $names;
    +    }
    +
    +    /**
    +     * Check whether a command $name is defined.
    +     *
    +     * @param string $name
    +     *
    +     * @return bool
    +     */
    +    protected function isCommand($name)
    +    {
    +        return in_array($name, $this->commands);
    +    }
    +
    +    /**
    +     * Check whether input matches a defined command.
    +     *
    +     * @param string $name
    +     *
    +     * @return bool
    +     */
    +    protected function matchCommand($name)
    +    {
    +        foreach ($this->commands as $cmd) {
    +            if ($this->startsWith($name, $cmd)) {
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $input = $this->getInput($tokens);
    +
    +        return array_filter($this->commands, function ($command) use ($input) {
    +            return AbstractMatcher::startsWith($input, $command);
    +        });
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        /* $openTag */ array_shift($tokens);
    +        $command = array_shift($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($command, self::T_STRING) &&
    +                !$this->isCommand($command[1]) &&
    +                $this->matchCommand($command[1]) &&
    +                empty($tokens):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ConstantsMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ConstantsMatcher.php
    new file mode 100644
    index 0000000..dfce451
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ConstantsMatcher.php
    @@ -0,0 +1,54 @@
    +
    + */
    +class ConstantsMatcher extends AbstractMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $const = $this->getInput($tokens);
    +
    +        return array_filter(array_keys(get_defined_constants()), function ($constant) use ($const) {
    +            return AbstractMatcher::startsWith($const, $constant);
    +        });
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($prevToken, self::T_NEW):
    +            case self::tokenIs($prevToken, self::T_NS_SEPARATOR):
    +                return false;
    +            case self::hasToken(array(self::T_OPEN_TAG, self::T_STRING), $token):
    +            case self::isOperator($token):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionsMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionsMatcher.php
    new file mode 100644
    index 0000000..0cad85d
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionsMatcher.php
    @@ -0,0 +1,56 @@
    +
    + */
    +class FunctionsMatcher extends AbstractMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $func = $this->getInput($tokens);
    +
    +        $functions = get_defined_functions();
    +        $allFunctions = array_merge($functions['user'], $functions['internal']);
    +
    +        return array_filter($allFunctions, function ($function) use ($func) {
    +            return AbstractMatcher::startsWith($func, $function);
    +        });
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($prevToken, self::T_NEW):
    +                return false;
    +            case self::hasToken(array(self::T_OPEN_TAG, self::T_STRING), $token):
    +            case self::isOperator($token):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/KeywordsMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/KeywordsMatcher.php
    new file mode 100644
    index 0000000..510c807
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/KeywordsMatcher.php
    @@ -0,0 +1,85 @@
    +
    + */
    +class KeywordsMatcher extends AbstractMatcher
    +{
    +    protected $keywords = array(
    +        'array', 'clone', 'declare', 'die', 'echo', 'empty', 'eval', 'exit', 'include',
    +        'include_once', 'isset', 'list', 'print',  'require', 'require_once', 'unset',
    +    );
    +
    +    protected $mandatoryStartKeywords = array(
    +        'die', 'echo', 'print', 'unset',
    +    );
    +
    +    /**
    +     * Get all (completable) PHP keywords.
    +     *
    +     * @return array
    +     */
    +    public function getKeywords()
    +    {
    +        return $this->keywords;
    +    }
    +
    +    /**
    +     * Check whether $keyword is a (completable) PHP keyword.
    +     *
    +     * @param string $keyword
    +     *
    +     * @return bool
    +     */
    +    public function isKeyword($keyword)
    +    {
    +        return in_array($keyword, $this->keywords);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $input = $this->getInput($tokens);
    +
    +        return array_filter($this->keywords, function ($keyword) use ($input) {
    +            return AbstractMatcher::startsWith($input, $keyword);
    +        });
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::hasToken(array(self::T_OPEN_TAG, self::T_VARIABLE), $token):
    +//            case is_string($token) && $token === '$':
    +            case self::hasToken(array(self::T_OPEN_TAG, self::T_VARIABLE), $prevToken) &&
    +                self::tokenIs($token, self::T_STRING):
    +            case self::isOperator($token):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/MongoClientMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/MongoClientMatcher.php
    new file mode 100644
    index 0000000..4a75d00
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/MongoClientMatcher.php
    @@ -0,0 +1,71 @@
    +
    + */
    +class MongoClientMatcher extends AbstractContextAwareMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $input = $this->getInput($tokens);
    +
    +        $firstToken = array_pop($tokens);
    +        if (self::tokenIs($firstToken, self::T_STRING)) {
    +            // second token is the object operator
    +            array_pop($tokens);
    +        }
    +        $objectToken = array_pop($tokens);
    +        $objectName = str_replace('$', '', $objectToken[1]);
    +        $object = $this->getVariable($objectName);
    +
    +        if (!$object instanceof \MongoClient) {
    +            return array();
    +        }
    +
    +        $list = $object->listDBs();
    +
    +        return array_filter(
    +            array_map(function ($info) {
    +                return $info['name'];
    +            }, $list['databases']),
    +            function ($var) use ($input) {
    +                return AbstractMatcher::startsWith($input, $var);
    +            }
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($token, self::T_OBJECT_OPERATOR):
    +            case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/MongoDatabaseMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/MongoDatabaseMatcher.php
    new file mode 100644
    index 0000000..161c456
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/MongoDatabaseMatcher.php
    @@ -0,0 +1,67 @@
    +
    + */
    +class MongoDatabaseMatcher extends AbstractContextAwareMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $input = $this->getInput($tokens);
    +
    +        $firstToken = array_pop($tokens);
    +        if (self::tokenIs($firstToken, self::T_STRING)) {
    +            // second token is the object operator
    +            array_pop($tokens);
    +        }
    +        $objectToken = array_pop($tokens);
    +        $objectName = str_replace('$', '', $objectToken[1]);
    +        $object = $this->getVariable($objectName);
    +
    +        if (!$object instanceof \MongoDB) {
    +            return array();
    +        }
    +
    +        return array_filter(
    +            $object->getCollectionNames(),
    +            function ($var) use ($input) {
    +                return AbstractMatcher::startsWith($input, $var);
    +            }
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($token, self::T_OBJECT_OPERATOR):
    +            case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectAttributesMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectAttributesMatcher.php
    new file mode 100644
    index 0000000..c0c7dab
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectAttributesMatcher.php
    @@ -0,0 +1,71 @@
    +
    + */
    +class ObjectAttributesMatcher extends AbstractContextAwareMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $input = $this->getInput($tokens);
    +
    +        $firstToken = array_pop($tokens);
    +        if (self::tokenIs($firstToken, self::T_STRING)) {
    +            // second token is the object operator
    +            array_pop($tokens);
    +        }
    +        $objectToken = array_pop($tokens);
    +        $objectName = str_replace('$', '', $objectToken[1]);
    +
    +        try {
    +            $object = $this->getVariable($objectName);
    +        } catch (InvalidArgumentException $e) {
    +            return array();
    +        }
    +
    +        return array_filter(
    +            array_keys(get_class_vars(get_class($object))),
    +            function ($var) use ($input) {
    +                return AbstractMatcher::startsWith($input, $var);
    +            }
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($token, self::T_OBJECT_OPERATOR):
    +            case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodsMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodsMatcher.php
    new file mode 100644
    index 0000000..ebb9f56
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodsMatcher.php
    @@ -0,0 +1,71 @@
    +
    + */
    +class ObjectMethodsMatcher extends AbstractContextAwareMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $input = $this->getInput($tokens);
    +
    +        $firstToken = array_pop($tokens);
    +        if (self::tokenIs($firstToken, self::T_STRING)) {
    +            // second token is the object operator
    +            array_pop($tokens);
    +        }
    +        $objectToken = array_pop($tokens);
    +        $objectName = str_replace('$', '', $objectToken[1]);
    +
    +        try {
    +            $object = $this->getVariable($objectName);
    +        } catch (InvalidArgumentException $e) {
    +            return array();
    +        }
    +
    +        return array_filter(
    +            get_class_methods($object),
    +            function ($var) use ($input) {
    +                return AbstractMatcher::startsWith($input, $var);
    +            }
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +        $prevToken = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::tokenIs($token, self::T_OBJECT_OPERATOR):
    +            case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/VariablesMatcher.php b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/VariablesMatcher.php
    new file mode 100644
    index 0000000..4835ca4
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/TabCompletion/Matcher/VariablesMatcher.php
    @@ -0,0 +1,51 @@
    +
    + */
    +class VariablesMatcher extends AbstractContextAwareMatcher
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMatches(array $tokens, array $info = array())
    +    {
    +        $var = str_replace('$', '', $this->getInput($tokens));
    +
    +        return array_filter(array_keys($this->getVariables()), function ($variable) use ($var) {
    +            return AbstractMatcher::startsWith($var, $variable);
    +        });
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hasMatched(array $tokens)
    +    {
    +        $token = array_pop($tokens);
    +
    +        switch (true) {
    +            case self::hasToken(array(self::T_OPEN_TAG, self::T_VARIABLE), $token):
    +            case is_string($token) && $token === '$':
    +            case self::isOperator($token):
    +                return true;
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Util/Docblock.php b/core/vendor/psy/psysh/src/Psy/Util/Docblock.php
    new file mode 100644
    index 0000000..4e949da
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Util/Docblock.php
    @@ -0,0 +1,241 @@
    +
    + * @author Justin Hileman 
    + */
    +class Docblock
    +{
    +    /**
    +     * Tags in the docblock that have a whitespace-delimited number of parameters
    +     * (such as `@param type var desc` and `@return type desc`) and the names of
    +     * those parameters.
    +     *
    +     * @var array
    +     */
    +    public static $vectors = array(
    +        'throws' => array('type', 'desc'),
    +        'param'  => array('type', 'var', 'desc'),
    +        'return' => array('type', 'desc'),
    +    );
    +
    +    protected $reflector;
    +
    +    /**
    +     * The description of the symbol.
    +     *
    +     * @var string
    +     */
    +    public $desc;
    +
    +    /**
    +     * The tags defined in the docblock.
    +     *
    +     * The array has keys which are the tag names (excluding the @) and values
    +     * that are arrays, each of which is an entry for the tag.
    +     *
    +     * In the case where the tag name is defined in {@see DocBlock::$vectors} the
    +     * value within the tag-value array is an array in itself with keys as
    +     * described by {@see DocBlock::$vectors}.
    +     *
    +     * @var array
    +     */
    +    public $tags;
    +
    +    /**
    +     * The entire DocBlock comment that was parsed.
    +     *
    +     * @var string
    +     */
    +    public $comment;
    +
    +    /**
    +     * Docblock constructor.
    +     *
    +     * @param \Reflector $reflector
    +     */
    +    public function __construct(\Reflector $reflector)
    +    {
    +        $this->reflector = $reflector;
    +        $this->setComment($reflector->getDocComment());
    +    }
    +
    +    /**
    +     * Set and parse the docblock comment.
    +     *
    +     * @param string $comment The docblock
    +     */
    +    protected function setComment($comment)
    +    {
    +        $this->desc    = '';
    +        $this->tags    = array();
    +        $this->comment = $comment;
    +
    +        $this->parseComment($comment);
    +    }
    +
    +    /**
    +     * Find the length of the docblock prefix.
    +     *
    +     * @param array $lines
    +     *
    +     * @return int Prefix length
    +     */
    +    protected static function prefixLength(array $lines)
    +    {
    +        // find only lines with interesting things
    +        $lines = array_filter($lines, function ($line) {
    +            return substr($line, strspn($line, "* \t\n\r\0\x0B"));
    +        });
    +
    +        // if we sort the lines, we only have to compare two items
    +        sort($lines);
    +
    +        $first = reset($lines);
    +        $last  = end($lines);
    +
    +        // find the longest common substring
    +        $count = min(strlen($first), strlen($last));
    +        for ($i = 0; $i < $count; $i++) {
    +            if ($first[$i] !== $last[$i]) {
    +                return $i;
    +            }
    +        }
    +
    +        return $count;
    +    }
    +
    +    /**
    +     * Parse the comment into the component parts and set the state of the object.
    +     *
    +     * @param string $comment The docblock
    +     */
    +    protected function parseComment($comment)
    +    {
    +        // Strip the opening and closing tags of the docblock
    +        $comment = substr($comment, 3, -2);
    +
    +        // Split into arrays of lines
    +        $comment = array_filter(preg_split('/\r?\n\r?/', $comment));
    +
    +        // Trim asterisks and whitespace from the beginning and whitespace from the end of lines
    +        $prefixLength = self::prefixLength($comment);
    +        $comment = array_map(function ($line) use ($prefixLength) {
    +            return rtrim(substr($line, $prefixLength));
    +        }, $comment);
    +
    +        // Group the lines together by @tags
    +        $blocks = array();
    +        $b = -1;
    +        foreach ($comment as $line) {
    +            if (self::isTagged($line)) {
    +                $b++;
    +                $blocks[] = array();
    +            } elseif ($b === -1) {
    +                $b = 0;
    +                $blocks[] = array();
    +            }
    +            $blocks[$b][] = $line;
    +        }
    +
    +        // Parse the blocks
    +        foreach ($blocks as $block => $body) {
    +            $body = trim(implode("\n", $body));
    +
    +            if ($block === 0 && !self::isTagged($body)) {
    +                // This is the description block
    +                $this->desc = $body;
    +            } else {
    +                // This block is tagged
    +                $tag  = substr(self::strTag($body), 1);
    +                $body = ltrim(substr($body, strlen($tag) + 2));
    +
    +                if (isset(self::$vectors[$tag])) {
    +                    // The tagged block is a vector
    +                    $count = count(self::$vectors[$tag]);
    +                    if ($body) {
    +                        $parts = preg_split('/\s+/', $body, $count);
    +                    } else {
    +                        $parts = array();
    +                    }
    +
    +                    // Default the trailing values
    +                    $parts = array_pad($parts, $count, null);
    +
    +                    // Store as a mapped array
    +                    $this->tags[$tag][] = array_combine(self::$vectors[$tag], $parts);
    +                } else {
    +                    // The tagged block is only text
    +                    $this->tags[$tag][] = $body;
    +                }
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Whether or not a docblock contains a given @tag.
    +     *
    +     * @param string $tag The name of the @tag to check for
    +     *
    +     * @return bool
    +     */
    +    public function hasTag($tag)
    +    {
    +        return is_array($this->tags) && array_key_exists($tag, $this->tags);
    +    }
    +
    +    /**
    +     * The value of a tag.
    +     *
    +     * @param string $tag
    +     *
    +     * @return array
    +     */
    +    public function tag($tag)
    +    {
    +        return $this->hasTag($tag) ? $this->tags[$tag] : null;
    +    }
    +
    +    /**
    +     * Whether or not a string begins with a @tag.
    +     *
    +     * @param string $str
    +     *
    +     * @return bool
    +     */
    +    public static function isTagged($str)
    +    {
    +        return isset($str[1]) && $str[0] === '@' && ctype_alpha($str[1]);
    +    }
    +
    +    /**
    +     * The tag at the beginning of a string.
    +     *
    +     * @param string $str
    +     *
    +     * @return string|null
    +     */
    +    public static function strTag($str)
    +    {
    +        if (preg_match('/^@[a-z0-9_]+/', $str, $matches)) {
    +            return $matches[0];
    +        }
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Util/Json.php b/core/vendor/psy/psysh/src/Psy/Util/Json.php
    new file mode 100644
    index 0000000..3c79ce7
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Util/Json.php
    @@ -0,0 +1,35 @@
    +=')) {
    +            $opt |= JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
    +        }
    +
    +        return json_encode($val, $opt);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Util/Mirror.php b/core/vendor/psy/psysh/src/Psy/Util/Mirror.php
    new file mode 100644
    index 0000000..15c07b8
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Util/Mirror.php
    @@ -0,0 +1,94 @@
    +hasConstant($member)) {
    +            return new ReflectionConstant($class, $member);
    +        } elseif ($filter & self::METHOD && $class->hasMethod($member)) {
    +            return $class->getMethod($member);
    +        } elseif ($filter & self::PROPERTY && $class->hasProperty($member)) {
    +            return $class->getProperty($member);
    +        } elseif ($filter & self::STATIC_PROPERTY && $class->hasProperty($member) && $class->getProperty($member)->isStatic()) {
    +            return $class->getProperty($member);
    +        } else {
    +            throw new RuntimeException(sprintf(
    +                'Unknown member %s on class %s',
    +                $member,
    +                is_object($value) ? get_class($value) : $value
    +            ));
    +        }
    +    }
    +
    +    /**
    +     * Get a ReflectionClass (or ReflectionObject) if possible.
    +     *
    +     * @throws \InvalidArgumentException if $value is not a class name or instance.
    +     *
    +     * @param mixed $value
    +     *
    +     * @return \ReflectionClass
    +     */
    +    private static function getClass($value)
    +    {
    +        if (is_object($value)) {
    +            return new \ReflectionObject($value);
    +        }
    +
    +        if (!is_string($value)) {
    +            throw new \InvalidArgumentException('Mirror expects an object or class');
    +        } elseif (!class_exists($value) && !interface_exists($value) && !(function_exists('trait_exists') && trait_exists($value))) {
    +            throw new \InvalidArgumentException('Unknown class or function: ' . $value);
    +        }
    +
    +        return new \ReflectionClass($value);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/Util/Str.php b/core/vendor/psy/psysh/src/Psy/Util/Str.php
    new file mode 100644
    index 0000000..90abedc
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/Util/Str.php
    @@ -0,0 +1,114 @@
    +filter = $filter;
    +
    +        return parent::cloneVar($var, $filter);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function castResource(Stub $stub, $isNested)
    +    {
    +        return Caster::EXCLUDE_VERBOSE & $this->filter ? array() : parent::castResource($stub, $isNested);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/VarDumper/Dumper.php b/core/vendor/psy/psysh/src/Psy/VarDumper/Dumper.php
    new file mode 100644
    index 0000000..9db93e0
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/VarDumper/Dumper.php
    @@ -0,0 +1,103 @@
    +formatter = $formatter;
    +        parent::__construct();
    +        $this->setColors(false);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function enterHash(Cursor $cursor, $type, $class, $hasChild)
    +    {
    +        if (Cursor::HASH_INDEXED === $type || Cursor::HASH_ASSOC === $type) {
    +            $class = 0;
    +        }
    +        parent::enterHash($cursor, $type, $class, $hasChild);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function dumpKey(Cursor $cursor)
    +    {
    +        if (Cursor::HASH_INDEXED !== $cursor->hashType) {
    +            parent::dumpKey($cursor);
    +        }
    +    }
    +
    +    protected function style($style, $value, $attr = array())
    +    {
    +        if ('ref' === $style) {
    +            $value = strtr($value, '@', '#');
    +        }
    +        $style = $this->styles[$style];
    +        $value = "<{$style}>" . $this->formatter->escape($value) . "";
    +        $cchr = $this->styles['cchr'];
    +        $value = preg_replace_callback(self::$controlCharsRx, function ($c) use ($cchr) {
    +            switch ($c[0]) {
    +                case "\t":
    +                    $c = '\t';
    +                    break;
    +                case "\n":
    +                    $c = '\n';
    +                    break;
    +                case "\v":
    +                    $c = '\v';
    +                    break;
    +                case "\f":
    +                    $c = '\f';
    +                    break;
    +                case "\r":
    +                    $c = '\r';
    +                    break;
    +                case "\033":
    +                    $c = '\e';
    +                    break;
    +                default:
    +                    $c = sprintf('\x%02X', ord($c[0]));
    +                    break;
    +            }
    +
    +            return "<{$cchr}>{$c}";
    +        }, $value);
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function dumpLine($depth, $endOfValue = false)
    +    {
    +        if ($endOfValue && 0 < $depth) {
    +            $this->line .= ',';
    +        }
    +        $this->line = $this->formatter->format($this->line);
    +        parent::dumpLine($depth, $endOfValue);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/VarDumper/Presenter.php b/core/vendor/psy/psysh/src/Psy/VarDumper/Presenter.php
    new file mode 100644
    index 0000000..8c61a1f
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/VarDumper/Presenter.php
    @@ -0,0 +1,123 @@
    + 'number',
    +        'const'     => 'const',
    +        'str'       => 'string',
    +        'cchr'      => 'default',
    +        'note'      => 'class',
    +        'ref'       => 'default',
    +        'public'    => 'public',
    +        'protected' => 'protected',
    +        'private'   => 'private',
    +        'meta'      => 'comment',
    +        'key'       => 'comment',
    +        'index'     => 'number',
    +    );
    +
    +    public function __construct(OutputFormatter $formatter)
    +    {
    +        $this->dumper = new Dumper($formatter);
    +        $this->dumper->setStyles($this->styles);
    +
    +        $this->cloner = new Cloner();
    +        $this->cloner->addCasters(array('*' => function ($obj, array $a, Stub $stub, $isNested, $filter = 0) {
    +            if ($filter || $isNested) {
    +                if ($obj instanceof \Exception) {
    +                    $a = Caster::filter($a, Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_EMPTY, $this->exceptionsImportants);
    +                } else {
    +                    $a = Caster::filter($a, Caster::EXCLUDE_PROTECTED | Caster::EXCLUDE_PRIVATE);
    +                }
    +            }
    +
    +            return $a;
    +        }));
    +    }
    +
    +    /**
    +     * Register casters.
    +     *
    +     * @see http://symfony.com/doc/current/components/var_dumper/advanced.html#casters
    +     *
    +     * @param callable[] $casters A map of casters.
    +     */
    +    public function addCasters(array $casters)
    +    {
    +        $this->cloner->addCasters($casters);
    +    }
    +
    +    /**
    +     * Present a reference to the value.
    +     *
    +     * @param mixed $value
    +     *
    +     * @return string
    +     */
    +    public function presentRef($value)
    +    {
    +        return $this->present($value, 0);
    +    }
    +
    +    /**
    +     * Present a full representation of the value.
    +     *
    +     * If $depth is 0, the value will be presented as a ref instead.
    +     *
    +     * @param mixed $value
    +     * @param int   $depth   (default: null)
    +     * @param int   $options One of Presenter constants
    +     *
    +     * @return string
    +     */
    +    public function present($value, $depth = null, $options = 0)
    +    {
    +        $data = $this->cloner->cloneVar($value, !($options & self::VERBOSE) ? Caster::EXCLUDE_VERBOSE : 0);
    +
    +        if (null !== $depth) {
    +            $data = $data->withMaxDepth($depth);
    +        }
    +
    +        $output = '';
    +        $this->dumper->dump($data, function ($line, $depth) use (&$output) {
    +            if ($depth >= 0) {
    +                if ('' !== $output) {
    +                    $output .= PHP_EOL;
    +                }
    +                $output .= str_repeat('  ', $depth) . $line;
    +            }
    +        });
    +
    +        return OutputFormatter::escape($output);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/src/Psy/VarDumper/PresenterAware.php b/core/vendor/psy/psysh/src/Psy/VarDumper/PresenterAware.php
    new file mode 100644
    index 0000000..4b4999b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/src/Psy/VarDumper/PresenterAware.php
    @@ -0,0 +1,26 @@
    + Shell::VERSION,
    +            'PHP version'         => PHP_VERSION,
    +            'default includes'    => $config->getDefaultIncludes(),
    +            'require semicolons'  => $config->requireSemicolons(),
    +            'error logging level' => $config->errorLoggingLevel(),
    +            'config file'         => array(
    +                'default config file' => $config->getConfigFile(),
    +                'local config file'   => $config->getLocalConfigFile(),
    +                'PSYSH_CONFIG env'    => getenv('PSYSH_CONFIG'),
    +            ),
    +            // 'config dir'  => $config->getConfigDir(),
    +            // 'data dir'    => $config->getDataDir(),
    +            // 'runtime dir' => $config->getRuntimeDir(),
    +        );
    +
    +        if ($config->hasReadline()) {
    +            $info = readline_info();
    +
    +            $readline = array(
    +                'readline available' => true,
    +                'readline enabled'   => $config->useReadline(),
    +                'readline service'   => get_class($config->getReadline()),
    +                'readline library'   => $info['library_version'],
    +            );
    +
    +            if ($info['readline_name'] !== '') {
    +                $readline['readline name'] = $info['readline_name'];
    +            }
    +        } else {
    +            $readline = array(
    +                'readline available' => false,
    +            );
    +        }
    +
    +        $pcntl = array(
    +            'pcntl available' => function_exists('pcntl_signal'),
    +            'posix available' => function_exists('posix_getpid'),
    +        );
    +
    +        $history = array(
    +            'history file'     => $config->getHistoryFile(),
    +            'history size'     => $config->getHistorySize(),
    +            'erase duplicates' => $config->getEraseDuplicates(),
    +        );
    +
    +        $docs = array(
    +            'manual db file'   => $config->getManualDbFile(),
    +            'sqlite available' => true,
    +        );
    +
    +        try {
    +            if ($db = $config->getManualDb()) {
    +                if ($q = $db->query('SELECT * FROM meta;')) {
    +                    $q->setFetchMode(\PDO::FETCH_KEY_PAIR);
    +                    $meta = $q->fetchAll();
    +
    +                    foreach ($meta as $key => $val) {
    +                        switch ($key) {
    +                            case 'built_at':
    +                                $d = new \DateTime('@' . $val);
    +                                $val = $d->format(\DateTime::RFC2822);
    +                                break;
    +                        }
    +                        $key = 'db ' . str_replace('_', ' ', $key);
    +                        $docs[$key] = $val;
    +                    }
    +                } else {
    +                    $docs['db schema'] = '0.1.0';
    +                }
    +            }
    +        } catch (Exception\RuntimeException $e) {
    +            if ($e->getMessage() === 'SQLite PDO driver not found') {
    +                $docs['sqlite available'] = false;
    +            } else {
    +                throw $e;
    +            }
    +        }
    +
    +        $autocomplete = array(
    +            'tab completion enabled' => $config->getTabCompletion(),
    +            'custom matchers'        => array_map('get_class', $config->getTabCompletionMatchers()),
    +        );
    +
    +        return array_merge($core, compact('pcntl', 'readline', 'history', 'docs', 'autocomplete'));
    +    }
    +}
    +
    +if (!function_exists('Psy\bin')) {
    +    /**
    +     * `psysh` command line executable.
    +     *
    +     * @return Closure
    +     */
    +    function bin()
    +    {
    +        return function () {
    +            $usageException = null;
    +
    +            $input = new ArgvInput();
    +            try {
    +                $input->bind(new InputDefinition(array(
    +                    new InputOption('help',     'h',  InputOption::VALUE_NONE),
    +                    new InputOption('config',   'c',  InputOption::VALUE_REQUIRED),
    +                    new InputOption('version',  'v',  InputOption::VALUE_NONE),
    +                    new InputOption('cwd',      null, InputOption::VALUE_REQUIRED),
    +                    new InputOption('color',    null, InputOption::VALUE_NONE),
    +                    new InputOption('no-color', null, InputOption::VALUE_NONE),
    +
    +                    new InputArgument('include', InputArgument::IS_ARRAY),
    +                )));
    +            } catch (\RuntimeException $e) {
    +                $usageException = $e;
    +            }
    +
    +            $config = array();
    +
    +            // Handle --config
    +            if ($configFile = $input->getOption('config')) {
    +                $config['configFile'] = $configFile;
    +            }
    +
    +            // Handle --color and --no-color
    +            if ($input->getOption('color') && $input->getOption('no-color')) {
    +                $usageException = new \RuntimeException('Using both "--color" and "--no-color" options is invalid.');
    +            } elseif ($input->getOption('color')) {
    +                $config['colorMode'] = Configuration::COLOR_MODE_FORCED;
    +            } elseif ($input->getOption('no-color')) {
    +                $config['colorMode'] = Configuration::COLOR_MODE_DISABLED;
    +            }
    +
    +            $shell = new Shell(new Configuration($config));
    +
    +            // Handle --help
    +            if ($usageException !== null || $input->getOption('help')) {
    +                if ($usageException !== null) {
    +                    echo $usageException->getMessage() . PHP_EOL . PHP_EOL;
    +                }
    +
    +                $version = $shell->getVersion();
    +                $name    = basename(reset($_SERVER['argv']));
    +                echo <<getOption('version')) {
    +                echo $shell->getVersion() . PHP_EOL;
    +                exit(0);
    +            }
    +
    +            // Pass additional arguments to Shell as 'includes'
    +            $shell->setIncludes($input->getArgument('include'));
    +
    +            try {
    +                // And go!
    +                $shell->run();
    +            } catch (Exception $e) {
    +                echo $e->getMessage() . PHP_EOL;
    +
    +                // TODO: this triggers the "exited unexpectedly" logic in the
    +                // ForkingLoop, so we can't exit(1) after starting the shell...
    +                // fix this :)
    +
    +                // exit(1);
    +            }
    +        };
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/AutoloaderTest.php b/core/vendor/psy/psysh/test/Psy/Test/AutoloaderTest.php
    new file mode 100644
    index 0000000..3fb46d3
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/AutoloaderTest.php
    @@ -0,0 +1,23 @@
    +assertTrue(spl_autoload_unregister(array('Psy\Autoloader', 'autoload')));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/AbstractClassPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/AbstractClassPassTest.php
    new file mode 100644
    index 0000000..571fa6e
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/AbstractClassPassTest.php
    @@ -0,0 +1,61 @@
    +pass      = new AbstractClassPass();
    +        $this->traverser = new NodeTraverser();
    +        $this->traverser->addVisitor($this->pass);
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     */
    +    public function testProcessStatementFails($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        return array(
    +            array('class A { abstract function a(); }'),
    +            array('abstract class B { abstract function b() {} }'),
    +            array('abstract class B { abstract function b() { echo "yep"; } }'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider validStatements
    +     */
    +    public function testProcessStatementPasses($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function validStatements()
    +    {
    +        return array(
    +            array('abstract class C { function c() {} }'),
    +            array('abstract class D { abstract function d(); }'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/AssignThisVariablePassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/AssignThisVariablePassTest.php
    new file mode 100644
    index 0000000..6cf77fb
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/AssignThisVariablePassTest.php
    @@ -0,0 +1,62 @@
    +pass      = new AssignThisVariablePass();
    +        $this->traverser = new NodeTraverser();
    +        $this->traverser->addVisitor($this->pass);
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     */
    +    public function testProcessStatementFails($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        return array(
    +            array('$this = 3'),
    +            array('strtolower($this = "this")'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider validStatements
    +     */
    +    public function testProcessStatementPasses($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function validStatements()
    +    {
    +        return array(
    +            array('$this'),
    +            array('$a = $this'),
    +            array('$a = "this"; $$a = 3'),
    +            array('$$this = "b"'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CallTimePassByReferencePassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CallTimePassByReferencePassTest.php
    new file mode 100644
    index 0000000..3932f4c
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CallTimePassByReferencePassTest.php
    @@ -0,0 +1,73 @@
    +pass      = new CallTimePassByReferencePass();
    +        $this->traverser = new NodeTraverser();
    +        $this->traverser->addVisitor($this->pass);
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     */
    +    public function testProcessStatementFails($code)
    +    {
    +        if (version_compare(PHP_VERSION, '5.4', '<')) {
    +            $this->markTestSkipped();
    +        }
    +
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        return array(
    +            array('f(&$arg)'),
    +            array('$object->method($first, &$arg)'),
    +            array('$closure($first, &$arg, $last)'),
    +            array('A::b(&$arg)'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider validStatements
    +     */
    +    public function testProcessStatementPasses($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function validStatements()
    +    {
    +        $data = array(
    +            array('array(&$var)'),
    +            array('$a = &$b'),
    +            array('f(array(&$b))'),
    +        );
    +
    +        if (version_compare(PHP_VERSION, '5.4', '<')) {
    +            $data = array_merge($data, $this->invalidStatements());
    +        }
    +
    +        return $data;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CalledClassPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CalledClassPassTest.php
    new file mode 100644
    index 0000000..1cde523
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CalledClassPassTest.php
    @@ -0,0 +1,98 @@
    +pass      = new CalledClassPass();
    +        $this->traverser = new NodeTraverser();
    +        $this->traverser->addVisitor($this->pass);
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\ErrorException
    +     */
    +    public function testProcessStatementFails($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        return array(
    +            array('get_class()'),
    +            array('get_class(null)'),
    +            array('get_called_class()'),
    +            array('get_called_class(null)'),
    +            array('function foo() { return get_class(); }'),
    +            array('function foo() { return get_class(null); }'),
    +            array('function foo() { return get_called_class(); }'),
    +            array('function foo() { return get_called_class(null); }'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider validStatements
    +     */
    +    public function testProcessStatementPasses($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function validStatements()
    +    {
    +        return array(
    +            array('get_class($foo)'),
    +            array('get_class(bar())'),
    +            array('get_called_class($foo)'),
    +            array('get_called_class(bar())'),
    +            array('function foo($bar) { return get_class($bar); }'),
    +            array('function foo($bar) { return get_called_class($bar); }'),
    +            array('class Foo { function bar() { return get_class(); } }'),
    +            array('class Foo { function bar() { return get_class(null); } }'),
    +            array('class Foo { function bar() { return get_called_class(); } }'),
    +            array('class Foo { function bar() { return get_called_class(null); } }'),
    +            array('$foo = function () {}; $foo()'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider validTraitStatements
    +     */
    +    public function testProcessTraitStatementPasses($code)
    +    {
    +        if (version_compare(PHP_VERSION, '5.4', '<')) {
    +            $this->markTestSkipped();
    +        }
    +
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function validTraitStatements()
    +    {
    +        return array(
    +            array('trait Foo { function bar() { return get_class(); } }'),
    +            array('trait Foo { function bar() { return get_class(null); } }'),
    +            array('trait Foo { function bar() { return get_called_class(); } }'),
    +            array('trait Foo { function bar() { return get_called_class(null); } }'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CodeCleanerTestCase.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CodeCleanerTestCase.php
    new file mode 100644
    index 0000000..5a9bb73
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/CodeCleanerTestCase.php
    @@ -0,0 +1,98 @@
    +pass = $pass;
    +        if (!isset($this->traverser)) {
    +            $this->traverser = new NodeTraverser();
    +        }
    +        $this->traverser->addVisitor($this->pass);
    +    }
    +
    +    protected function parse($code, $prefix = 'getParser()->parse($code);
    +        } catch (\PhpParser\Error $e) {
    +            if (!$this->parseErrorIsEOF($e)) {
    +                throw ParseErrorException::fromParseError($e);
    +            }
    +
    +            try {
    +                // Unexpected EOF, try again with an implicit semicolon
    +                return $this->getParser()->parse($code . ';');
    +            } catch (\PhpParser\Error $e) {
    +                return false;
    +            }
    +        }
    +    }
    +
    +    protected function traverse(array $stmts)
    +    {
    +        return $this->traverser->traverse($stmts);
    +    }
    +
    +    protected function prettyPrint(array $stmts)
    +    {
    +        return $this->getPrinter()->prettyPrint($stmts);
    +    }
    +
    +    protected function assertProcessesAs($from, $to)
    +    {
    +        $stmts = $this->parse($from);
    +        $stmts = $this->traverse($stmts);
    +        $this->assertEquals($to, $this->prettyPrint($stmts));
    +    }
    +
    +    private function getParser()
    +    {
    +        if (!isset($this->parser)) {
    +            $parserFactory = new ParserFactory();
    +            $this->parser  = $parserFactory->createParser();
    +        }
    +
    +        return $this->parser;
    +    }
    +
    +    private function getPrinter()
    +    {
    +        if (!isset($this->printer)) {
    +            $this->printer = new Printer();
    +        }
    +
    +        return $this->printer;
    +    }
    +
    +    private function parseErrorIsEOF(\PhpParser\Error $e)
    +    {
    +        $msg = $e->getRawMessage();
    +
    +        return ($msg === 'Unexpected token EOF') || (strpos($msg, 'Syntax error, unexpected EOF') !== false);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ExitPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ExitPassTest.php
    new file mode 100644
    index 0000000..861afab
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ExitPassTest.php
    @@ -0,0 +1,51 @@
    +setPass(new ExitPass());
    +    }
    +
    +    /**
    +     * @dataProvider dataProviderExitStatement
    +     */
    +    public function testExitStatement($from, $to)
    +    {
    +        $this->assertProcessesAs($from, $to);
    +    }
    +
    +    /**
    +     * Data provider for testExitStatement.
    +     *
    +     * @return array
    +     */
    +    public function dataProviderExitStatement()
    +    {
    +        return array(
    +            array('exit;', $this->expectedExceptionString),
    +            array('exit();', $this->expectedExceptionString),
    +            array('die;', $this->expectedExceptionString),
    +            array('if (true) { exit; }', "if (true) {\n    $this->expectedExceptionString\n}"),
    +            array('if (false) { exit; }', "if (false) {\n    $this->expectedExceptionString\n}"),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/Fixtures/ClassWithCallStatic.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/Fixtures/ClassWithCallStatic.php
    new file mode 100644
    index 0000000..8f8624b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/Fixtures/ClassWithCallStatic.php
    @@ -0,0 +1,20 @@
    +pass      = new FunctionReturnInWriteContextPass();
    +        $this->traverser = new NodeTraverser();
    +        $this->traverser->addVisitor($this->pass);
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     * @expectedExceptionMessage Can't use function return value in write context
    +     */
    +    public function testProcessStatementFails($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        return array(
    +            array('f(&g())'),
    +            array('array(& $object->method())'),
    +            array('$a->method(& $closure())'),
    +            array('array(& A::b())'),
    +            array('f() = 5'),
    +        );
    +    }
    +
    +    public function testIsset()
    +    {
    +        try {
    +            $this->traverser->traverse($this->parse('isset(strtolower("A"))'));
    +            $this->fail();
    +        } catch (FatalErrorException $e) {
    +            if (version_compare(PHP_VERSION, '5.5', '>=')) {
    +                $this->assertContains(
    +                    'Cannot use isset() on the result of a function call (you can use "null !== func()" instead)',
    +                    $e->getMessage()
    +                );
    +            } else {
    +                $this->assertContains("Can't use function return value in write context", $e->getMessage());
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     * @expectedExceptionMessage Can't use function return value in write context
    +     */
    +    public function testEmpty()
    +    {
    +        if (version_compare(PHP_VERSION, '5.5', '>=')) {
    +            $this->markTestSkipped();
    +        }
    +
    +        $this->traverser->traverse($this->parse('empty(strtolower("A"))'));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ImplicitReturnPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ImplicitReturnPassTest.php
    new file mode 100644
    index 0000000..7186262
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ImplicitReturnPassTest.php
    @@ -0,0 +1,39 @@
    +setPass(new ImplicitReturnPass());
    +    }
    +
    +    /**
    +     * @dataProvider implicitReturns
    +     */
    +    public function testProcess($from, $to)
    +    {
    +        $this->assertProcessesAs($from, $to);
    +    }
    +
    +    public function implicitReturns()
    +    {
    +        return array(
    +            array('4',     'return 4;'),
    +            array('foo()', 'return foo();'),
    +            array('exit()', 'die;'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/InstanceOfPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/InstanceOfPassTest.php
    new file mode 100644
    index 0000000..db0d091
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/InstanceOfPassTest.php
    @@ -0,0 +1,74 @@
    +setPass(new InstanceOfPass());
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     */
    +    public function testProcessInvalidStatement($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        return array(
    +            array('null instanceof stdClass'),
    +            array('true instanceof stdClass'),
    +            array('9 instanceof stdClass'),
    +            array('1.0 instanceof stdClass'),
    +            array('"foo" instanceof stdClass'),
    +            array('__DIR__ instanceof stdClass'),
    +            array('PHP_SAPI instanceof stdClass'),
    +            array('1+1 instanceof stdClass'),
    +            array('true && false instanceof stdClass'),
    +            array('"a"."b" instanceof stdClass'),
    +            array('!5 instanceof stdClass'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider validStatements
    +     */
    +    public function testProcessValidStatement($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function validStatements()
    +    {
    +        $data = array(
    +            array('$a instanceof stdClass'),
    +            array('strtolower("foo") instanceof stdClass'),
    +            array('array(1) instanceof stdClass'),
    +            array('(string) "foo" instanceof stdClass'),
    +            array('(1+1) instanceof stdClass'),
    +            array('"foo ${foo} $bar" instanceof stdClass'),
    +            array('DateTime::ISO8601 instanceof stdClass'),
    +
    +        );
    +
    +        return $data;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/LeavePsyshAlonePassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/LeavePsyshAlonePassTest.php
    new file mode 100644
    index 0000000..717f381
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/LeavePsyshAlonePassTest.php
    @@ -0,0 +1,69 @@
    +setPass(new LeavePsyshAlonePass());
    +    }
    +
    +    public function testPassesInlineHtmlThroughJustFine()
    +    {
    +        $inline = $this->parse('not php at all!', '');
    +        $this->traverse($inline);
    +    }
    +
    +    /**
    +     * @dataProvider validStatements
    +     */
    +    public function testProcessStatementPasses($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverse($stmts);
    +    }
    +
    +    public function validStatements()
    +    {
    +        return array(
    +            array('array_merge()'),
    +            array('__psysh__()'),
    +            array('$this'),
    +            array('$psysh'),
    +            array('$__psysh'),
    +            array('$banana'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\RuntimeException
    +     */
    +    public function testProcessStatementFails($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        return array(
    +            array('$__psysh__'),
    +            array('var_dump($__psysh__)'),
    +            array('$__psysh__ = "your mom"'),
    +            array('$__psysh__->fakeFunctionCall()'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/LegacyEmptyPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/LegacyEmptyPassTest.php
    new file mode 100644
    index 0000000..da38e07
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/LegacyEmptyPassTest.php
    @@ -0,0 +1,77 @@
    +setPass(new LegacyEmptyPass());
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\ParseErrorException
    +     */
    +    public function testProcessInvalidStatement($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        if (version_compare(PHP_VERSION, '5.5', '>=')) {
    +            return array(
    +                array('empty()'),
    +            );
    +        }
    +
    +        return array(
    +            array('empty()'),
    +            array('empty(null)'),
    +            array('empty(PHP_EOL)'),
    +            array('empty("wat")'),
    +            array('empty(1.1)'),
    +            array('empty(Foo::$bar)'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider validStatements
    +     */
    +    public function testProcessValidStatement($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function validStatements()
    +    {
    +        if (version_compare(PHP_VERSION, '5.5', '<')) {
    +            return array(
    +                array('empty($foo)'),
    +            );
    +        }
    +
    +        return array(
    +            array('empty($foo)'),
    +            array('empty(null)'),
    +            array('empty(PHP_EOL)'),
    +            array('empty("wat")'),
    +            array('empty(1.1)'),
    +            array('empty(Foo::$bar)'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/MagicConstantsPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/MagicConstantsPassTest.php
    new file mode 100644
    index 0000000..1cc33cb
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/MagicConstantsPassTest.php
    @@ -0,0 +1,39 @@
    +setPass(new MagicConstantsPass());
    +    }
    +
    +    /**
    +     * @dataProvider magicConstants
    +     */
    +    public function testProcess($from, $to)
    +    {
    +        $this->assertProcessesAs($from, $to);
    +    }
    +
    +    public function magicConstants()
    +    {
    +        return array(
    +            array('__DIR__;', 'getcwd();'),
    +            array('__FILE__;', "'';"),
    +            array('___FILE___;', '___FILE___;'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/NamespacePassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/NamespacePassTest.php
    new file mode 100644
    index 0000000..bb24c91
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/NamespacePassTest.php
    @@ -0,0 +1,49 @@
    +cleaner = new CodeCleaner();
    +        $this->setPass(new NamespacePass($this->cleaner));
    +    }
    +
    +    public function testProcess()
    +    {
    +        $this->process('array_merge()');
    +        $this->assertNull($this->cleaner->getNamespace());
    +
    +        // A non-block namespace statement should set the current namespace.
    +        $this->process('namespace Alpha');
    +        $this->assertEquals(array('Alpha'), $this->cleaner->getNamespace());
    +
    +        // A new non-block namespace statement should override the current namespace.
    +        $this->process('namespace Beta');
    +        $this->assertEquals(array('Beta'), $this->cleaner->getNamespace());
    +
    +        $this->process('namespace Gamma { array_merge(); }');
    +        $this->assertNull($this->cleaner->getNamespace());
    +    }
    +
    +    private function process($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverse($stmts);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/StaticConstructorPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/StaticConstructorPassTest.php
    new file mode 100644
    index 0000000..ab8c567
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/StaticConstructorPassTest.php
    @@ -0,0 +1,91 @@
    +setPass(new StaticConstructorPass());
    +    }
    +
    +    /**
    +     * @dataProvider invalidStatements
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     */
    +    public function testProcessInvalidStatement($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    /**
    +     * @dataProvider invalidParserStatements
    +     * @expectedException \Psy\Exception\ParseErrorException
    +     */
    +    public function testProcessInvalidStatementCatchedByParser($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidStatements()
    +    {
    +        $statements = array(
    +            array('class A { public static function A() {}}'),
    +            array('class A { private static function A() {}}'),
    +        );
    +
    +        if (version_compare(PHP_VERSION, '5.3.3', '<')) {
    +            $statements[] = array('namespace B; class A { private static function A() {}}');
    +        }
    +
    +        return $statements;
    +    }
    +
    +    public function invalidParserStatements()
    +    {
    +        $statements = array(
    +            array('class A { public static function __construct() {}}'),
    +            array('class A { private static function __construct() {}}'),
    +            array('class A { private static function __construct() {} public function A() {}}'),
    +            array('namespace B; class A { private static function __construct() {}}'),
    +        );
    +
    +        return $statements;
    +    }
    +
    +    /**
    +     * @dataProvider validStatements
    +     */
    +    public function testProcessValidStatement($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function validStatements()
    +    {
    +        $statements = array(
    +            array('class A { public static function A() {} public function __construct() {}}'),
    +            array('class A { private function __construct() {} public static function A() {}}'),
    +        );
    +
    +        if (version_compare(PHP_VERSION, '5.3.3', '>=')) {
    +            $statements[] = array('namespace B; class A { private static function A() {}}');
    +        }
    +
    +        return $statements;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/StrictTypesPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/StrictTypesPassTest.php
    new file mode 100644
    index 0000000..d3f95dc
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/StrictTypesPassTest.php
    @@ -0,0 +1,53 @@
    +markTestSkipped();
    +        }
    +
    +        $this->setPass(new StrictTypesPass());
    +    }
    +
    +    public function testProcess()
    +    {
    +        $this->assertProcessesAs('declare(strict_types=1)', 'declare (strict_types=1);');
    +        $this->assertProcessesAs('null', "declare (strict_types=1);\nnull;");
    +        $this->assertProcessesAs('declare(strict_types=0)', 'declare (strict_types=0);');
    +        $this->assertProcessesAs('null', 'null;');
    +    }
    +
    +    /**
    +     * @dataProvider invalidDeclarations
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     */
    +    public function testInvalidDeclarations($declaration)
    +    {
    +        $stmts = $this->parse($declaration);
    +        $this->traverser->traverse($stmts);
    +    }
    +
    +    public function invalidDeclarations()
    +    {
    +        return array(
    +            array('declare(strict_types=-1)'),
    +            array('declare(strict_types=2)'),
    +            array('declare(strict_types="foo")'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/UseStatementPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/UseStatementPassTest.php
    new file mode 100644
    index 0000000..d3a7a75
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/UseStatementPassTest.php
    @@ -0,0 +1,52 @@
    +setPass(new UseStatementPass());
    +    }
    +
    +    /**
    +     * @dataProvider useStatements
    +     */
    +    public function testProcess($from, $to)
    +    {
    +        $this->assertProcessesAs($from, $to);
    +    }
    +
    +    public function useStatements()
    +    {
    +        return array(
    +            array(
    +                "use StdClass as NotSoStd;\n\$std = new NotSoStd();",
    +                '$std = new \\StdClass();',
    +            ),
    +            array(
    +                "namespace Foo;\n\nuse StdClass as S;\n\$std = new S();",
    +                "namespace Foo;\n\n\$std = new \\StdClass();",
    +            ),
    +            array(
    +                "namespace Foo;\n\nuse \\StdClass as S;\n\$std = new S();",
    +                "namespace Foo;\n\n\$std = new \\StdClass();",
    +            ),
    +            array(
    +                "use Foo\\Bar as fb;\n\$baz = new fb\\Baz();",
    +                '$baz = new \\Foo\\Bar\\Baz();',
    +            ),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidClassNamePassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidClassNamePassTest.php
    new file mode 100644
    index 0000000..db15395
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidClassNamePassTest.php
    @@ -0,0 +1,279 @@
    +setPass(new ValidClassNamePass());
    +    }
    +
    +    /**
    +     * @dataProvider getInvalid
    +     */
    +    public function testProcessInvalid($code, $php54 = false)
    +    {
    +        try {
    +            $stmts = $this->parse($code);
    +            $this->traverse($stmts);
    +            $this->fail();
    +        } catch (Exception $e) {
    +            if ($php54 && version_compare(PHP_VERSION, '5.4', '<')) {
    +                $this->assertInstanceOf('Psy\Exception\ParseErrorException', $e);
    +            } else {
    +                $this->assertInstanceOf('Psy\Exception\FatalErrorException', $e);
    +            }
    +        }
    +    }
    +
    +    public function getInvalid()
    +    {
    +        // class declarations
    +        return array(
    +            // core class
    +            array('class stdClass {}'),
    +            // capitalization
    +            array('class stdClass {}'),
    +
    +            // collisions with interfaces and traits
    +            array('interface stdClass {}'),
    +            array('trait stdClass {}', true),
    +
    +            // collisions inside the same code snippet
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +                trait Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +            ', true),
    +            array('
    +                trait Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +            ', true),
    +            array('
    +                trait Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +                interface Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +            ', true),
    +            array('
    +                interface Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +                trait Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +            ', true),
    +            array('
    +                interface Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +                interface Psy_Test_CodeCleaner_ValidClassNamePass_Alpha {}
    +            '),
    +
    +            // namespaced collisions
    +            array('
    +                namespace Psy\\Test\\CodeCleaner {
    +                    class ValidClassNamePassTest {}
    +                }
    +            '),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
    +                    class Beta {}
    +                }
    +                namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
    +                    class Beta {}
    +                }
    +            '),
    +
    +            // extends and implements
    +            array('class ValidClassNamePassTest extends NotAClass {}'),
    +            array('class ValidClassNamePassTest extends ArrayAccess {}'),
    +            array('class ValidClassNamePassTest implements stdClass {}'),
    +            array('class ValidClassNamePassTest implements ArrayAccess, stdClass {}'),
    +            array('interface ValidClassNamePassTest extends stdClass {}'),
    +            array('interface ValidClassNamePassTest extends ArrayAccess, stdClass {}'),
    +
    +            // class instantiations
    +            array('new Psy_Test_CodeCleaner_ValidClassNamePass_Gamma();'),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
    +                    new Psy_Test_CodeCleaner_ValidClassNamePass_Delta();
    +                }
    +            '),
    +
    +            // class constant fetch
    +            array('Psy\\Test\\CodeCleaner\\ValidClassNamePass\\NotAClass::FOO'),
    +
    +            // static call
    +            array('Psy\\Test\\CodeCleaner\\ValidClassNamePass\\NotAClass::foo()'),
    +            array('Psy\\Test\\CodeCleaner\\ValidClassNamePass\\NotAClass::$foo()'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider getValid
    +     */
    +    public function testProcessValid($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverse($stmts);
    +    }
    +
    +    public function getValid()
    +    {
    +        $valid = array(
    +            // class declarations
    +            array('class Psy_Test_CodeCleaner_ValidClassNamePass_Epsilon {}'),
    +            array('namespace Psy\Test\CodeCleaner\ValidClassNamePass; class Zeta {}'),
    +            array('
    +                namespace { class Psy_Test_CodeCleaner_ValidClassNamePass_Eta {}; }
    +                namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
    +                    class Psy_Test_CodeCleaner_ValidClassNamePass_Eta {}
    +                }
    +            '),
    +            array('namespace Psy\Test\CodeCleaner\ValidClassNamePass { class stdClass {} }'),
    +
    +            // class instantiations
    +            array('new stdClass();'),
    +            array('new stdClass();'),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
    +                    class Theta {}
    +                }
    +                namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
    +                    new Theta();
    +                }
    +            '),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
    +                    class Iota {}
    +                    new Iota();
    +                }
    +            '),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidClassNamePass {
    +                    class Kappa {}
    +                }
    +                namespace {
    +                    new \\Psy\\Test\\CodeCleaner\\ValidClassNamePass\\Kappa();
    +                }
    +            '),
    +
    +            // Class constant fetch (ValidConstantPassTest validates the actual constant)
    +            array('class A {} A::FOO'),
    +            array('$a = new DateTime; $a::ATOM'),
    +            array('interface A { const B = 1; } A::B'),
    +
    +            // static call
    +            array('DateTime::createFromFormat()'),
    +            array('DateTime::$someMethod()'),
    +            array('Psy\Test\CodeCleaner\Fixtures\ClassWithStatic::doStuff()'),
    +            array('Psy\Test\CodeCleaner\Fixtures\ClassWithCallStatic::doStuff()'),
    +
    +            // Allow `self` and `static` as class names.
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function getInstance() {
    +                        return new self();
    +                    }
    +                }
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function getInstance() {
    +                        return new SELF();
    +                    }
    +                }
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function getInstance() {
    +                        return new self;
    +                    }
    +                }
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function getInstance() {
    +                        return new static();
    +                    }
    +                }
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function getInstance() {
    +                        return new Static();
    +                    }
    +                }
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function getInstance() {
    +                        return new static;
    +                    }
    +                }
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function foo() {
    +                        return parent::bar();
    +                    }
    +                }
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function foo() {
    +                        return self::bar();
    +                    }
    +                }
    +            '),
    +            array('
    +                class Psy_Test_CodeCleaner_ValidClassNamePass_ClassWithStatic {
    +                    public static function foo() {
    +                        return static::bar();
    +                    }
    +                }
    +            '),
    +
    +            array('class A { static function b() { return new A; } }'),
    +            array('
    +                class A {
    +                    const B = 123;
    +                    function c() {
    +                        return A::B;
    +                    }
    +                }
    +            '),
    +            array('class A {} class B { function c() { return new A; } }'),
    +        );
    +
    +        // Ugh. There's gotta be a better way to test for this.
    +        if (class_exists('PhpParser\ParserFactory')) {
    +            // PHP 7.0 anonymous classes, only supported by PHP Parser v2.x
    +            $valid[] = array('$obj = new class() {}');
    +        }
    +
    +        if (version_compare(PHP_VERSION, '5.5', '>=')) {
    +            $valid[] = array('interface A {} A::class');
    +            $valid[] = array('interface A {} A::CLASS');
    +            $valid[] = array('class A {} A::class');
    +            $valid[] = array('class A {} A::CLASS');
    +            $valid[] = array('A::class');
    +            $valid[] = array('A::CLASS');
    +        }
    +
    +        return $valid;
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidConstantPassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidConstantPassTest.php
    new file mode 100644
    index 0000000..65a6d6b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidConstantPassTest.php
    @@ -0,0 +1,66 @@
    +setPass(new ValidConstantPass());
    +    }
    +
    +    /**
    +     * @dataProvider getInvalidReferences
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     */
    +    public function testProcessInvalidConstantReferences($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverse($stmts);
    +    }
    +
    +    public function getInvalidReferences()
    +    {
    +        return array(
    +            array('Foo\BAR'),
    +
    +            // class constant fetch
    +            array('Psy\Test\CodeCleaner\ValidConstantPassTest::FOO'),
    +            array('DateTime::BACON'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider getValidReferences
    +     */
    +    public function testProcessValidConstantReferences($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverse($stmts);
    +    }
    +
    +    public function getValidReferences()
    +    {
    +        return array(
    +            array('PHP_EOL'),
    +
    +            // class constant fetch
    +            array('NotAClass::FOO'),
    +            array('DateTime::ATOM'),
    +            array('$a = new DateTime; $a::ATOM'),
    +            array('DateTime::class'),
    +            array('$a = new DateTime; $a::class'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidFunctionNamePassTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidFunctionNamePassTest.php
    new file mode 100644
    index 0000000..c07e80e
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleaner/ValidFunctionNamePassTest.php
    @@ -0,0 +1,130 @@
    +setPass(new ValidFunctionNamePass());
    +    }
    +
    +    /**
    +     * @dataProvider getInvalidFunctions
    +     * @expectedException \Psy\Exception\FatalErrorException
    +     */
    +    public function testProcessInvalidFunctionCallsAndDeclarations($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverse($stmts);
    +    }
    +
    +    public function getInvalidFunctions()
    +    {
    +        return array(
    +            // function declarations
    +            array('function array_merge() {}'),
    +            array('function Array_Merge() {}'),
    +            array('
    +                function psy_test_codecleaner_validfunctionnamepass_alpha() {}
    +                function psy_test_codecleaner_validfunctionnamepass_alpha() {}
    +            '),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    function beta() {}
    +                }
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    function beta() {}
    +                }
    +            '),
    +
    +            // function calls
    +            array('psy_test_codecleaner_validfunctionnamepass_gamma()'),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    delta();
    +                }
    +            '),
    +
    +            // recursion
    +            array('function a() { a(); } function a() {}'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider getValidFunctions
    +     */
    +    public function testProcessValidFunctionCallsAndDeclarations($code)
    +    {
    +        $stmts = $this->parse($code);
    +        $this->traverse($stmts);
    +    }
    +
    +    public function getValidFunctions()
    +    {
    +        return array(
    +            array('function psy_test_codecleaner_validfunctionnamepass_epsilon() {}'),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    function zeta() {}
    +                }
    +            '),
    +            array('
    +                namespace {
    +                    function psy_test_codecleaner_validfunctionnamepass_eta() {}
    +                }
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    function psy_test_codecleaner_validfunctionnamepass_eta() {}
    +                }
    +            '),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    function psy_test_codecleaner_validfunctionnamepass_eta() {}
    +                }
    +                namespace {
    +                    function psy_test_codecleaner_validfunctionnamepass_eta() {}
    +                }
    +            '),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    function array_merge() {}
    +                }
    +            '),
    +
    +            // function calls
    +            array('array_merge();'),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    function theta() {}
    +                }
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    theta();
    +                }
    +            '),
    +            // closures
    +            array('$test = function(){};$test()'),
    +            array('
    +                namespace Psy\\Test\\CodeCleaner\\ValidFunctionNamePass {
    +                    function theta() {}
    +                }
    +                namespace {
    +                    Psy\\Test\\CodeCleaner\\ValidFunctionNamePass\\theta();
    +                }
    +            '),
    +
    +            // recursion
    +            array('function a() { a(); }'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/CodeCleanerTest.php b/core/vendor/psy/psysh/test/Psy/Test/CodeCleanerTest.php
    new file mode 100644
    index 0000000..2a2eb20
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/CodeCleanerTest.php
    @@ -0,0 +1,95 @@
    +assertEquals($expected, $cc->clean($lines, $requireSemicolons));
    +    }
    +
    +    public function semicolonCodeProvider()
    +    {
    +        return array(
    +            array(array('true'),  false, 'return true;'),
    +            array(array('true;'), false, 'return true;'),
    +            array(array('true;'), true,  'return true;'),
    +            array(array('true'),  true,  false),
    +
    +            array(array('echo "foo";', 'true'), false, "echo 'foo';\nreturn true;"),
    +            array(array('echo "foo";', 'true'), true,  false),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider unclosedStatementsProvider
    +     */
    +    public function testUnclosedStatements(array $lines, $isUnclosed)
    +    {
    +        $cc  = new CodeCleaner();
    +        $res = $cc->clean($lines);
    +
    +        if ($isUnclosed) {
    +            $this->assertFalse($res);
    +        } else {
    +            $this->assertNotFalse($res);
    +        }
    +    }
    +
    +    public function unclosedStatementsProvider()
    +    {
    +        return array(
    +            array(array('echo "'),   true),
    +            array(array('echo \''),  true),
    +            array(array('if (1) {'), true),
    +
    +            array(array('echo ""'),   false),
    +            array(array("echo ''"),   false),
    +            array(array('if (1) {}'), false),
    +
    +            array(array("\$content = <<clean(array($code));
    +    }
    +
    +    public function invalidStatementsProvider()
    +    {
    +        return array(
    +            array('function "what'),
    +            array("function 'what"),
    +            array('echo }'),
    +            array('echo {'),
    +            array('if (1) }'),
    +            array('echo """'),
    +            array("echo '''"),
    +            array('$foo "bar'),
    +            array('$foo \'bar'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/ConfigurationTest.php b/core/vendor/psy/psysh/test/Psy/Test/ConfigurationTest.php
    new file mode 100644
    index 0000000..58f3a3b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/ConfigurationTest.php
    @@ -0,0 +1,241 @@
    +assertEquals(function_exists('readline'), $config->hasReadline());
    +        $this->assertEquals(function_exists('readline'), $config->useReadline());
    +        $this->assertEquals(function_exists('pcntl_signal'), $config->hasPcntl());
    +        $this->assertEquals(function_exists('pcntl_signal'), $config->usePcntl());
    +        $this->assertFalse($config->requireSemicolons());
    +        $this->assertSame(Configuration::COLOR_MODE_AUTO, $config->colorMode());
    +    }
    +
    +    public function testGettersAndSetters()
    +    {
    +        $config = new Configuration();
    +
    +        $this->assertNull($config->getDataDir());
    +        $config->setDataDir('wheee');
    +        $this->assertEquals('wheee', $config->getDataDir());
    +
    +        $this->assertNull($config->getConfigDir());
    +        $config->setConfigDir('wheee');
    +        $this->assertEquals('wheee', $config->getConfigDir());
    +    }
    +
    +    /**
    +     * @dataProvider directories
    +     */
    +    public function testFilesAndDirectories($home, $configFile, $historyFile, $manualDbFile)
    +    {
    +        $oldHome = getenv('HOME');
    +        putenv("HOME=$home");
    +
    +        $config = new Configuration();
    +        $this->assertEquals(realpath($configFile),   realpath($config->getConfigFile()));
    +        $this->assertEquals(realpath($historyFile),  realpath($config->getHistoryFile()));
    +        $this->assertEquals(realpath($manualDbFile), realpath($config->getManualDbFile()));
    +
    +        putenv("HOME=$oldHome");
    +    }
    +
    +    public function directories()
    +    {
    +        $base = realpath(__DIR__ . '/../../fixtures');
    +
    +        return array(
    +            array(
    +                $base . '/default',
    +                $base . '/default/.config/psysh/config.php',
    +                $base . '/default/.config/psysh/psysh_history',
    +                $base . '/default/.local/share/psysh/php_manual.sqlite',
    +            ),
    +            array(
    +                $base . '/legacy',
    +                $base . '/legacy/.psysh/rc.php',
    +                $base . '/legacy/.psysh/history',
    +                $base . '/legacy/.psysh/php_manual.sqlite',
    +            ),
    +            array(
    +                $base . '/mixed',
    +                $base . '/mixed/.psysh/config.php',
    +                $base . '/mixed/.psysh/psysh_history',
    +                null,
    +            ),
    +        );
    +    }
    +
    +    public function testLoadConfig()
    +    {
    +        $config  = new Configuration();
    +        $cleaner = new CodeCleaner();
    +        $pager   = new PassthruPager(new ConsoleOutput());
    +        $loop    = new Loop($config);
    +
    +        $config->loadConfig(array(
    +            'useReadline'       => false,
    +            'usePcntl'          => false,
    +            'codeCleaner'       => $cleaner,
    +            'pager'             => $pager,
    +            'loop'              => $loop,
    +            'requireSemicolons' => true,
    +            'errorLoggingLevel' => E_ERROR | E_WARNING,
    +            'colorMode'         => Configuration::COLOR_MODE_FORCED,
    +        ));
    +
    +        $this->assertFalse($config->useReadline());
    +        $this->assertFalse($config->usePcntl());
    +        $this->assertSame($cleaner, $config->getCodeCleaner());
    +        $this->assertSame($pager, $config->getPager());
    +        $this->assertSame($loop, $config->getLoop());
    +        $this->assertTrue($config->requireSemicolons());
    +        $this->assertEquals(E_ERROR | E_WARNING, $config->errorLoggingLevel());
    +        $this->assertSame(Configuration::COLOR_MODE_FORCED, $config->colorMode());
    +    }
    +
    +    public function testLoadConfigFile()
    +    {
    +        $config = new Configuration(array('configFile' => __DIR__ . '/../../fixtures/config.php'));
    +
    +        $runtimeDir = $this->joinPath(realpath(sys_get_temp_dir()), 'psysh_test', 'withconfig', 'temp');
    +
    +        $this->assertStringStartsWith($runtimeDir, realpath($config->getTempFile('foo', 123)));
    +        $this->assertStringStartsWith($runtimeDir, realpath(dirname($config->getPipe('pipe', 123))));
    +        $this->assertStringStartsWith($runtimeDir, realpath($config->getRuntimeDir()));
    +
    +        $this->assertEquals(function_exists('readline'), $config->useReadline());
    +        $this->assertFalse($config->usePcntl());
    +        $this->assertEquals(E_ALL & ~E_NOTICE, $config->errorLoggingLevel());
    +    }
    +
    +    public function testLoadLocalConfigFile()
    +    {
    +        $oldPwd = getenv('PWD');
    +        putenv('PWD=' . realpath(__DIR__ . '/../../fixtures/project/'));
    +
    +        $config = new Configuration();
    +
    +        // When no configuration file is specified local project config is merged
    +        $this->assertFalse($config->useReadline());
    +        $this->assertTrue($config->usePcntl());
    +
    +        $config = new Configuration(array('configFile' => __DIR__ . '/../../fixtures/config.php'));
    +
    +        // Defining a configuration file skips loading local project config
    +        $this->assertTrue($config->useReadline());
    +        $this->assertFalse($config->usePcntl());
    +
    +        putenv("PWD=$oldPwd");
    +    }
    +
    +    /**
    +     * @expectedException Psy\Exception\DeprecatedException
    +     */
    +    public function testBaseDirConfigIsDeprecated()
    +    {
    +        $config = new Configuration(array('baseDir' => 'fake'));
    +    }
    +
    +    private function joinPath()
    +    {
    +        return implode(DIRECTORY_SEPARATOR, func_get_args());
    +    }
    +
    +    public function testConfigIncludes()
    +    {
    +        $config = new Configuration(array(
    +            'defaultIncludes' => array('/file.php'),
    +            'configFile'      => __DIR__ . '/../../fixtures/empty.php',
    +        ));
    +
    +        $includes = $config->getDefaultIncludes();
    +        $this->assertCount(1, $includes);
    +        $this->assertEquals('/file.php', $includes[0]);
    +    }
    +
    +    public function testGetOutput()
    +    {
    +        $config = new Configuration();
    +        $output = $config->getOutput();
    +
    +        $this->assertInstanceOf('\Psy\Output\ShellOutput', $output);
    +    }
    +
    +    public function getOutputDecoratedProvider()
    +    {
    +        return array(
    +            'auto' => array(
    +                null,
    +                Configuration::COLOR_MODE_AUTO,
    +            ),
    +            'forced' => array(
    +                true,
    +                Configuration::COLOR_MODE_FORCED,
    +            ),
    +            'disabled' => array(
    +                false,
    +                Configuration::COLOR_MODE_DISABLED,
    +            ),
    +        );
    +    }
    +
    +    /** @dataProvider getOutputDecoratedProvider */
    +    public function testGetOutputDecorated($expectation, $colorMode)
    +    {
    +        $config = new Configuration();
    +        $config->setColorMode($colorMode);
    +
    +        $this->assertSame($expectation, $config->getOutputDecorated());
    +    }
    +
    +    public function setColorModeValidProvider()
    +    {
    +        return array(
    +            'auto'     => array(Configuration::COLOR_MODE_AUTO),
    +            'forced'   => array(Configuration::COLOR_MODE_FORCED),
    +            'disabled' => array(Configuration::COLOR_MODE_DISABLED),
    +        );
    +    }
    +
    +    /** @dataProvider setColorModeValidProvider */
    +    public function testSetColorModeValid($colorMode)
    +    {
    +        $config = new Configuration();
    +        $config->setColorMode($colorMode);
    +
    +        $this->assertEquals($colorMode, $config->colorMode());
    +    }
    +
    +    public function testSetColorModeInvalid()
    +    {
    +        $config = new Configuration();
    +        $colorMode = 'some invalid mode';
    +
    +        $this->setExpectedException(
    +            '\InvalidArgumentException',
    +            'invalid color mode: some invalid mode'
    +        );
    +        $config->setColorMode($colorMode);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/ConsoleColorFactoryTest.php b/core/vendor/psy/psysh/test/Psy/Test/ConsoleColorFactoryTest.php
    new file mode 100644
    index 0000000..ab3c89b
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/ConsoleColorFactoryTest.php
    @@ -0,0 +1,51 @@
    +getConsoleColor();
    +        $themes = $colors->getThemes();
    +
    +        $this->assertFalse($colors->isStyleForced());
    +        $this->assertEquals(array('blue'), $themes['line_number']);
    +    }
    +
    +    public function testGetConsoleColorForced()
    +    {
    +        $colorMode = Configuration::COLOR_MODE_FORCED;
    +        $factory = new ConsoleColorFactory($colorMode);
    +        $colors = $factory->getConsoleColor();
    +        $themes = $colors->getThemes();
    +
    +        $this->assertTrue($colors->isStyleForced());
    +        $this->assertEquals(array('blue'), $themes['line_number']);
    +    }
    +
    +    public function testGetConsoleColorDisabled()
    +    {
    +        $colorMode = Configuration::COLOR_MODE_DISABLED;
    +        $factory = new ConsoleColorFactory($colorMode);
    +        $colors = $factory->getConsoleColor();
    +        $themes = $colors->getThemes();
    +
    +        $this->assertFalse($colors->isStyleForced());
    +        $this->assertEquals(array('none'), $themes['line_number']);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Exception/BreakExceptionTest.php b/core/vendor/psy/psysh/test/Psy/Test/Exception/BreakExceptionTest.php
    new file mode 100644
    index 0000000..94a90fd
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Exception/BreakExceptionTest.php
    @@ -0,0 +1,34 @@
    +assertTrue($e instanceof Exception);
    +        $this->assertTrue($e instanceof BreakException);
    +    }
    +
    +    public function testMessage()
    +    {
    +        $e = new BreakException('foo');
    +
    +        $this->assertContains('foo', $e->getMessage());
    +        $this->assertEquals('foo', $e->getRawMessage());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Exception/ErrorExceptionTest.php b/core/vendor/psy/psysh/test/Psy/Test/Exception/ErrorExceptionTest.php
    new file mode 100644
    index 0000000..a04f427
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Exception/ErrorExceptionTest.php
    @@ -0,0 +1,108 @@
    +assertTrue($e instanceof Exception);
    +        $this->assertTrue($e instanceof \ErrorException);
    +        $this->assertTrue($e instanceof ErrorException);
    +    }
    +
    +    public function testMessage()
    +    {
    +        $e = new ErrorException('foo');
    +
    +        $this->assertContains('foo', $e->getMessage());
    +        $this->assertEquals('foo', $e->getRawMessage());
    +    }
    +
    +    /**
    +     * @dataProvider getLevels
    +     */
    +    public function testErrorLevels($level, $type)
    +    {
    +        $e = new ErrorException('foo', 0, $level);
    +        $this->assertContains('PHP ' . $type, $e->getMessage());
    +    }
    +
    +    /**
    +     * @dataProvider getLevels
    +     */
    +    public function testThrowException($level, $type)
    +    {
    +        try {
    +            ErrorException::throwException($level, '{whot}', '{file}', '13');
    +        } catch (ErrorException $e) {
    +            $this->assertContains('PHP ' . $type, $e->getMessage());
    +            $this->assertContains('{whot}', $e->getMessage());
    +            $this->assertContains('in {file}', $e->getMessage());
    +            $this->assertContains('on line 13', $e->getMessage());
    +        }
    +    }
    +
    +    public function getLevels()
    +    {
    +        return array(
    +            array(E_WARNING,         'warning'),
    +            array(E_CORE_WARNING,    'warning'),
    +            array(E_COMPILE_WARNING, 'warning'),
    +            array(E_USER_WARNING,    'warning'),
    +            array(E_STRICT,          'Strict error'),
    +            array(0,                 'error'),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider getUserLevels
    +     */
    +    public function testThrowExceptionAsErrorHandler($level, $type)
    +    {
    +        set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
    +        try {
    +            trigger_error('{whot}', $level);
    +        } catch (ErrorException $e) {
    +            $this->assertContains('PHP ' . $type, $e->getMessage());
    +            $this->assertContains('{whot}', $e->getMessage());
    +        }
    +        restore_error_handler();
    +    }
    +
    +    public function getUserLevels()
    +    {
    +        return array(
    +            array(E_USER_ERROR,      'error'),
    +            array(E_USER_WARNING,    'warning'),
    +            array(E_USER_NOTICE,     'error'),
    +            array(E_USER_DEPRECATED, 'error'),
    +        );
    +    }
    +
    +    public function testIgnoreExecutionLoopFilename()
    +    {
    +        $e = new ErrorException('{{message}}', 0, 1, '/fake/path/to/Psy/ExecutionLoop/Loop.php');
    +        $this->assertEmpty($e->getFile());
    +
    +        $e = new ErrorException('{{message}}', 0, 1, 'c:\fake\path\to\Psy\ExecutionLoop\Loop.php');
    +        $this->assertEmpty($e->getFile());
    +
    +        $e = new ErrorException('{{message}}', 0, 1, '/fake/path/to/Psy/File.php');
    +        $this->assertNotEmpty($e->getFile());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Exception/FatalErrorExceptionTest.php b/core/vendor/psy/psysh/test/Psy/Test/Exception/FatalErrorExceptionTest.php
    new file mode 100644
    index 0000000..ee7173d
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Exception/FatalErrorExceptionTest.php
    @@ -0,0 +1,46 @@
    +assertTrue($e instanceof Exception);
    +        $this->assertTrue($e instanceof \ErrorException);
    +        $this->assertTrue($e instanceof FatalErrorException);
    +    }
    +
    +    public function testMessage()
    +    {
    +        $e = new FatalErrorException('{msg}', 0, 0, '{filename}', 13);
    +
    +        $this->assertEquals('{msg}', $e->getRawMessage());
    +        $this->assertContains('{msg}', $e->getMessage());
    +        $this->assertContains('{filename}', $e->getMessage());
    +        $this->assertContains('line 13', $e->getMessage());
    +    }
    +
    +    public function testMessageWithNoFilename()
    +    {
    +        $e = new FatalErrorException('{msg}');
    +
    +        $this->assertEquals('{msg}', $e->getRawMessage());
    +        $this->assertContains('{msg}', $e->getMessage());
    +        $this->assertContains('eval()\'d code', $e->getMessage());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Exception/ParseErrorExceptionTest.php b/core/vendor/psy/psysh/test/Psy/Test/Exception/ParseErrorExceptionTest.php
    new file mode 100644
    index 0000000..59602d1
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Exception/ParseErrorExceptionTest.php
    @@ -0,0 +1,43 @@
    +assertTrue($e instanceof Exception);
    +        $this->assertTrue($e instanceof \PhpParser\Error);
    +        $this->assertTrue($e instanceof ParseErrorException);
    +    }
    +
    +    public function testMessage()
    +    {
    +        $e = new ParseErrorException('{msg}', 1);
    +
    +        $this->assertContains('{msg}', $e->getMessage());
    +        $this->assertContains('PHP Parse error:', $e->getMessage());
    +    }
    +
    +    public function testConstructFromParseError()
    +    {
    +        $e = ParseErrorException::fromParseError(new \PhpParser\Error('{msg}'));
    +
    +        $this->assertContains('{msg}', $e->getRawMessage());
    +        $this->assertContains('PHP Parse error:', $e->getMessage());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Exception/RuntimeExceptionTest.php b/core/vendor/psy/psysh/test/Psy/Test/Exception/RuntimeExceptionTest.php
    new file mode 100644
    index 0000000..0396a26
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Exception/RuntimeExceptionTest.php
    @@ -0,0 +1,31 @@
    +assertTrue($e instanceof Exception);
    +        $this->assertTrue($e instanceof \RuntimeException);
    +        $this->assertTrue($e instanceof RuntimeException);
    +
    +        $this->assertEquals($msg, $e->getMessage());
    +        $this->assertEquals($msg, $e->getRawMessage());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Formatter/CodeFormatterTest.php b/core/vendor/psy/psysh/test/Psy/Test/Formatter/CodeFormatterTest.php
    new file mode 100644
    index 0000000..7aa8ae8
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Formatter/CodeFormatterTest.php
    @@ -0,0 +1,61 @@
    + 18|     private function ignoreThisMethod($arg)
    +    19|     {
    +    20|         echo 'whot!';
    +    21|     }
    +EOS;
    +
    +        $formatted = CodeFormatter::format(new \ReflectionMethod($this, 'ignoreThisMethod'));
    +        $formattedWithoutColors = preg_replace('#' . chr(27) . '\[\d\d?m#', '', $formatted);
    +
    +        $this->assertEquals($expected, rtrim($formattedWithoutColors));
    +        $this->assertNotEquals($expected, rtrim($formatted));
    +    }
    +
    +    /**
    +     * @dataProvider filenames
    +     * @expectedException Psy\Exception\RuntimeException
    +     */
    +    public function testCodeFormatterThrowsException($filename)
    +    {
    +        $reflector = $this->getMockBuilder('ReflectionClass')
    +            ->disableOriginalConstructor()
    +            ->getMock();
    +
    +        $reflector
    +            ->expects($this->once())
    +            ->method('getFileName')
    +            ->will($this->returnValue($filename));
    +
    +        CodeFormatter::format($reflector);
    +    }
    +
    +    public function filenames()
    +    {
    +        return array(array(null), array('not a file'));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Formatter/DocblockFormatterTest.php b/core/vendor/psy/psysh/test/Psy/Test/Formatter/DocblockFormatterTest.php
    new file mode 100644
    index 0000000..4aad9a8
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Formatter/DocblockFormatterTest.php
    @@ -0,0 +1,63 @@
    +
    +     *
    +     * @throws InvalidArgumentException if $foo is empty.
    +     *
    +     * @param mixed $foo It's a foo thing.
    +     * @param int   $bar This is definitely bar.
    +     *
    +     * @return string A string of no consequence.
    +     */
    +    private function methodWithDocblock($foo, $bar = 1)
    +    {
    +        if (empty($foo)) {
    +            throw new \InvalidArgumentException();
    +        }
    +
    +        return 'method called';
    +    }
    +
    +    public function testFormat()
    +    {
    +        $expected = <<Description:
    +  This is a docblock!
    +
    +Throws:
    +  InvalidArgumentException  if \$foo is empty.
    +
    +Param:
    +  mixed  \$foo  It's a foo thing.
    +  int    \$bar  This is definitely bar.
    +
    +Return:
    +  string  A string of no consequence.
    +
    +Author: Justin Hileman \
    +EOS;
    +
    +        $this->assertEquals(
    +            $expected,
    +            DocblockFormatter::format(new \ReflectionMethod($this, 'methodWithDocblock'))
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Formatter/SignatureFormatterTest.php b/core/vendor/psy/psysh/test/Psy/Test/Formatter/SignatureFormatterTest.php
    new file mode 100644
    index 0000000..41e9451
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Formatter/SignatureFormatterTest.php
    @@ -0,0 +1,77 @@
    +assertEquals($expected, strip_tags(SignatureFormatter::format($reflector)));
    +    }
    +
    +    public function signatureReflectors()
    +    {
    +        return array(
    +            array(
    +                new \ReflectionClass($this),
    +                "class Psy\Test\Formatter\SignatureFormatterTest "
    +                . 'extends PHPUnit_Framework_TestCase implements '
    +                . 'Countable, PHPUnit_Framework_SelfDescribing, '
    +                . 'PHPUnit_Framework_Test',
    +            ),
    +            array(
    +                new \ReflectionFunction('implode'),
    +                defined('HHVM_VERSION') ? 'function implode($arg1, $arg2 = null)' : 'function implode($glue, $pieces)',
    +            ),
    +            array(
    +                new ReflectionConstant($this, 'FOO'),
    +                'const FOO = "foo value"',
    +            ),
    +            array(
    +                new \ReflectionMethod($this, 'someFakeMethod'),
    +                'private function someFakeMethod(array $one, $two = \'TWO\', Reflector $three = null)',
    +            ),
    +            array(
    +                new \ReflectionProperty($this, 'bar'),
    +                'private static $bar',
    +            ),
    +            array(
    +                new \ReflectionClass('Psy\CodeCleaner\CodeCleanerPass'),
    +                'abstract class Psy\CodeCleaner\CodeCleanerPass '
    +                . 'extends PhpParser\NodeVisitorAbstract '
    +                . 'implements PhpParser\NodeVisitor',
    +            ),
    +        );
    +    }
    +
    +    /**
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testSignatureFormatterThrowsUnknownReflectorExpeption()
    +    {
    +        $refl = $this->getMock('Reflector');
    +        SignatureFormatter::format($refl);
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Readline/GNUReadlineTest.php b/core/vendor/psy/psysh/test/Psy/Test/Readline/GNUReadlineTest.php
    new file mode 100644
    index 0000000..60756aa
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Readline/GNUReadlineTest.php
    @@ -0,0 +1,80 @@
    +markTestSkipped('GNUReadline not enabled');
    +        }
    +
    +        $this->historyFile = tempnam(sys_get_temp_dir(), 'psysh_test_history');
    +        file_put_contents($this->historyFile, "_HiStOrY_V2_\n");
    +    }
    +
    +    public function testHistory()
    +    {
    +        $readline = new GNUReadline($this->historyFile);
    +        $this->assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $this->assertEquals(array('foo'), $readline->listHistory());
    +        $readline->addHistory('bar');
    +        $this->assertEquals(array('foo', 'bar'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('foo', 'bar', 'baz'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +
    +    /**
    +     * @depends testHistory
    +     */
    +    public function testHistorySize()
    +    {
    +        $readline = new GNUReadline($this->historyFile, 2);
    +        $this->assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $readline->addHistory('bar');
    +        $this->assertEquals(array('foo', 'bar'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('bar', 'baz'), $readline->listHistory());
    +        $readline->addHistory('w00t');
    +        $this->assertEquals(array('baz', 'w00t'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +
    +    /**
    +     * @depends testHistory
    +     */
    +    public function testHistoryEraseDups()
    +    {
    +        $readline = new GNUReadline($this->historyFile, 0, true);
    +        $this->assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $readline->addHistory('bar');
    +        $readline->addHistory('foo');
    +        $this->assertEquals(array('bar', 'foo'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $readline->addHistory('w00t');
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('bar', 'foo', 'w00t', 'baz'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Readline/LibeditTest.php b/core/vendor/psy/psysh/test/Psy/Test/Readline/LibeditTest.php
    new file mode 100644
    index 0000000..f0d01a0
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Readline/LibeditTest.php
    @@ -0,0 +1,128 @@
    +markTestSkipped('Libedit not enabled');
    +        }
    +
    +        $this->historyFile = tempnam(sys_get_temp_dir(), 'psysh_test_history');
    +        if (false === file_put_contents($this->historyFile, "_HiStOrY_V2_\n")) {
    +            $this->fail('Unable to write history file: ' . $this->historyFile);
    +        }
    +        // Calling readline_read_history before readline_clear_history
    +        // avoids segfault with PHP 5.5.7 & libedit v3.1
    +        readline_read_history($this->historyFile);
    +        readline_clear_history();
    +    }
    +
    +    public function tearDown()
    +    {
    +        if (is_file($this->historyFile)) {
    +            unlink($this->historyFile);
    +        }
    +    }
    +
    +    public function testHistory()
    +    {
    +        $readline = new Libedit($this->historyFile);
    +        $this->assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $this->assertEquals(array('foo'), $readline->listHistory());
    +        $readline->addHistory('bar');
    +        $this->assertEquals(array('foo', 'bar'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('foo', 'bar', 'baz'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +
    +    /**
    +     * @depends testHistory
    +     */
    +    public function testHistorySize()
    +    {
    +        $readline = new Libedit($this->historyFile, 2);
    +        $this->assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $readline->addHistory('bar');
    +        $this->assertEquals(array('foo', 'bar'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('bar', 'baz'), $readline->listHistory());
    +        $readline->addHistory('w00t');
    +        $this->assertEquals(array('baz', 'w00t'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +
    +    /**
    +     * @depends testHistory
    +     */
    +    public function testHistoryEraseDups()
    +    {
    +        $readline = new Libedit($this->historyFile, 0, true);
    +        $this->assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $readline->addHistory('bar');
    +        $readline->addHistory('foo');
    +        $this->assertEquals(array('bar', 'foo'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $readline->addHistory('w00t');
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('bar', 'foo', 'w00t', 'baz'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +
    +    public function testListHistory()
    +    {
    +        $readline = new Libedit($this->historyFile);
    +        file_put_contents(
    +            $this->historyFile,
    +            "This is an entry\n\0This is a comment\nThis is an entry\0With a comment\n",
    +            FILE_APPEND
    +        );
    +        $this->assertEquals(array(
    +            'This is an entry',
    +            'This is an entry',
    +        ), $readline->listHistory());
    +        $readline->clearHistory();
    +    }
    +
    +    /**
    +     * Libedit being a BSD library,
    +     * it doesn't support non-unix line separators.
    +     */
    +    public function testLinebreaksSupport()
    +    {
    +        $readline = new Libedit($this->historyFile);
    +        file_put_contents(
    +            $this->historyFile,
    +            "foo\rbar\nbaz\r\nw00t",
    +            FILE_APPEND
    +        );
    +        $this->assertEquals(array(
    +            "foo\rbar",
    +            "baz\r",
    +            'w00t',
    +        ), $readline->listHistory());
    +        $readline->clearHistory();
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Readline/TransientTest.php b/core/vendor/psy/psysh/test/Psy/Test/Readline/TransientTest.php
    new file mode 100644
    index 0000000..6fcd182
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Readline/TransientTest.php
    @@ -0,0 +1,76 @@
    +assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $this->assertEquals(array('foo'), $readline->listHistory());
    +        $readline->addHistory('bar');
    +        $this->assertEquals(array('foo', 'bar'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('foo', 'bar', 'baz'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +
    +    /**
    +     * @depends testHistory
    +     */
    +    public function testHistorySize()
    +    {
    +        $readline = new Transient(null, 2);
    +        $this->assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $readline->addHistory('bar');
    +        $this->assertEquals(array('foo', 'bar'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('bar', 'baz'), $readline->listHistory());
    +        $readline->addHistory('w00t');
    +        $this->assertEquals(array('baz', 'w00t'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +
    +    /**
    +     * @depends testHistory
    +     */
    +    public function testHistoryEraseDups()
    +    {
    +        $readline = new Transient(null, 0, true);
    +        $this->assertEmpty($readline->listHistory());
    +        $readline->addHistory('foo');
    +        $readline->addHistory('bar');
    +        $readline->addHistory('foo');
    +        $this->assertEquals(array('bar', 'foo'), $readline->listHistory());
    +        $readline->addHistory('baz');
    +        $readline->addHistory('w00t');
    +        $readline->addHistory('baz');
    +        $this->assertEquals(array('bar', 'foo', 'w00t', 'baz'), $readline->listHistory());
    +        $readline->clearHistory();
    +        $this->assertEmpty($readline->listHistory());
    +    }
    +
    +    public function testSomeThingsAreAlwaysTrue()
    +    {
    +        $readline = new Transient();
    +        $this->assertTrue(Transient::isSupported());
    +        $this->assertTrue($readline->readHistory());
    +        $this->assertTrue($readline->writeHistory());
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Reflection/ReflectionConstantTest.php b/core/vendor/psy/psysh/test/Psy/Test/Reflection/ReflectionConstantTest.php
    new file mode 100644
    index 0000000..0e12fa7
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Reflection/ReflectionConstantTest.php
    @@ -0,0 +1,60 @@
    +getDeclaringClass();
    +
    +        $this->assertTrue($class instanceof \ReflectionClass);
    +        $this->assertEquals('Psy\Test\Reflection\ReflectionConstantTest', $class->getName());
    +        $this->assertEquals('CONSTANT_ONE', $refl->getName());
    +        $this->assertEquals('CONSTANT_ONE', (string) $refl);
    +        $this->assertEquals('one', $refl->getValue());
    +        $this->assertEquals(null, $refl->getFileName());
    +        $this->assertFalse($refl->getDocComment());
    +    }
    +
    +    /**
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testUnknownConstantThrowsException()
    +    {
    +        new ReflectionConstant($this, 'UNKNOWN_CONSTANT');
    +    }
    +
    +    /**
    +     * @expectedException \RuntimeException
    +     * @dataProvider notYetImplemented
    +     */
    +    public function testNotYetImplemented($method)
    +    {
    +        $refl = new ReflectionConstant($this, 'CONSTANT_ONE');
    +        $refl->$method();
    +    }
    +
    +    public function notYetImplemented()
    +    {
    +        return array(
    +            array('getStartLine'),
    +            array('getEndLine'),
    +            array('export'),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/ShellTest.php b/core/vendor/psy/psysh/test/Psy/Test/ShellTest.php
    new file mode 100644
    index 0000000..db47dc5
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/ShellTest.php
    @@ -0,0 +1,340 @@
    +streams as $stream) {
    +            fclose($stream);
    +        }
    +    }
    +
    +    public function testScopeVariables()
    +    {
    +        $one       = 'banana';
    +        $two       = 123;
    +        $three     = new \StdClass();
    +        $__psysh__ = 'ignore this';
    +        $_         = 'ignore this';
    +        $_e        = 'ignore this';
    +
    +        $shell = new Shell($this->getConfig());
    +        $shell->setScopeVariables(compact('one', 'two', 'three', '__psysh__', '_', '_e'));
    +
    +        $this->assertNotContains('__psysh__', $shell->getScopeVariableNames());
    +        $this->assertEquals(array('one', 'two', 'three', '_'), $shell->getScopeVariableNames());
    +        $this->assertEquals('banana', $shell->getScopeVariable('one'));
    +        $this->assertEquals(123, $shell->getScopeVariable('two'));
    +        $this->assertSame($three, $shell->getScopeVariable('three'));
    +        $this->assertNull($shell->getScopeVariable('_'));
    +
    +        $shell->setScopeVariables(array());
    +        $this->assertEquals(array('_'), $shell->getScopeVariableNames());
    +    }
    +
    +    /**
    +     * @expectedException \InvalidArgumentException
    +     */
    +    public function testUnknownScopeVariablesThrowExceptions()
    +    {
    +        $shell = new Shell($this->getConfig());
    +        $shell->setScopeVariables(array('foo' => 'FOO', 'bar' => 1));
    +        $shell->getScopeVariable('baz');
    +    }
    +
    +    public function testIncludes()
    +    {
    +        $config = $this->getConfig(array('configFile' => __DIR__ . '/../../fixtures/empty.php'));
    +
    +        $shell = new Shell($config);
    +        $this->assertEmpty($shell->getIncludes());
    +        $shell->setIncludes(array('foo', 'bar', 'baz'));
    +        $this->assertEquals(array('foo', 'bar', 'baz'), $shell->getIncludes());
    +    }
    +
    +    public function testIncludesConfig()
    +    {
    +        $config = $this->getConfig(array(
    +            'defaultIncludes' => array('/file.php'),
    +            'configFile'      => __DIR__ . '/../../fixtures/empty.php',
    +        ));
    +
    +        $shell = new Shell($config);
    +
    +        $includes = $shell->getIncludes();
    +        $this->assertEquals('/file.php', $includes[0]);
    +    }
    +
    +    public function testAddMatchersViaConfig()
    +    {
    +        $config = $this->getConfig(array(
    +            'tabCompletionMatchers' => array(
    +                new ClassMethodsMatcher(),
    +            ),
    +        ));
    +
    +        $matchers = $config->getTabCompletionMatchers();
    +
    +        $this->assertTrue(array_pop($matchers) instanceof ClassMethodsMatcher);
    +    }
    +
    +    public function testRenderingExceptions()
    +    {
    +        $shell  = new Shell($this->getConfig());
    +        $output = $this->getOutput();
    +        $stream = $output->getStream();
    +        $e      = new ParseErrorException('message', 13);
    +
    +        $shell->setOutput($output);
    +        $shell->addCode('code');
    +        $this->assertTrue($shell->hasCode());
    +        $this->assertNotEmpty($shell->getCodeBuffer());
    +
    +        $shell->writeException($e);
    +
    +        $this->assertSame($e, $shell->getScopeVariable('_e'));
    +        $this->assertFalse($shell->hasCode());
    +        $this->assertEmpty($shell->getCodeBuffer());
    +
    +        rewind($stream);
    +        $streamContents = stream_get_contents($stream);
    +
    +        $this->assertContains('PHP Parse error', $streamContents);
    +        $this->assertContains('message', $streamContents);
    +        $this->assertContains('line 13', $streamContents);
    +    }
    +
    +    public function testHandlingErrors()
    +    {
    +        $shell  = new Shell($this->getConfig());
    +        $output = $this->getOutput();
    +        $stream = $output->getStream();
    +        $shell->setOutput($output);
    +
    +        $oldLevel = error_reporting();
    +        error_reporting($oldLevel & ~E_USER_NOTICE);
    +
    +        try {
    +            $shell->handleError(E_USER_NOTICE, 'wheee', null, 13);
    +        } catch (ErrorException $e) {
    +            error_reporting($oldLevel);
    +            $this->fail('Unexpected error exception');
    +        }
    +        error_reporting($oldLevel);
    +
    +        rewind($stream);
    +        $streamContents = stream_get_contents($stream);
    +
    +        $this->assertContains('PHP error:', $streamContents);
    +        $this->assertContains('wheee',      $streamContents);
    +        $this->assertContains('line 13',    $streamContents);
    +    }
    +
    +    /**
    +     * @expectedException Psy\Exception\ErrorException
    +     */
    +    public function testNotHandlingErrors()
    +    {
    +        $shell    = new Shell($this->getConfig());
    +        $oldLevel = error_reporting();
    +        error_reporting($oldLevel | E_USER_NOTICE);
    +
    +        try {
    +            $shell->handleError(E_USER_NOTICE, 'wheee', null, 13);
    +        } catch (ErrorException $e) {
    +            error_reporting($oldLevel);
    +            throw $e;
    +        }
    +    }
    +
    +    public function testVersion()
    +    {
    +        $shell = new Shell($this->getConfig());
    +
    +        $this->assertInstanceOf('Symfony\Component\Console\Application', $shell);
    +        $this->assertContains(Shell::VERSION, $shell->getVersion());
    +        $this->assertContains(phpversion(), $shell->getVersion());
    +        $this->assertContains(php_sapi_name(), $shell->getVersion());
    +    }
    +
    +    public function testCodeBuffer()
    +    {
    +        $shell = new Shell($this->getConfig());
    +
    +        $shell->addCode('class');
    +        $this->assertNull($shell->flushCode());
    +        $this->assertTrue($shell->hasCode());
    +
    +        $shell->addCode('a');
    +        $this->assertNull($shell->flushCode());
    +        $this->assertTrue($shell->hasCode());
    +
    +        $shell->addCode('{}');
    +        $code = $shell->flushCode();
    +        $this->assertFalse($shell->hasCode());
    +        $code = preg_replace('/\s+/', ' ', $code);
    +        $this->assertNotNull($code);
    +        $this->assertEquals('class a { }', $code);
    +    }
    +
    +    public function testKeepCodeBufferOpen()
    +    {
    +        $shell = new Shell($this->getConfig());
    +
    +        $shell->addCode('1 \\');
    +        $this->assertNull($shell->flushCode());
    +        $this->assertTrue($shell->hasCode());
    +
    +        $shell->addCode('+ 1 \\');
    +        $this->assertNull($shell->flushCode());
    +        $this->assertTrue($shell->hasCode());
    +
    +        $shell->addCode('+ 1');
    +        $code = $shell->flushCode();
    +        $this->assertFalse($shell->hasCode());
    +        $code = preg_replace('/\s+/', ' ', $code);
    +        $this->assertNotNull($code);
    +        $this->assertEquals('return 1 + 1 + 1;', $code);
    +    }
    +
    +    /**
    +     * @expectedException \Psy\Exception\ParseErrorException
    +     */
    +    public function testCodeBufferThrowsParseExceptions()
    +    {
    +        $shell = new Shell($this->getConfig());
    +        $shell->addCode('this is not valid');
    +        $shell->flushCode();
    +    }
    +
    +    public function testClosuresSupport()
    +    {
    +        $shell = new Shell($this->getConfig());
    +        $code = '$test = function () {}';
    +        $shell->addCode($code);
    +        $shell->flushCode();
    +        $code = '$test()';
    +        $shell->addCode($code);
    +        $shell->flushCode();
    +    }
    +
    +    public function testWriteStdout()
    +    {
    +        $output = $this->getOutput();
    +        $stream = $output->getStream();
    +        $shell  = new Shell($this->getConfig());
    +        $shell->setOutput($output);
    +
    +        $shell->writeStdout("{{stdout}}\n");
    +
    +        rewind($stream);
    +        $streamContents = stream_get_contents($stream);
    +
    +        $this->assertEquals('{{stdout}}' . PHP_EOL, $streamContents);
    +    }
    +
    +    public function testWriteStdoutWithoutNewline()
    +    {
    +        $output = $this->getOutput();
    +        $stream = $output->getStream();
    +        $shell  = new Shell($this->getConfig());
    +        $shell->setOutput($output);
    +
    +        $shell->writeStdout('{{stdout}}');
    +
    +        rewind($stream);
    +        $streamContents = stream_get_contents($stream);
    +
    +        $this->assertEquals('{{stdout}}' . PHP_EOL, $streamContents);
    +    }
    +
    +    /**
    +     * @dataProvider getReturnValues
    +     */
    +    public function testWriteReturnValue($input, $expected)
    +    {
    +        $output = $this->getOutput();
    +        $stream = $output->getStream();
    +        $shell  = new Shell($this->getConfig());
    +        $shell->setOutput($output);
    +
    +        $shell->writeReturnValue($input);
    +        rewind($stream);
    +        $this->assertEquals($expected, stream_get_contents($stream));
    +    }
    +
    +    public function getReturnValues()
    +    {
    +        return array(
    +            array('{{return value}}', "=> \"\033[32m{{return value}}\033[39m\"" . PHP_EOL),
    +            array(1, "=> \033[35m1\033[39m" . PHP_EOL),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider getRenderedExceptions
    +     */
    +    public function testWriteException($exception, $expected)
    +    {
    +        $output = $this->getOutput();
    +        $stream = $output->getStream();
    +        $shell  = new Shell($this->getConfig());
    +        $shell->setOutput($output);
    +
    +        $shell->writeException($exception);
    +        rewind($stream);
    +        $this->assertEquals($expected, stream_get_contents($stream));
    +    }
    +
    +    public function getRenderedExceptions()
    +    {
    +        return array(
    +            array(new \Exception('{{message}}'), "Exception with message '{{message}}'" . PHP_EOL),
    +        );
    +    }
    +
    +    private function getOutput()
    +    {
    +        $stream = fopen('php://memory', 'w+');
    +        $this->streams[] = $stream;
    +
    +        $output = new StreamOutput($stream, StreamOutput::VERBOSITY_NORMAL, false);
    +
    +        return $output;
    +    }
    +
    +    private function getConfig(array $config = array())
    +    {
    +        // Mebbe there's a better way than this?
    +        $dir = tempnam(sys_get_temp_dir(), 'psysh_shell_test_');
    +        unlink($dir);
    +
    +        $defaults = array(
    +            'configDir'  => $dir,
    +            'dataDir'    => $dir,
    +            'runtimeDir' => $dir,
    +        );
    +
    +        return new Configuration(array_merge($defaults, $config));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/TabCompletion/AutoCompleterTest.php b/core/vendor/psy/psysh/test/Psy/Test/TabCompletion/AutoCompleterTest.php
    new file mode 100644
    index 0000000..8bb4828
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/TabCompletion/AutoCompleterTest.php
    @@ -0,0 +1,145 @@
    +getAutoCompleter();
    +        foreach ($matchers as $matcher) {
    +            if ($matcher instanceof ContextAware) {
    +                $matcher->setContext($context);
    +            }
    +            $tabCompletion->addMatcher($matcher);
    +        }
    +
    +        $context->setAll(array('foo' => 12, 'bar' => new \DOMDocument()));
    +
    +        $code = $tabCompletion->processCallback('', 0, array(
    +           'line_buffer' => $line,
    +           'point'       => 0,
    +           'end'         => strlen($line),
    +        ));
    +
    +        foreach ($mustContain as $mc) {
    +            $this->assertContains($mc, $code);
    +        }
    +
    +        foreach ($mustNotContain as $mnc) {
    +            $this->assertNotContains($mnc, $code);
    +        }
    +    }
    +
    +    /**
    +     * TODO
    +     * ====
    +     * draft, open to modifications
    +     * - [ ] if the variable is an array, return the square bracket for completion
    +     * - [ ] if the variable is a constructor or method, reflect to complete as a function call
    +     * - [ ] if the preceding token is a variable, call operators or keywords compatible for completion
    +     * - [X] a command always should be the second token after php_open_tag
    +     * - [X] keywords are never consecutive
    +     * - [X] namespacing completion should work just fine
    +     * - [X] after a new keyword, should always be a class constructor, never a function call or keyword, constant,
    +     *       or variable that does not contain a existing class name.
    +     * - [X] on a namespaced constructor the completion must show the classes related, not constants.
    +     *
    +     * @return array
    +     */
    +    public function classesInput()
    +    {
    +        return array(
    +            // input, must had, must not had
    +            array('T_OPE', array('T_OPEN_TAG'), array()),
    +            array('st', array('stdClass'), array()),
    +            array('stdCla', array('stdClass'), array()),
    +            array('new s', array('stdClass'), array()),
    +            array(
    +                'new ',
    +                array('stdClass', 'Psy\\Context', 'Psy\\Configuration'),
    +                array('require', 'array_search', 'T_OPEN_TAG', '$foo'),
    +            ),
    +            array('new Psy\\C', array('Context'), array('CASE_LOWER')),
    +            array('\s', array('stdClass'), array()),
    +            array('array_', array('array_search', 'array_map', 'array_merge'), array()),
    +            array('$bar->', array('load'), array()),
    +            array('$b', array('bar'), array()),
    +            array('6 + $b', array('bar'), array()),
    +            array('$f', array('foo'), array()),
    +            array('l', array('ls'), array()),
    +            array('ls ', array(), array('ls')),
    +            array('sho', array('show'), array()),
    +            array('12 + clone $', array('foo'), array()),
    +            // array(
    +            //   '$foo ',
    +            //   array('+', 'clone'),
    +            //   array('$foo', 'DOMDocument', 'array_map')
    +            // ), requires a operator matcher?
    +            array('$', array('foo', 'bar'), array('require', 'array_search', 'T_OPEN_TAG', 'Psy')),
    +            array(
    +                'Psy\\',
    +                array('Context', 'TabCompletion\\Matcher\\AbstractMatcher'),
    +                array('require', 'array_search'),
    +            ),
    +            array(
    +                'Psy\Test\TabCompletion\StaticSample::CO',
    +                array('Psy\Test\TabCompletion\StaticSample::CONSTANT_VALUE'),
    +                array(),
    +            ),
    +            array(
    +                'Psy\Test\TabCompletion\StaticSample::',
    +                array('Psy\Test\TabCompletion\StaticSample::$staticVariable'),
    +                array(),
    +            ),
    +            array(
    +                'Psy\Test\TabCompletion\StaticSample::',
    +                array('Psy\Test\TabCompletion\StaticSample::staticFunction'),
    +                array(),
    +            ),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/TabCompletion/StaticSample.php b/core/vendor/psy/psysh/test/Psy/Test/TabCompletion/StaticSample.php
    new file mode 100644
    index 0000000..7fb0ada
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/TabCompletion/StaticSample.php
    @@ -0,0 +1,27 @@
    +getMockBuilder('ReflectionClass')
    +            ->disableOriginalConstructor()
    +            ->getMock();
    +
    +        $reflector->expects($this->once())
    +            ->method('getDocComment')
    +            ->will($this->returnValue($comment));
    +
    +        $docblock = new Docblock($reflector);
    +
    +        $this->assertEquals($body, $docblock->desc);
    +
    +        foreach ($tags as $tag => $value) {
    +            $this->assertTrue($docblock->hasTag($tag));
    +            $this->assertEquals($value, $docblock->tag($tag));
    +        }
    +    }
    +
    +    public function comments()
    +    {
    +        return array(
    +            array('', '', array()),
    +            array(
    +                '/**
    +                 * This is a docblock
    +                 *
    +                 * @throws \Exception with a description
    +                 */',
    +                'This is a docblock',
    +                array(
    +                    'throws' => array(array('type' => '\Exception', 'desc' => 'with a description')),
    +                ),
    +            ),
    +            array(
    +                '/**
    +                 * This is a slightly longer docblock
    +                 *
    +                 * @param int         $foo Is a Foo
    +                 * @param string      $bar With some sort of description
    +                 * @param \ClassName $baz is cool too
    +                 *
    +                 * @return int At least it isn\'t a string
    +                 */',
    +                'This is a slightly longer docblock',
    +                array(
    +                    'param' => array(
    +                        array('type' => 'int', 'desc' => 'Is a Foo', 'var' => '$foo'),
    +                        array('type' => 'string', 'desc' => 'With some sort of description', 'var' => '$bar'),
    +                        array('type' => '\ClassName', 'desc' => 'is cool too', 'var' => '$baz'),
    +                    ),
    +                    'return' => array(
    +                        array('type' => 'int', 'desc' => 'At least it isn\'t a string'),
    +                    ),
    +                ),
    +            ),
    +            array(
    +                '/**
    +                 * This is a docblock!
    +                 *
    +                 * It spans lines, too!
    +                 *
    +                 * @tagname plus a description
    +                 *
    +                 * @return
    +                 */',
    +                "This is a docblock!\n\nIt spans lines, too!",
    +                array(
    +                    'tagname' => array('plus a description'),
    +                ),
    +            ),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Util/MirrorTest.php b/core/vendor/psy/psysh/test/Psy/Test/Util/MirrorTest.php
    new file mode 100644
    index 0000000..82726d3
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Util/MirrorTest.php
    @@ -0,0 +1,80 @@
    +assertTrue($refl instanceof \ReflectionFunction);
    +
    +        $refl = Mirror::get('Psy\Test\Util\MirrorTest');
    +        $this->assertTrue($refl instanceof \ReflectionClass);
    +
    +        $refl = Mirror::get($this);
    +        $this->assertTrue($refl instanceof \ReflectionObject);
    +
    +        $refl = Mirror::get($this, 'FOO');
    +        $this->assertTrue($refl instanceof ReflectionConstant);
    +
    +        $refl = Mirror::get($this, 'bar');
    +        $this->assertTrue($refl instanceof \ReflectionProperty);
    +
    +        $refl = Mirror::get($this, 'baz');
    +        $this->assertTrue($refl instanceof \ReflectionProperty);
    +
    +        $refl = Mirror::get($this, 'aPublicMethod');
    +        $this->assertTrue($refl instanceof \ReflectionMethod);
    +
    +        $refl = Mirror::get($this, 'baz', Mirror::STATIC_PROPERTY);
    +        $this->assertTrue($refl instanceof \ReflectionProperty);
    +    }
    +
    +    /**
    +     * @expectedException \RuntimeException
    +     */
    +    public function testMirrorThrowsExceptions()
    +    {
    +        Mirror::get($this, 'notAMethod');
    +    }
    +
    +    /**
    +     * @expectedException \InvalidArgumentException
    +     * @dataProvider invalidArguments
    +     */
    +    public function testMirrorThrowsInvalidArgumentExceptions($value)
    +    {
    +        Mirror::get($value);
    +    }
    +
    +    public function invalidArguments()
    +    {
    +        return array(
    +            array('not_a_function_or_class'),
    +            array(array()),
    +            array(1),
    +        );
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/Psy/Test/Util/StrTest.php b/core/vendor/psy/psysh/test/Psy/Test/Util/StrTest.php
    new file mode 100644
    index 0000000..2972e25
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/Psy/Test/Util/StrTest.php
    @@ -0,0 +1,31 @@
    +assertEquals($expected, Str::unvis($input));
    +    }
    +
    +    public function testUnvisProvider()
    +    {
    +        //return require_once(__DIR__.'/../../../fixtures/unvis_fixtures.php');
    +        return json_decode(file_get_contents(__DIR__ . '/../../../fixtures/unvis_fixtures.json'));
    +    }
    +}
    diff --git a/core/vendor/psy/psysh/test/fixtures/config.php b/core/vendor/psy/psysh/test/fixtures/config.php
    new file mode 100644
    index 0000000..0057eca
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/fixtures/config.php
    @@ -0,0 +1,18 @@
    +setRuntimeDir(sys_get_temp_dir() . '/psysh_test/withconfig/temp');
    +
    +return array(
    +    'useReadline'       => true,
    +    'usePcntl'          => false,
    +    'errorLoggingLevel' => E_ALL & ~E_NOTICE,
    +);
    diff --git a/core/vendor/psy/psysh/test/fixtures/default/.config/psysh/config.php b/core/vendor/psy/psysh/test/fixtures/default/.config/psysh/config.php
    new file mode 100644
    index 0000000..b3d9bbc
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/fixtures/default/.config/psysh/config.php
    @@ -0,0 +1 @@
    + false,
    +    'usePcntl'    => true,
    +);
    diff --git a/core/vendor/psy/psysh/test/fixtures/unvis_fixtures.json b/core/vendor/psy/psysh/test/fixtures/unvis_fixtures.json
    new file mode 100644
    index 0000000..960fb02
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/fixtures/unvis_fixtures.json
    @@ -0,0 +1 @@
    +[["", ""], ["\\^A", "\u0001"], ["\\^B", "\u0002"], ["\\^C", "\u0003"], ["\\^D", "\u0004"], ["\\^E", "\u0005"], ["\\^F", "\u0006"], ["\\^G", "\u0007"], ["\\^H", "\b"], ["\\^I", "\t"], ["\\^J", "\n"], ["\\^K", "\u000b"], ["\\^L", "\f"], ["\\^M", "\r"], ["\\^N", "\u000e"], ["\\^O", "\u000f"], ["\\^P", "\u0010"], ["\\^Q", "\u0011"], ["\\^R", "\u0012"], ["\\^S", "\u0013"], ["\\^T", "\u0014"], ["\\^U", "\u0015"], ["\\^V", "\u0016"], ["\\^W", "\u0017"], ["\\^X", "\u0018"], ["\\^Y", "\u0019"], ["\\^Z", "\u001a"], ["\\^[", "\u001b"], ["\\^\\", "\u001c"], ["\\^]", "\u001d"], ["\\^^", "\u001e"], ["\\^_", "\u001f"], ["\\040", " "], ["!", "!"], ["\"", "\""], ["#", "#"], ["$", "$"], ["%", "%"], ["&", "&"], ["'", "'"], ["(", "("], [")", ")"], ["*", "*"], ["+", "+"], [",", ","], ["-", "-"], [".", "."], ["/", "/"], ["0", "0"], ["1", "1"], ["2", "2"], ["3", "3"], ["4", "4"], ["5", "5"], ["6", "6"], ["7", "7"], ["8", "8"], ["9", "9"], [":", ":"], [";", ";"], ["<", "<"], ["=", "="], [">", ">"], ["?", "?"], ["@", "@"], ["A", "A"], ["B", "B"], ["C", "C"], ["D", "D"], ["E", "E"], ["F", "F"], ["G", "G"], ["H", "H"], ["I", "I"], ["J", "J"], ["K", "K"], ["L", "L"], ["M", "M"], ["N", "N"], ["O", "O"], ["P", "P"], ["Q", "Q"], ["R", "R"], ["S", "S"], ["T", "T"], ["U", "U"], ["V", "V"], ["W", "W"], ["X", "X"], ["Y", "Y"], ["Z", "Z"], ["[", "["], ["\\\\", "\\"], ["]", "]"], ["^", "^"], ["_", "_"], ["`", "`"], ["a", "a"], ["b", "b"], ["c", "c"], ["d", "d"], ["e", "e"], ["f", "f"], ["g", "g"], ["h", "h"], ["i", "i"], ["j", "j"], ["k", "k"], ["l", "l"], ["m", "m"], ["n", "n"], ["o", "o"], ["p", "p"], ["q", "q"], ["r", "r"], ["s", "s"], ["t", "t"], ["u", "u"], ["v", "v"], ["w", "w"], ["x", "x"], ["y", "y"], ["z", "z"], ["{", "{"], ["|", "|"], ["}", "}"], ["~", "~"], ["\\^?", "\u007f"], ["\\M-B\\M^@", "\u0080"], ["\\M-B\\M^A", "\u0081"], ["\\M-B\\M^B", "\u0082"], ["\\M-B\\M^C", "\u0083"], ["\\M-B\\M^D", "\u0084"], ["\\M-B\\M^E", "\u0085"], ["\\M-B\\M^F", "\u0086"], ["\\M-B\\M^G", "\u0087"], ["\\M-B\\M^H", "\u0088"], ["\\M-B\\M^I", "\u0089"], ["\\M-B\\M^J", "\u008a"], ["\\M-B\\M^K", "\u008b"], ["\\M-B\\M^L", "\u008c"], ["\\M-B\\M^M", "\u008d"], ["\\M-B\\M^N", "\u008e"], ["\\M-B\\M^O", "\u008f"], ["\\M-B\\M^P", "\u0090"], ["\\M-B\\M^Q", "\u0091"], ["\\M-B\\M^R", "\u0092"], ["\\M-B\\M^S", "\u0093"], ["\\M-B\\M^T", "\u0094"], ["\\M-B\\M^U", "\u0095"], ["\\M-B\\M^V", "\u0096"], ["\\M-B\\M^W", "\u0097"], ["\\M-B\\M^X", "\u0098"], ["\\M-B\\M^Y", "\u0099"], ["\\M-B\\M^Z", "\u009a"], ["\\M-B\\M^[", "\u009b"], ["\\M-B\\M^\\", "\u009c"], ["\\M-B\\M^]", "\u009d"], ["\\M-B\\M^^", "\u009e"], ["\\M-B\\M^_", "\u009f"], ["\\M-B\\240", "\u00a0"], ["\\M-B\\M-!", "\u00a1"], ["\\M-B\\M-\"", "\u00a2"], ["\\M-B\\M-#", "\u00a3"], ["\\M-B\\M-$", "\u00a4"], ["\\M-B\\M-%", "\u00a5"], ["\\M-B\\M-&", "\u00a6"], ["\\M-B\\M-'", "\u00a7"], ["\\M-B\\M-(", "\u00a8"], ["\\M-B\\M-)", "\u00a9"], ["\\M-B\\M-*", "\u00aa"], ["\\M-B\\M-+", "\u00ab"], ["\\M-B\\M-,", "\u00ac"], ["\\M-B\\M--", "\u00ad"], ["\\M-B\\M-.", "\u00ae"], ["\\M-B\\M-/", "\u00af"], ["\\M-B\\M-0", "\u00b0"], ["\\M-B\\M-1", "\u00b1"], ["\\M-B\\M-2", "\u00b2"], ["\\M-B\\M-3", "\u00b3"], ["\\M-B\\M-4", "\u00b4"], ["\\M-B\\M-5", "\u00b5"], ["\\M-B\\M-6", "\u00b6"], ["\\M-B\\M-7", "\u00b7"], ["\\M-B\\M-8", "\u00b8"], ["\\M-B\\M-9", "\u00b9"], ["\\M-B\\M-:", "\u00ba"], ["\\M-B\\M-;", "\u00bb"], ["\\M-B\\M-<", "\u00bc"], ["\\M-B\\M-=", "\u00bd"], ["\\M-B\\M->", "\u00be"], ["\\M-B\\M-?", "\u00bf"], ["\\M-C\\M^@", "\u00c0"], ["\\M-C\\M^A", "\u00c1"], ["\\M-C\\M^B", "\u00c2"], ["\\M-C\\M^C", "\u00c3"], ["\\M-C\\M^D", "\u00c4"], ["\\M-C\\M^E", "\u00c5"], ["\\M-C\\M^F", "\u00c6"], ["\\M-C\\M^G", "\u00c7"], ["\\M-C\\M^H", "\u00c8"], ["\\M-C\\M^I", "\u00c9"], ["\\M-C\\M^J", "\u00ca"], ["\\M-C\\M^K", "\u00cb"], ["\\M-C\\M^L", "\u00cc"], ["\\M-C\\M^M", "\u00cd"], ["\\M-C\\M^N", "\u00ce"], ["\\M-C\\M^O", "\u00cf"], ["\\M-C\\M^P", "\u00d0"], ["\\M-C\\M^Q", "\u00d1"], ["\\M-C\\M^R", "\u00d2"], ["\\M-C\\M^S", "\u00d3"], ["\\M-C\\M^T", "\u00d4"], ["\\M-C\\M^U", "\u00d5"], ["\\M-C\\M^V", "\u00d6"], ["\\M-C\\M^W", "\u00d7"], ["\\M-C\\M^X", "\u00d8"], ["\\M-C\\M^Y", "\u00d9"], ["\\M-C\\M^Z", "\u00da"], ["\\M-C\\M^[", "\u00db"], ["\\M-C\\M^\\", "\u00dc"], ["\\M-C\\M^]", "\u00dd"], ["\\M-C\\M^^", "\u00de"], ["\\M-C\\M^_", "\u00df"], ["\\M-C\\240", "\u00e0"], ["\\M-C\\M-!", "\u00e1"], ["\\M-C\\M-\"", "\u00e2"], ["\\M-C\\M-#", "\u00e3"], ["\\M-C\\M-$", "\u00e4"], ["\\M-C\\M-%", "\u00e5"], ["\\M-C\\M-&", "\u00e6"], ["\\M-C\\M-'", "\u00e7"], ["\\M-C\\M-(", "\u00e8"], ["\\M-C\\M-)", "\u00e9"], ["\\M-C\\M-*", "\u00ea"], ["\\M-C\\M-+", "\u00eb"], ["\\M-C\\M-,", "\u00ec"], ["\\M-C\\M--", "\u00ed"], ["\\M-C\\M-.", "\u00ee"], ["\\M-C\\M-/", "\u00ef"], ["\\M-C\\M-0", "\u00f0"], ["\\M-C\\M-1", "\u00f1"], ["\\M-C\\M-2", "\u00f2"], ["\\M-C\\M-3", "\u00f3"], ["\\M-C\\M-4", "\u00f4"], ["\\M-C\\M-5", "\u00f5"], ["\\M-C\\M-6", "\u00f6"], ["\\M-C\\M-7", "\u00f7"], ["\\M-C\\M-8", "\u00f8"], ["\\M-C\\M-9", "\u00f9"], ["\\M-C\\M-:", "\u00fa"], ["\\M-C\\M-;", "\u00fb"], ["\\M-C\\M-<", "\u00fc"], ["\\M-C\\M-=", "\u00fd"], ["\\M-C\\M->", "\u00fe"], ["\\M-C\\M-?", "\u00ff"], ["\\M-D\\M^@", "\u0100"], ["\\M-D\\M^A", "\u0101"], ["\\M-D\\M^B", "\u0102"], ["\\M-D\\M^C", "\u0103"], ["\\M-D\\M^D", "\u0104"], ["\\M-D\\M^E", "\u0105"], ["\\M-D\\M^F", "\u0106"], ["\\M-D\\M^G", "\u0107"], ["\\M-D\\M^H", "\u0108"], ["\\M-D\\M^I", "\u0109"], ["\\M-D\\M^J", "\u010a"], ["\\M-D\\M^K", "\u010b"], ["\\M-D\\M^L", "\u010c"], ["\\M-D\\M^M", "\u010d"], ["\\M-D\\M^N", "\u010e"], ["\\M-D\\M^O", "\u010f"], ["\\M-D\\M^P", "\u0110"], ["\\M-D\\M^Q", "\u0111"], ["\\M-D\\M^R", "\u0112"], ["\\M-D\\M^S", "\u0113"], ["\\M-D\\M^T", "\u0114"], ["\\M-D\\M^U", "\u0115"], ["\\M-D\\M^V", "\u0116"], ["\\M-D\\M^W", "\u0117"], ["\\M-D\\M^X", "\u0118"], ["\\M-D\\M^Y", "\u0119"], ["\\M-D\\M^Z", "\u011a"], ["\\M-D\\M^[", "\u011b"], ["\\M-D\\M^\\", "\u011c"], ["\\M-D\\M^]", "\u011d"], ["\\M-D\\M^^", "\u011e"], ["\\M-D\\M^_", "\u011f"], ["\\M-D\\240", "\u0120"], ["\\M-D\\M-!", "\u0121"], ["\\M-D\\M-\"", "\u0122"], ["\\M-D\\M-#", "\u0123"], ["\\M-D\\M-$", "\u0124"], ["\\M-D\\M-%", "\u0125"], ["\\M-D\\M-&", "\u0126"], ["\\M-D\\M-'", "\u0127"], ["\\M-D\\M-(", "\u0128"], ["\\M-D\\M-)", "\u0129"], ["\\M-D\\M-*", "\u012a"], ["\\M-D\\M-+", "\u012b"], ["\\M-D\\M-,", "\u012c"], ["\\M-D\\M--", "\u012d"], ["\\M-D\\M-.", "\u012e"], ["\\M-D\\M-/", "\u012f"], ["\\M-D\\M-0", "\u0130"], ["\\M-D\\M-1", "\u0131"], ["\\M-D\\M-2", "\u0132"], ["\\M-D\\M-3", "\u0133"], ["\\M-D\\M-4", "\u0134"], ["\\M-D\\M-5", "\u0135"], ["\\M-D\\M-6", "\u0136"], ["\\M-D\\M-7", "\u0137"], ["\\M-D\\M-8", "\u0138"], ["\\M-D\\M-9", "\u0139"], ["\\M-D\\M-:", "\u013a"], ["\\M-D\\M-;", "\u013b"], ["\\M-D\\M-<", "\u013c"], ["\\M-D\\M-=", "\u013d"], ["\\M-D\\M->", "\u013e"], ["\\M-D\\M-?", "\u013f"], ["\\M-E\\M^@", "\u0140"], ["\\M-E\\M^A", "\u0141"], ["\\M-E\\M^B", "\u0142"], ["\\M-E\\M^C", "\u0143"], ["\\M-E\\M^D", "\u0144"], ["\\M-E\\M^E", "\u0145"], ["\\M-E\\M^F", "\u0146"], ["\\M-E\\M^G", "\u0147"], ["\\M-E\\M^H", "\u0148"], ["\\M-E\\M^I", "\u0149"], ["\\M-E\\M^J", "\u014a"], ["\\M-E\\M^K", "\u014b"], ["\\M-E\\M^L", "\u014c"], ["\\M-E\\M^M", "\u014d"], ["\\M-E\\M^N", "\u014e"], ["\\M-E\\M^O", "\u014f"], ["\\M-E\\M^P", "\u0150"], ["\\M-E\\M^Q", "\u0151"], ["\\M-E\\M^R", "\u0152"], ["\\M-E\\M^S", "\u0153"], ["\\M-E\\M^T", "\u0154"], ["\\M-E\\M^U", "\u0155"], ["\\M-E\\M^V", "\u0156"], ["\\M-E\\M^W", "\u0157"], ["\\M-E\\M^X", "\u0158"], ["\\M-E\\M^Y", "\u0159"], ["\\M-E\\M^Z", "\u015a"], ["\\M-E\\M^[", "\u015b"], ["\\M-E\\M^\\", "\u015c"], ["\\M-E\\M^]", "\u015d"], ["\\M-E\\M^^", "\u015e"], ["\\M-E\\M^_", "\u015f"], ["\\M-E\\240", "\u0160"], ["\\M-E\\M-!", "\u0161"], ["\\M-E\\M-\"", "\u0162"], ["\\M-E\\M-#", "\u0163"], ["\\M-E\\M-$", "\u0164"], ["\\M-E\\M-%", "\u0165"], ["\\M-E\\M-&", "\u0166"], ["\\M-E\\M-'", "\u0167"], ["\\M-E\\M-(", "\u0168"], ["\\M-E\\M-)", "\u0169"], ["\\M-E\\M-*", "\u016a"], ["\\M-E\\M-+", "\u016b"], ["\\M-E\\M-,", "\u016c"], ["\\M-E\\M--", "\u016d"], ["\\M-E\\M-.", "\u016e"], ["\\M-E\\M-/", "\u016f"], ["\\M-E\\M-0", "\u0170"], ["\\M-E\\M-1", "\u0171"], ["\\M-E\\M-2", "\u0172"], ["\\M-E\\M-3", "\u0173"], ["\\M-E\\M-4", "\u0174"], ["\\M-E\\M-5", "\u0175"], ["\\M-E\\M-6", "\u0176"], ["\\M-E\\M-7", "\u0177"], ["\\M-E\\M-8", "\u0178"], ["\\M-E\\M-9", "\u0179"], ["\\M-E\\M-:", "\u017a"], ["\\M-E\\M-;", "\u017b"], ["\\M-E\\M-<", "\u017c"], ["\\M-E\\M-=", "\u017d"], ["\\M-E\\M->", "\u017e"], ["\\M-E\\M-?", "\u017f"], ["\\M-F\\M^@", "\u0180"], ["\\M-F\\M^A", "\u0181"], ["\\M-F\\M^B", "\u0182"], ["\\M-F\\M^C", "\u0183"], ["\\M-F\\M^D", "\u0184"], ["\\M-F\\M^E", "\u0185"], ["\\M-F\\M^F", "\u0186"], ["\\M-F\\M^G", "\u0187"], ["\\M-F\\M^H", "\u0188"], ["\\M-F\\M^I", "\u0189"], ["\\M-F\\M^J", "\u018a"], ["\\M-F\\M^K", "\u018b"], ["\\M-F\\M^L", "\u018c"], ["\\M-F\\M^M", "\u018d"], ["\\M-F\\M^N", "\u018e"], ["\\M-F\\M^O", "\u018f"], ["\\M-F\\M^P", "\u0190"], ["\\M-F\\M^Q", "\u0191"], ["\\M-F\\M^R", "\u0192"], ["\\M-F\\M^S", "\u0193"], ["\\M-F\\M^T", "\u0194"], ["\\M-F\\M^U", "\u0195"], ["\\M-F\\M^V", "\u0196"], ["\\M-F\\M^W", "\u0197"], ["\\M-F\\M^X", "\u0198"], ["\\M-F\\M^Y", "\u0199"], ["\\M-F\\M^Z", "\u019a"], ["\\M-F\\M^[", "\u019b"], ["\\M-F\\M^\\", "\u019c"], ["\\M-F\\M^]", "\u019d"], ["\\M-F\\M^^", "\u019e"], ["\\M-F\\M^_", "\u019f"], ["\\M-F\\240", "\u01a0"], ["\\M-F\\M-!", "\u01a1"], ["\\M-F\\M-\"", "\u01a2"], ["\\M-F\\M-#", "\u01a3"], ["\\M-F\\M-$", "\u01a4"], ["\\M-F\\M-%", "\u01a5"], ["\\M-F\\M-&", "\u01a6"], ["\\M-F\\M-'", "\u01a7"], ["\\M-F\\M-(", "\u01a8"], ["\\M-F\\M-)", "\u01a9"], ["\\M-F\\M-*", "\u01aa"], ["\\M-F\\M-+", "\u01ab"], ["\\M-F\\M-,", "\u01ac"], ["\\M-F\\M--", "\u01ad"], ["\\M-F\\M-.", "\u01ae"], ["\\M-F\\M-/", "\u01af"], ["\\M-F\\M-0", "\u01b0"], ["\\M-F\\M-1", "\u01b1"], ["\\M-F\\M-2", "\u01b2"], ["\\M-F\\M-3", "\u01b3"], ["\\M-F\\M-4", "\u01b4"], ["\\M-F\\M-5", "\u01b5"], ["\\M-F\\M-6", "\u01b6"], ["\\M-F\\M-7", "\u01b7"], ["\\M-F\\M-8", "\u01b8"], ["\\M-F\\M-9", "\u01b9"], ["\\M-F\\M-:", "\u01ba"], ["\\M-F\\M-;", "\u01bb"], ["\\M-F\\M-<", "\u01bc"], ["\\M-F\\M-=", "\u01bd"], ["\\M-F\\M->", "\u01be"], ["\\M-F\\M-?", "\u01bf"], ["\\M-G\\M^@", "\u01c0"], ["\\M-G\\M^A", "\u01c1"], ["\\M-G\\M^B", "\u01c2"], ["\\M-G\\M^C", "\u01c3"], ["\\M-G\\M^D", "\u01c4"], ["\\M-G\\M^E", "\u01c5"], ["\\M-G\\M^F", "\u01c6"], ["\\M-G\\M^G", "\u01c7"], ["\\M-G\\M^H", "\u01c8"], ["\\M-G\\M^I", "\u01c9"], ["\\M-G\\M^J", "\u01ca"], ["\\M-G\\M^K", "\u01cb"], ["\\M-G\\M^L", "\u01cc"], ["\\M-G\\M^M", "\u01cd"], ["\\M-G\\M^N", "\u01ce"], ["\\M-G\\M^O", "\u01cf"], ["\\M-G\\M^P", "\u01d0"], ["\\M-G\\M^Q", "\u01d1"], ["\\M-G\\M^R", "\u01d2"], ["\\M-G\\M^S", "\u01d3"], ["\\M-G\\M^T", "\u01d4"], ["\\M-G\\M^U", "\u01d5"], ["\\M-G\\M^V", "\u01d6"], ["\\M-G\\M^W", "\u01d7"], ["\\M-G\\M^X", "\u01d8"], ["\\M-G\\M^Y", "\u01d9"], ["\\M-G\\M^Z", "\u01da"], ["\\M-G\\M^[", "\u01db"], ["\\M-G\\M^\\", "\u01dc"], ["\\M-G\\M^]", "\u01dd"], ["\\M-G\\M^^", "\u01de"], ["\\M-G\\M^_", "\u01df"], ["\\M-G\\240", "\u01e0"], ["\\M-G\\M-!", "\u01e1"], ["\\M-G\\M-\"", "\u01e2"], ["\\M-G\\M-#", "\u01e3"], ["\\M-G\\M-$", "\u01e4"], ["\\M-G\\M-%", "\u01e5"], ["\\M-G\\M-&", "\u01e6"], ["\\M-G\\M-'", "\u01e7"], ["\\M-G\\M-(", "\u01e8"], ["\\M-G\\M-)", "\u01e9"], ["\\M-G\\M-*", "\u01ea"], ["\\M-G\\M-+", "\u01eb"], ["\\M-G\\M-,", "\u01ec"], ["\\M-G\\M--", "\u01ed"], ["\\M-G\\M-.", "\u01ee"], ["\\M-G\\M-/", "\u01ef"], ["\\M-G\\M-0", "\u01f0"], ["\\M-G\\M-1", "\u01f1"], ["\\M-G\\M-2", "\u01f2"], ["\\M-G\\M-3", "\u01f3"], ["\\M-G\\M-4", "\u01f4"], ["\\M-G\\M-5", "\u01f5"], ["\\M-G\\M-6", "\u01f6"], ["\\M-G\\M-7", "\u01f7"], ["\\M-G\\M-8", "\u01f8"], ["\\M-G\\M-9", "\u01f9"], ["\\M-G\\M-:", "\u01fa"], ["\\M-G\\M-;", "\u01fb"], ["\\M-G\\M-<", "\u01fc"], ["\\M-G\\M-=", "\u01fd"], ["\\M-G\\M->", "\u01fe"], ["\\M-G\\M-?", "\u01ff"], ["\\M-H\\M^@", "\u0200"], ["\\M-H\\M^A", "\u0201"], ["\\M-H\\M^B", "\u0202"], ["\\M-H\\M^C", "\u0203"], ["\\M-H\\M^D", "\u0204"], ["\\M-H\\M^E", "\u0205"], ["\\M-H\\M^F", "\u0206"], ["\\M-H\\M^G", "\u0207"], ["\\M-H\\M^H", "\u0208"], ["\\M-H\\M^I", "\u0209"], ["\\M-H\\M^J", "\u020a"], ["\\M-H\\M^K", "\u020b"], ["\\M-H\\M^L", "\u020c"], ["\\M-H\\M^M", "\u020d"], ["\\M-H\\M^N", "\u020e"], ["\\M-H\\M^O", "\u020f"], ["\\M-H\\M^P", "\u0210"], ["\\M-H\\M^Q", "\u0211"], ["\\M-H\\M^R", "\u0212"], ["\\M-H\\M^S", "\u0213"], ["\\M-H\\M^T", "\u0214"], ["\\M-H\\M^U", "\u0215"], ["\\M-H\\M^V", "\u0216"], ["\\M-H\\M^W", "\u0217"], ["\\M-H\\M^X", "\u0218"], ["\\M-H\\M^Y", "\u0219"], ["\\M-H\\M^Z", "\u021a"], ["\\M-H\\M^[", "\u021b"], ["\\M-H\\M^\\", "\u021c"], ["\\M-H\\M^]", "\u021d"], ["\\M-H\\M^^", "\u021e"], ["\\M-H\\M^_", "\u021f"], ["\\M-H\\240", "\u0220"], ["\\M-H\\M-!", "\u0221"], ["\\M-H\\M-\"", "\u0222"], ["\\M-H\\M-#", "\u0223"], ["\\M-H\\M-$", "\u0224"], ["\\M-H\\M-%", "\u0225"], ["\\M-H\\M-&", "\u0226"], ["\\M-H\\M-'", "\u0227"], ["\\M-H\\M-(", "\u0228"], ["\\M-H\\M-)", "\u0229"], ["\\M-H\\M-*", "\u022a"], ["\\M-H\\M-+", "\u022b"], ["\\M-H\\M-,", "\u022c"], ["\\M-H\\M--", "\u022d"], ["\\M-H\\M-.", "\u022e"], ["\\M-H\\M-/", "\u022f"], ["\\M-H\\M-0", "\u0230"], ["\\M-H\\M-1", "\u0231"], ["\\M-H\\M-2", "\u0232"], ["\\M-H\\M-3", "\u0233"], ["\\M-H\\M-4", "\u0234"], ["\\M-H\\M-5", "\u0235"], ["\\M-H\\M-6", "\u0236"], ["\\M-H\\M-7", "\u0237"], ["\\M-H\\M-8", "\u0238"], ["\\M-H\\M-9", "\u0239"], ["\\M-H\\M-:", "\u023a"], ["\\M-H\\M-;", "\u023b"], ["\\M-H\\M-<", "\u023c"], ["\\M-H\\M-=", "\u023d"], ["\\M-H\\M->", "\u023e"], ["\\M-H\\M-?", "\u023f"], ["\\M-I\\M^@", "\u0240"], ["\\M-I\\M^A", "\u0241"], ["\\M-I\\M^B", "\u0242"], ["\\M-I\\M^C", "\u0243"], ["\\M-I\\M^D", "\u0244"], ["\\M-I\\M^E", "\u0245"], ["\\M-I\\M^F", "\u0246"], ["\\M-I\\M^G", "\u0247"], ["\\M-I\\M^H", "\u0248"], ["\\M-I\\M^I", "\u0249"], ["\\M-I\\M^J", "\u024a"], ["\\M-I\\M^K", "\u024b"], ["\\M-I\\M^L", "\u024c"], ["\\M-I\\M^M", "\u024d"], ["\\M-I\\M^N", "\u024e"], ["\\M-I\\M^O", "\u024f"], ["\\M-M\\M-0", "\u0370"], ["\\M-M\\M-1", "\u0371"], ["\\M-M\\M-2", "\u0372"], ["\\M-M\\M-3", "\u0373"], ["\\M-M\\M-4", "\u0374"], ["\\M-M\\M-5", "\u0375"], ["\\M-M\\M-6", "\u0376"], ["\\M-M\\M-7", "\u0377"], ["\\M-M\\M-8", "\u0378"], ["\\M-M\\M-9", "\u0379"], ["\\M-M\\M-:", "\u037a"], ["\\M-M\\M-;", "\u037b"], ["\\M-M\\M-<", "\u037c"], ["\\M-M\\M-=", "\u037d"], ["\\M-M\\M->", "\u037e"], ["\\M-M\\M-?", "\u037f"], ["\\M-N\\M^@", "\u0380"], ["\\M-N\\M^A", "\u0381"], ["\\M-N\\M^B", "\u0382"], ["\\M-N\\M^C", "\u0383"], ["\\M-N\\M^D", "\u0384"], ["\\M-N\\M^E", "\u0385"], ["\\M-N\\M^F", "\u0386"], ["\\M-N\\M^G", "\u0387"], ["\\M-N\\M^H", "\u0388"], ["\\M-N\\M^I", "\u0389"], ["\\M-N\\M^J", "\u038a"], ["\\M-N\\M^K", "\u038b"], ["\\M-N\\M^L", "\u038c"], ["\\M-N\\M^M", "\u038d"], ["\\M-N\\M^N", "\u038e"], ["\\M-N\\M^O", "\u038f"], ["\\M-N\\M^P", "\u0390"], ["\\M-N\\M^Q", "\u0391"], ["\\M-N\\M^R", "\u0392"], ["\\M-N\\M^S", "\u0393"], ["\\M-N\\M^T", "\u0394"], ["\\M-N\\M^U", "\u0395"], ["\\M-N\\M^V", "\u0396"], ["\\M-N\\M^W", "\u0397"], ["\\M-N\\M^X", "\u0398"], ["\\M-N\\M^Y", "\u0399"], ["\\M-N\\M^Z", "\u039a"], ["\\M-N\\M^[", "\u039b"], ["\\M-N\\M^\\", "\u039c"], ["\\M-N\\M^]", "\u039d"], ["\\M-N\\M^^", "\u039e"], ["\\M-N\\M^_", "\u039f"], ["\\M-N\\240", "\u03a0"], ["\\M-N\\M-!", "\u03a1"], ["\\M-N\\M-\"", "\u03a2"], ["\\M-N\\M-#", "\u03a3"], ["\\M-N\\M-$", "\u03a4"], ["\\M-N\\M-%", "\u03a5"], ["\\M-N\\M-&", "\u03a6"], ["\\M-N\\M-'", "\u03a7"], ["\\M-N\\M-(", "\u03a8"], ["\\M-N\\M-)", "\u03a9"], ["\\M-N\\M-*", "\u03aa"], ["\\M-N\\M-+", "\u03ab"], ["\\M-N\\M-,", "\u03ac"], ["\\M-N\\M--", "\u03ad"], ["\\M-N\\M-.", "\u03ae"], ["\\M-N\\M-/", "\u03af"], ["\\M-N\\M-0", "\u03b0"], ["\\M-N\\M-1", "\u03b1"], ["\\M-N\\M-2", "\u03b2"], ["\\M-N\\M-3", "\u03b3"], ["\\M-N\\M-4", "\u03b4"], ["\\M-N\\M-5", "\u03b5"], ["\\M-N\\M-6", "\u03b6"], ["\\M-N\\M-7", "\u03b7"], ["\\M-N\\M-8", "\u03b8"], ["\\M-N\\M-9", "\u03b9"], ["\\M-N\\M-:", "\u03ba"], ["\\M-N\\M-;", "\u03bb"], ["\\M-N\\M-<", "\u03bc"], ["\\M-N\\M-=", "\u03bd"], ["\\M-N\\M->", "\u03be"], ["\\M-N\\M-?", "\u03bf"], ["\\M-O\\M^@", "\u03c0"], ["\\M-O\\M^A", "\u03c1"], ["\\M-O\\M^B", "\u03c2"], ["\\M-O\\M^C", "\u03c3"], ["\\M-O\\M^D", "\u03c4"], ["\\M-O\\M^E", "\u03c5"], ["\\M-O\\M^F", "\u03c6"], ["\\M-O\\M^G", "\u03c7"], ["\\M-O\\M^H", "\u03c8"], ["\\M-O\\M^I", "\u03c9"], ["\\M-O\\M^J", "\u03ca"], ["\\M-O\\M^K", "\u03cb"], ["\\M-O\\M^L", "\u03cc"], ["\\M-O\\M^M", "\u03cd"], ["\\M-O\\M^N", "\u03ce"], ["\\M-O\\M^O", "\u03cf"], ["\\M-O\\M^P", "\u03d0"], ["\\M-O\\M^Q", "\u03d1"], ["\\M-O\\M^R", "\u03d2"], ["\\M-O\\M^S", "\u03d3"], ["\\M-O\\M^T", "\u03d4"], ["\\M-O\\M^U", "\u03d5"], ["\\M-O\\M^V", "\u03d6"], ["\\M-O\\M^W", "\u03d7"], ["\\M-O\\M^X", "\u03d8"], ["\\M-O\\M^Y", "\u03d9"], ["\\M-O\\M^Z", "\u03da"], ["\\M-O\\M^[", "\u03db"], ["\\M-O\\M^\\", "\u03dc"], ["\\M-O\\M^]", "\u03dd"], ["\\M-O\\M^^", "\u03de"], ["\\M-O\\M^_", "\u03df"], ["\\M-O\\240", "\u03e0"], ["\\M-O\\M-!", "\u03e1"], ["\\M-O\\M-\"", "\u03e2"], ["\\M-O\\M-#", "\u03e3"], ["\\M-O\\M-$", "\u03e4"], ["\\M-O\\M-%", "\u03e5"], ["\\M-O\\M-&", "\u03e6"], ["\\M-O\\M-'", "\u03e7"], ["\\M-O\\M-(", "\u03e8"], ["\\M-O\\M-)", "\u03e9"], ["\\M-O\\M-*", "\u03ea"], ["\\M-O\\M-+", "\u03eb"], ["\\M-O\\M-,", "\u03ec"], ["\\M-O\\M--", "\u03ed"], ["\\M-O\\M-.", "\u03ee"], ["\\M-O\\M-/", "\u03ef"], ["\\M-O\\M-0", "\u03f0"], ["\\M-O\\M-1", "\u03f1"], ["\\M-O\\M-2", "\u03f2"], ["\\M-O\\M-3", "\u03f3"], ["\\M-O\\M-4", "\u03f4"], ["\\M-O\\M-5", "\u03f5"], ["\\M-O\\M-6", "\u03f6"], ["\\M-O\\M-7", "\u03f7"], ["\\M-O\\M-8", "\u03f8"], ["\\M-O\\M-9", "\u03f9"], ["\\M-O\\M-:", "\u03fa"], ["\\M-O\\M-;", "\u03fb"], ["\\M-O\\M-<", "\u03fc"], ["\\M-O\\M-=", "\u03fd"], ["\\M-O\\M->", "\u03fe"], ["\\M-O\\M-?", "\u03ff"], ["\\M-P\\M^@", "\u0400"], ["\\M-P\\M^A", "\u0401"], ["\\M-P\\M^B", "\u0402"], ["\\M-P\\M^C", "\u0403"], ["\\M-P\\M^D", "\u0404"], ["\\M-P\\M^E", "\u0405"], ["\\M-P\\M^F", "\u0406"], ["\\M-P\\M^G", "\u0407"], ["\\M-P\\M^H", "\u0408"], ["\\M-P\\M^I", "\u0409"], ["\\M-P\\M^J", "\u040a"], ["\\M-P\\M^K", "\u040b"], ["\\M-P\\M^L", "\u040c"], ["\\M-P\\M^M", "\u040d"], ["\\M-P\\M^N", "\u040e"], ["\\M-P\\M^O", "\u040f"], ["\\M-P\\M^P", "\u0410"], ["\\M-P\\M^Q", "\u0411"], ["\\M-P\\M^R", "\u0412"], ["\\M-P\\M^S", "\u0413"], ["\\M-P\\M^T", "\u0414"], ["\\M-P\\M^U", "\u0415"], ["\\M-P\\M^V", "\u0416"], ["\\M-P\\M^W", "\u0417"], ["\\M-P\\M^X", "\u0418"], ["\\M-P\\M^Y", "\u0419"], ["\\M-P\\M^Z", "\u041a"], ["\\M-P\\M^[", "\u041b"], ["\\M-P\\M^\\", "\u041c"], ["\\M-P\\M^]", "\u041d"], ["\\M-P\\M^^", "\u041e"], ["\\M-P\\M^_", "\u041f"], ["\\M-P\\240", "\u0420"], ["\\M-P\\M-!", "\u0421"], ["\\M-P\\M-\"", "\u0422"], ["\\M-P\\M-#", "\u0423"], ["\\M-P\\M-$", "\u0424"], ["\\M-P\\M-%", "\u0425"], ["\\M-P\\M-&", "\u0426"], ["\\M-P\\M-'", "\u0427"], ["\\M-P\\M-(", "\u0428"], ["\\M-P\\M-)", "\u0429"], ["\\M-P\\M-*", "\u042a"], ["\\M-P\\M-+", "\u042b"], ["\\M-P\\M-,", "\u042c"], ["\\M-P\\M--", "\u042d"], ["\\M-P\\M-.", "\u042e"], ["\\M-P\\M-/", "\u042f"], ["\\M-P\\M-0", "\u0430"], ["\\M-P\\M-1", "\u0431"], ["\\M-P\\M-2", "\u0432"], ["\\M-P\\M-3", "\u0433"], ["\\M-P\\M-4", "\u0434"], ["\\M-P\\M-5", "\u0435"], ["\\M-P\\M-6", "\u0436"], ["\\M-P\\M-7", "\u0437"], ["\\M-P\\M-8", "\u0438"], ["\\M-P\\M-9", "\u0439"], ["\\M-P\\M-:", "\u043a"], ["\\M-P\\M-;", "\u043b"], ["\\M-P\\M-<", "\u043c"], ["\\M-P\\M-=", "\u043d"], ["\\M-P\\M->", "\u043e"], ["\\M-P\\M-?", "\u043f"], ["\\M-Q\\M^@", "\u0440"], ["\\M-Q\\M^A", "\u0441"], ["\\M-Q\\M^B", "\u0442"], ["\\M-Q\\M^C", "\u0443"], ["\\M-Q\\M^D", "\u0444"], ["\\M-Q\\M^E", "\u0445"], ["\\M-Q\\M^F", "\u0446"], ["\\M-Q\\M^G", "\u0447"], ["\\M-Q\\M^H", "\u0448"], ["\\M-Q\\M^I", "\u0449"], ["\\M-Q\\M^J", "\u044a"], ["\\M-Q\\M^K", "\u044b"], ["\\M-Q\\M^L", "\u044c"], ["\\M-Q\\M^M", "\u044d"], ["\\M-Q\\M^N", "\u044e"], ["\\M-Q\\M^O", "\u044f"], ["\\M-Q\\M^P", "\u0450"], ["\\M-Q\\M^Q", "\u0451"], ["\\M-Q\\M^R", "\u0452"], ["\\M-Q\\M^S", "\u0453"], ["\\M-Q\\M^T", "\u0454"], ["\\M-Q\\M^U", "\u0455"], ["\\M-Q\\M^V", "\u0456"], ["\\M-Q\\M^W", "\u0457"], ["\\M-Q\\M^X", "\u0458"], ["\\M-Q\\M^Y", "\u0459"], ["\\M-Q\\M^Z", "\u045a"], ["\\M-Q\\M^[", "\u045b"], ["\\M-Q\\M^\\", "\u045c"], ["\\M-Q\\M^]", "\u045d"], ["\\M-Q\\M^^", "\u045e"], ["\\M-Q\\M^_", "\u045f"], ["\\M-Q\\240", "\u0460"], ["\\M-Q\\M-!", "\u0461"], ["\\M-Q\\M-\"", "\u0462"], ["\\M-Q\\M-#", "\u0463"], ["\\M-Q\\M-$", "\u0464"], ["\\M-Q\\M-%", "\u0465"], ["\\M-Q\\M-&", "\u0466"], ["\\M-Q\\M-'", "\u0467"], ["\\M-Q\\M-(", "\u0468"], ["\\M-Q\\M-)", "\u0469"], ["\\M-Q\\M-*", "\u046a"], ["\\M-Q\\M-+", "\u046b"], ["\\M-Q\\M-,", "\u046c"], ["\\M-Q\\M--", "\u046d"], ["\\M-Q\\M-.", "\u046e"], ["\\M-Q\\M-/", "\u046f"], ["\\M-Q\\M-0", "\u0470"], ["\\M-Q\\M-1", "\u0471"], ["\\M-Q\\M-2", "\u0472"], ["\\M-Q\\M-3", "\u0473"], ["\\M-Q\\M-4", "\u0474"], ["\\M-Q\\M-5", "\u0475"], ["\\M-Q\\M-6", "\u0476"], ["\\M-Q\\M-7", "\u0477"], ["\\M-Q\\M-8", "\u0478"], ["\\M-Q\\M-9", "\u0479"], ["\\M-Q\\M-:", "\u047a"], ["\\M-Q\\M-;", "\u047b"], ["\\M-Q\\M-<", "\u047c"], ["\\M-Q\\M-=", "\u047d"], ["\\M-Q\\M->", "\u047e"], ["\\M-Q\\M-?", "\u047f"], ["\\M-R\\M^@", "\u0480"], ["\\M-R\\M^A", "\u0481"], ["\\M-R\\M^B", "\u0482"], ["\\M-R\\M^C", "\u0483"], ["\\M-R\\M^D", "\u0484"], ["\\M-R\\M^E", "\u0485"], ["\\M-R\\M^F", "\u0486"], ["\\M-R\\M^G", "\u0487"], ["\\M-R\\M^H", "\u0488"], ["\\M-R\\M^I", "\u0489"], ["\\M-R\\M^J", "\u048a"], ["\\M-R\\M^K", "\u048b"], ["\\M-R\\M^L", "\u048c"], ["\\M-R\\M^M", "\u048d"], ["\\M-R\\M^N", "\u048e"], ["\\M-R\\M^O", "\u048f"], ["\\M-R\\M^P", "\u0490"], ["\\M-R\\M^Q", "\u0491"], ["\\M-R\\M^R", "\u0492"], ["\\M-R\\M^S", "\u0493"], ["\\M-R\\M^T", "\u0494"], ["\\M-R\\M^U", "\u0495"], ["\\M-R\\M^V", "\u0496"], ["\\M-R\\M^W", "\u0497"], ["\\M-R\\M^X", "\u0498"], ["\\M-R\\M^Y", "\u0499"], ["\\M-R\\M^Z", "\u049a"], ["\\M-R\\M^[", "\u049b"], ["\\M-R\\M^\\", "\u049c"], ["\\M-R\\M^]", "\u049d"], ["\\M-R\\M^^", "\u049e"], ["\\M-R\\M^_", "\u049f"], ["\\M-R\\240", "\u04a0"], ["\\M-R\\M-!", "\u04a1"], ["\\M-R\\M-\"", "\u04a2"], ["\\M-R\\M-#", "\u04a3"], ["\\M-R\\M-$", "\u04a4"], ["\\M-R\\M-%", "\u04a5"], ["\\M-R\\M-&", "\u04a6"], ["\\M-R\\M-'", "\u04a7"], ["\\M-R\\M-(", "\u04a8"], ["\\M-R\\M-)", "\u04a9"], ["\\M-R\\M-*", "\u04aa"], ["\\M-R\\M-+", "\u04ab"], ["\\M-R\\M-,", "\u04ac"], ["\\M-R\\M--", "\u04ad"], ["\\M-R\\M-.", "\u04ae"], ["\\M-R\\M-/", "\u04af"], ["\\M-R\\M-0", "\u04b0"], ["\\M-R\\M-1", "\u04b1"], ["\\M-R\\M-2", "\u04b2"], ["\\M-R\\M-3", "\u04b3"], ["\\M-R\\M-4", "\u04b4"], ["\\M-R\\M-5", "\u04b5"], ["\\M-R\\M-6", "\u04b6"], ["\\M-R\\M-7", "\u04b7"], ["\\M-R\\M-8", "\u04b8"], ["\\M-R\\M-9", "\u04b9"], ["\\M-R\\M-:", "\u04ba"], ["\\M-R\\M-;", "\u04bb"], ["\\M-R\\M-<", "\u04bc"], ["\\M-R\\M-=", "\u04bd"], ["\\M-R\\M->", "\u04be"], ["\\M-R\\M-?", "\u04bf"], ["\\M-S\\M^@", "\u04c0"], ["\\M-S\\M^A", "\u04c1"], ["\\M-S\\M^B", "\u04c2"], ["\\M-S\\M^C", "\u04c3"], ["\\M-S\\M^D", "\u04c4"], ["\\M-S\\M^E", "\u04c5"], ["\\M-S\\M^F", "\u04c6"], ["\\M-S\\M^G", "\u04c7"], ["\\M-S\\M^H", "\u04c8"], ["\\M-S\\M^I", "\u04c9"], ["\\M-S\\M^J", "\u04ca"], ["\\M-S\\M^K", "\u04cb"], ["\\M-S\\M^L", "\u04cc"], ["\\M-S\\M^M", "\u04cd"], ["\\M-S\\M^N", "\u04ce"], ["\\M-S\\M^O", "\u04cf"], ["\\M-S\\M^P", "\u04d0"], ["\\M-S\\M^Q", "\u04d1"], ["\\M-S\\M^R", "\u04d2"], ["\\M-S\\M^S", "\u04d3"], ["\\M-S\\M^T", "\u04d4"], ["\\M-S\\M^U", "\u04d5"], ["\\M-S\\M^V", "\u04d6"], ["\\M-S\\M^W", "\u04d7"], ["\\M-S\\M^X", "\u04d8"], ["\\M-S\\M^Y", "\u04d9"], ["\\M-S\\M^Z", "\u04da"], ["\\M-S\\M^[", "\u04db"], ["\\M-S\\M^\\", "\u04dc"], ["\\M-S\\M^]", "\u04dd"], ["\\M-S\\M^^", "\u04de"], ["\\M-S\\M^_", "\u04df"], ["\\M-S\\240", "\u04e0"], ["\\M-S\\M-!", "\u04e1"], ["\\M-S\\M-\"", "\u04e2"], ["\\M-S\\M-#", "\u04e3"], ["\\M-S\\M-$", "\u04e4"], ["\\M-S\\M-%", "\u04e5"], ["\\M-S\\M-&", "\u04e6"], ["\\M-S\\M-'", "\u04e7"], ["\\M-S\\M-(", "\u04e8"], ["\\M-S\\M-)", "\u04e9"], ["\\M-S\\M-*", "\u04ea"], ["\\M-S\\M-+", "\u04eb"], ["\\M-S\\M-,", "\u04ec"], ["\\M-S\\M--", "\u04ed"], ["\\M-S\\M-.", "\u04ee"], ["\\M-S\\M-/", "\u04ef"], ["\\M-S\\M-0", "\u04f0"], ["\\M-S\\M-1", "\u04f1"], ["\\M-S\\M-2", "\u04f2"], ["\\M-S\\M-3", "\u04f3"], ["\\M-S\\M-4", "\u04f4"], ["\\M-S\\M-5", "\u04f5"], ["\\M-S\\M-6", "\u04f6"], ["\\M-S\\M-7", "\u04f7"], ["\\M-S\\M-8", "\u04f8"], ["\\M-S\\M-9", "\u04f9"], ["\\M-S\\M-:", "\u04fa"], ["\\M-S\\M-;", "\u04fb"], ["\\M-S\\M-<", "\u04fc"], ["\\M-S\\M-=", "\u04fd"], ["\\M-S\\M->", "\u04fe"], ["\\M-S\\M-?", "\u04ff"], ["\\M-T\\M^@", "\u0500"], ["\\M-T\\M^A", "\u0501"], ["\\M-T\\M^B", "\u0502"], ["\\M-T\\M^C", "\u0503"], ["\\M-T\\M^D", "\u0504"], ["\\M-T\\M^E", "\u0505"], ["\\M-T\\M^F", "\u0506"], ["\\M-T\\M^G", "\u0507"], ["\\M-T\\M^H", "\u0508"], ["\\M-T\\M^I", "\u0509"], ["\\M-T\\M^J", "\u050a"], ["\\M-T\\M^K", "\u050b"], ["\\M-T\\M^L", "\u050c"], ["\\M-T\\M^M", "\u050d"], ["\\M-T\\M^N", "\u050e"], ["\\M-T\\M^O", "\u050f"], ["\\M-T\\M^P", "\u0510"], ["\\M-T\\M^Q", "\u0511"], ["\\M-T\\M^R", "\u0512"], ["\\M-T\\M^S", "\u0513"], ["\\M-T\\M^T", "\u0514"], ["\\M-T\\M^U", "\u0515"], ["\\M-T\\M^V", "\u0516"], ["\\M-T\\M^W", "\u0517"], ["\\M-T\\M^X", "\u0518"], ["\\M-T\\M^Y", "\u0519"], ["\\M-T\\M^Z", "\u051a"], ["\\M-T\\M^[", "\u051b"], ["\\M-T\\M^\\", "\u051c"], ["\\M-T\\M^]", "\u051d"], ["\\M-T\\M^^", "\u051e"], ["\\M-T\\M^_", "\u051f"], ["\\M-T\\240", "\u0520"], ["\\M-T\\M-!", "\u0521"], ["\\M-T\\M-\"", "\u0522"], ["\\M-T\\M-#", "\u0523"], ["\\M-T\\M-$", "\u0524"], ["\\M-T\\M-%", "\u0525"], ["\\M-T\\M-&", "\u0526"], ["\\M-T\\M-'", "\u0527"], ["\\M-T\\M-(", "\u0528"], ["\\M-T\\M-)", "\u0529"], ["\\M-T\\M-*", "\u052a"], ["\\M-T\\M-+", "\u052b"], ["\\M-T\\M-,", "\u052c"], ["\\M-T\\M--", "\u052d"], ["\\M-T\\M-.", "\u052e"], ["\\M-T\\M-/", "\u052f"], ["\\M-V\\M^P", "\u0590"], ["\\M-V\\M^Q", "\u0591"], ["\\M-V\\M^R", "\u0592"], ["\\M-V\\M^S", "\u0593"], ["\\M-V\\M^T", "\u0594"], ["\\M-V\\M^U", "\u0595"], ["\\M-V\\M^V", "\u0596"], ["\\M-V\\M^W", "\u0597"], ["\\M-V\\M^X", "\u0598"], ["\\M-V\\M^Y", "\u0599"], ["\\M-V\\M^Z", "\u059a"], ["\\M-V\\M^[", "\u059b"], ["\\M-V\\M^\\", "\u059c"], ["\\M-V\\M^]", "\u059d"], ["\\M-V\\M^^", "\u059e"], ["\\M-V\\M^_", "\u059f"], ["\\M-V\\240", "\u05a0"], ["\\M-V\\M-!", "\u05a1"], ["\\M-V\\M-\"", "\u05a2"], ["\\M-V\\M-#", "\u05a3"], ["\\M-V\\M-$", "\u05a4"], ["\\M-V\\M-%", "\u05a5"], ["\\M-V\\M-&", "\u05a6"], ["\\M-V\\M-'", "\u05a7"], ["\\M-V\\M-(", "\u05a8"], ["\\M-V\\M-)", "\u05a9"], ["\\M-V\\M-*", "\u05aa"], ["\\M-V\\M-+", "\u05ab"], ["\\M-V\\M-,", "\u05ac"], ["\\M-V\\M--", "\u05ad"], ["\\M-V\\M-.", "\u05ae"], ["\\M-V\\M-/", "\u05af"], ["\\M-V\\M-0", "\u05b0"], ["\\M-V\\M-1", "\u05b1"], ["\\M-V\\M-2", "\u05b2"], ["\\M-V\\M-3", "\u05b3"], ["\\M-V\\M-4", "\u05b4"], ["\\M-V\\M-5", "\u05b5"], ["\\M-V\\M-6", "\u05b6"], ["\\M-V\\M-7", "\u05b7"], ["\\M-V\\M-8", "\u05b8"], ["\\M-V\\M-9", "\u05b9"], ["\\M-V\\M-:", "\u05ba"], ["\\M-V\\M-;", "\u05bb"], ["\\M-V\\M-<", "\u05bc"], ["\\M-V\\M-=", "\u05bd"], ["\\M-V\\M->", "\u05be"], ["\\M-V\\M-?", "\u05bf"], ["\\M-W\\M^@", "\u05c0"], ["\\M-W\\M^A", "\u05c1"], ["\\M-W\\M^B", "\u05c2"], ["\\M-W\\M^C", "\u05c3"], ["\\M-W\\M^D", "\u05c4"], ["\\M-W\\M^E", "\u05c5"], ["\\M-W\\M^F", "\u05c6"], ["\\M-W\\M^G", "\u05c7"], ["\\M-W\\M^H", "\u05c8"], ["\\M-W\\M^I", "\u05c9"], ["\\M-W\\M^J", "\u05ca"], ["\\M-W\\M^K", "\u05cb"], ["\\M-W\\M^L", "\u05cc"], ["\\M-W\\M^M", "\u05cd"], ["\\M-W\\M^N", "\u05ce"], ["\\M-W\\M^O", "\u05cf"], ["\\M-W\\M^P", "\u05d0"], ["\\M-W\\M^Q", "\u05d1"], ["\\M-W\\M^R", "\u05d2"], ["\\M-W\\M^S", "\u05d3"], ["\\M-W\\M^T", "\u05d4"], ["\\M-W\\M^U", "\u05d5"], ["\\M-W\\M^V", "\u05d6"], ["\\M-W\\M^W", "\u05d7"], ["\\M-W\\M^X", "\u05d8"], ["\\M-W\\M^Y", "\u05d9"], ["\\M-W\\M^Z", "\u05da"], ["\\M-W\\M^[", "\u05db"], ["\\M-W\\M^\\", "\u05dc"], ["\\M-W\\M^]", "\u05dd"], ["\\M-W\\M^^", "\u05de"], ["\\M-W\\M^_", "\u05df"], ["\\M-W\\240", "\u05e0"], ["\\M-W\\M-!", "\u05e1"], ["\\M-W\\M-\"", "\u05e2"], ["\\M-W\\M-#", "\u05e3"], ["\\M-W\\M-$", "\u05e4"], ["\\M-W\\M-%", "\u05e5"], ["\\M-W\\M-&", "\u05e6"], ["\\M-W\\M-'", "\u05e7"], ["\\M-W\\M-(", "\u05e8"], ["\\M-W\\M-)", "\u05e9"], ["\\M-W\\M-*", "\u05ea"], ["\\M-W\\M-+", "\u05eb"], ["\\M-W\\M-,", "\u05ec"], ["\\M-W\\M--", "\u05ed"], ["\\M-W\\M-.", "\u05ee"], ["\\M-W\\M-/", "\u05ef"], ["\\M-W\\M-0", "\u05f0"], ["\\M-W\\M-1", "\u05f1"], ["\\M-W\\M-2", "\u05f2"], ["\\M-W\\M-3", "\u05f3"], ["\\M-W\\M-4", "\u05f4"], ["\\M-W\\M-5", "\u05f5"], ["\\M-W\\M-6", "\u05f6"], ["\\M-W\\M-7", "\u05f7"], ["\\M-W\\M-8", "\u05f8"], ["\\M-W\\M-9", "\u05f9"], ["\\M-W\\M-:", "\u05fa"], ["\\M-W\\M-;", "\u05fb"], ["\\M-W\\M-<", "\u05fc"], ["\\M-W\\M-=", "\u05fd"], ["\\M-W\\M->", "\u05fe"], ["\\M-W\\M-?", "\u05ff"], ["\\M-X\\M^@", "\u0600"], ["\\M-X\\M^A", "\u0601"], ["\\M-X\\M^B", "\u0602"], ["\\M-X\\M^C", "\u0603"], ["\\M-X\\M^D", "\u0604"], ["\\M-X\\M^E", "\u0605"], ["\\M-X\\M^F", "\u0606"], ["\\M-X\\M^G", "\u0607"], ["\\M-X\\M^H", "\u0608"], ["\\M-X\\M^I", "\u0609"], ["\\M-X\\M^J", "\u060a"], ["\\M-X\\M^K", "\u060b"], ["\\M-X\\M^L", "\u060c"], ["\\M-X\\M^M", "\u060d"], ["\\M-X\\M^N", "\u060e"], ["\\M-X\\M^O", "\u060f"], ["\\M-X\\M^P", "\u0610"], ["\\M-X\\M^Q", "\u0611"], ["\\M-X\\M^R", "\u0612"], ["\\M-X\\M^S", "\u0613"], ["\\M-X\\M^T", "\u0614"], ["\\M-X\\M^U", "\u0615"], ["\\M-X\\M^V", "\u0616"], ["\\M-X\\M^W", "\u0617"], ["\\M-X\\M^X", "\u0618"], ["\\M-X\\M^Y", "\u0619"], ["\\M-X\\M^Z", "\u061a"], ["\\M-X\\M^[", "\u061b"], ["\\M-X\\M^\\", "\u061c"], ["\\M-X\\M^]", "\u061d"], ["\\M-X\\M^^", "\u061e"], ["\\M-X\\M^_", "\u061f"], ["\\M-X\\240", "\u0620"], ["\\M-X\\M-!", "\u0621"], ["\\M-X\\M-\"", "\u0622"], ["\\M-X\\M-#", "\u0623"], ["\\M-X\\M-$", "\u0624"], ["\\M-X\\M-%", "\u0625"], ["\\M-X\\M-&", "\u0626"], ["\\M-X\\M-'", "\u0627"], ["\\M-X\\M-(", "\u0628"], ["\\M-X\\M-)", "\u0629"], ["\\M-X\\M-*", "\u062a"], ["\\M-X\\M-+", "\u062b"], ["\\M-X\\M-,", "\u062c"], ["\\M-X\\M--", "\u062d"], ["\\M-X\\M-.", "\u062e"], ["\\M-X\\M-/", "\u062f"], ["\\M-X\\M-0", "\u0630"], ["\\M-X\\M-1", "\u0631"], ["\\M-X\\M-2", "\u0632"], ["\\M-X\\M-3", "\u0633"], ["\\M-X\\M-4", "\u0634"], ["\\M-X\\M-5", "\u0635"], ["\\M-X\\M-6", "\u0636"], ["\\M-X\\M-7", "\u0637"], ["\\M-X\\M-8", "\u0638"], ["\\M-X\\M-9", "\u0639"], ["\\M-X\\M-:", "\u063a"], ["\\M-X\\M-;", "\u063b"], ["\\M-X\\M-<", "\u063c"], ["\\M-X\\M-=", "\u063d"], ["\\M-X\\M->", "\u063e"], ["\\M-X\\M-?", "\u063f"], ["\\M-Y\\M^@", "\u0640"], ["\\M-Y\\M^A", "\u0641"], ["\\M-Y\\M^B", "\u0642"], ["\\M-Y\\M^C", "\u0643"], ["\\M-Y\\M^D", "\u0644"], ["\\M-Y\\M^E", "\u0645"], ["\\M-Y\\M^F", "\u0646"], ["\\M-Y\\M^G", "\u0647"], ["\\M-Y\\M^H", "\u0648"], ["\\M-Y\\M^I", "\u0649"], ["\\M-Y\\M^J", "\u064a"], ["\\M-Y\\M^K", "\u064b"], ["\\M-Y\\M^L", "\u064c"], ["\\M-Y\\M^M", "\u064d"], ["\\M-Y\\M^N", "\u064e"], ["\\M-Y\\M^O", "\u064f"], ["\\M-Y\\M^P", "\u0650"], ["\\M-Y\\M^Q", "\u0651"], ["\\M-Y\\M^R", "\u0652"], ["\\M-Y\\M^S", "\u0653"], ["\\M-Y\\M^T", "\u0654"], ["\\M-Y\\M^U", "\u0655"], ["\\M-Y\\M^V", "\u0656"], ["\\M-Y\\M^W", "\u0657"], ["\\M-Y\\M^X", "\u0658"], ["\\M-Y\\M^Y", "\u0659"], ["\\M-Y\\M^Z", "\u065a"], ["\\M-Y\\M^[", "\u065b"], ["\\M-Y\\M^\\", "\u065c"], ["\\M-Y\\M^]", "\u065d"], ["\\M-Y\\M^^", "\u065e"], ["\\M-Y\\M^_", "\u065f"], ["\\M-Y\\240", "\u0660"], ["\\M-Y\\M-!", "\u0661"], ["\\M-Y\\M-\"", "\u0662"], ["\\M-Y\\M-#", "\u0663"], ["\\M-Y\\M-$", "\u0664"], ["\\M-Y\\M-%", "\u0665"], ["\\M-Y\\M-&", "\u0666"], ["\\M-Y\\M-'", "\u0667"], ["\\M-Y\\M-(", "\u0668"], ["\\M-Y\\M-)", "\u0669"], ["\\M-Y\\M-*", "\u066a"], ["\\M-Y\\M-+", "\u066b"], ["\\M-Y\\M-,", "\u066c"], ["\\M-Y\\M--", "\u066d"], ["\\M-Y\\M-.", "\u066e"], ["\\M-Y\\M-/", "\u066f"], ["\\M-Y\\M-0", "\u0670"], ["\\M-Y\\M-1", "\u0671"], ["\\M-Y\\M-2", "\u0672"], ["\\M-Y\\M-3", "\u0673"], ["\\M-Y\\M-4", "\u0674"], ["\\M-Y\\M-5", "\u0675"], ["\\M-Y\\M-6", "\u0676"], ["\\M-Y\\M-7", "\u0677"], ["\\M-Y\\M-8", "\u0678"], ["\\M-Y\\M-9", "\u0679"], ["\\M-Y\\M-:", "\u067a"], ["\\M-Y\\M-;", "\u067b"], ["\\M-Y\\M-<", "\u067c"], ["\\M-Y\\M-=", "\u067d"], ["\\M-Y\\M->", "\u067e"], ["\\M-Y\\M-?", "\u067f"], ["\\M-Z\\M^@", "\u0680"], ["\\M-Z\\M^A", "\u0681"], ["\\M-Z\\M^B", "\u0682"], ["\\M-Z\\M^C", "\u0683"], ["\\M-Z\\M^D", "\u0684"], ["\\M-Z\\M^E", "\u0685"], ["\\M-Z\\M^F", "\u0686"], ["\\M-Z\\M^G", "\u0687"], ["\\M-Z\\M^H", "\u0688"], ["\\M-Z\\M^I", "\u0689"], ["\\M-Z\\M^J", "\u068a"], ["\\M-Z\\M^K", "\u068b"], ["\\M-Z\\M^L", "\u068c"], ["\\M-Z\\M^M", "\u068d"], ["\\M-Z\\M^N", "\u068e"], ["\\M-Z\\M^O", "\u068f"], ["\\M-Z\\M^P", "\u0690"], ["\\M-Z\\M^Q", "\u0691"], ["\\M-Z\\M^R", "\u0692"], ["\\M-Z\\M^S", "\u0693"], ["\\M-Z\\M^T", "\u0694"], ["\\M-Z\\M^U", "\u0695"], ["\\M-Z\\M^V", "\u0696"], ["\\M-Z\\M^W", "\u0697"], ["\\M-Z\\M^X", "\u0698"], ["\\M-Z\\M^Y", "\u0699"], ["\\M-Z\\M^Z", "\u069a"], ["\\M-Z\\M^[", "\u069b"], ["\\M-Z\\M^\\", "\u069c"], ["\\M-Z\\M^]", "\u069d"], ["\\M-Z\\M^^", "\u069e"], ["\\M-Z\\M^_", "\u069f"], ["\\M-Z\\240", "\u06a0"], ["\\M-Z\\M-!", "\u06a1"], ["\\M-Z\\M-\"", "\u06a2"], ["\\M-Z\\M-#", "\u06a3"], ["\\M-Z\\M-$", "\u06a4"], ["\\M-Z\\M-%", "\u06a5"], ["\\M-Z\\M-&", "\u06a6"], ["\\M-Z\\M-'", "\u06a7"], ["\\M-Z\\M-(", "\u06a8"], ["\\M-Z\\M-)", "\u06a9"], ["\\M-Z\\M-*", "\u06aa"], ["\\M-Z\\M-+", "\u06ab"], ["\\M-Z\\M-,", "\u06ac"], ["\\M-Z\\M--", "\u06ad"], ["\\M-Z\\M-.", "\u06ae"], ["\\M-Z\\M-/", "\u06af"], ["\\M-Z\\M-0", "\u06b0"], ["\\M-Z\\M-1", "\u06b1"], ["\\M-Z\\M-2", "\u06b2"], ["\\M-Z\\M-3", "\u06b3"], ["\\M-Z\\M-4", "\u06b4"], ["\\M-Z\\M-5", "\u06b5"], ["\\M-Z\\M-6", "\u06b6"], ["\\M-Z\\M-7", "\u06b7"], ["\\M-Z\\M-8", "\u06b8"], ["\\M-Z\\M-9", "\u06b9"], ["\\M-Z\\M-:", "\u06ba"], ["\\M-Z\\M-;", "\u06bb"], ["\\M-Z\\M-<", "\u06bc"], ["\\M-Z\\M-=", "\u06bd"], ["\\M-Z\\M->", "\u06be"], ["\\M-Z\\M-?", "\u06bf"], ["\\M-[\\M^@", "\u06c0"], ["\\M-[\\M^A", "\u06c1"], ["\\M-[\\M^B", "\u06c2"], ["\\M-[\\M^C", "\u06c3"], ["\\M-[\\M^D", "\u06c4"], ["\\M-[\\M^E", "\u06c5"], ["\\M-[\\M^F", "\u06c6"], ["\\M-[\\M^G", "\u06c7"], ["\\M-[\\M^H", "\u06c8"], ["\\M-[\\M^I", "\u06c9"], ["\\M-[\\M^J", "\u06ca"], ["\\M-[\\M^K", "\u06cb"], ["\\M-[\\M^L", "\u06cc"], ["\\M-[\\M^M", "\u06cd"], ["\\M-[\\M^N", "\u06ce"], ["\\M-[\\M^O", "\u06cf"], ["\\M-[\\M^P", "\u06d0"], ["\\M-[\\M^Q", "\u06d1"], ["\\M-[\\M^R", "\u06d2"], ["\\M-[\\M^S", "\u06d3"], ["\\M-[\\M^T", "\u06d4"], ["\\M-[\\M^U", "\u06d5"], ["\\M-[\\M^V", "\u06d6"], ["\\M-[\\M^W", "\u06d7"], ["\\M-[\\M^X", "\u06d8"], ["\\M-[\\M^Y", "\u06d9"], ["\\M-[\\M^Z", "\u06da"], ["\\M-[\\M^[", "\u06db"], ["\\M-[\\M^\\", "\u06dc"], ["\\M-[\\M^]", "\u06dd"], ["\\M-[\\M^^", "\u06de"], ["\\M-[\\M^_", "\u06df"], ["\\M-[\\240", "\u06e0"], ["\\M-[\\M-!", "\u06e1"], ["\\M-[\\M-\"", "\u06e2"], ["\\M-[\\M-#", "\u06e3"], ["\\M-[\\M-$", "\u06e4"], ["\\M-[\\M-%", "\u06e5"], ["\\M-[\\M-&", "\u06e6"], ["\\M-[\\M-'", "\u06e7"], ["\\M-[\\M-(", "\u06e8"], ["\\M-[\\M-)", "\u06e9"], ["\\M-[\\M-*", "\u06ea"], ["\\M-[\\M-+", "\u06eb"], ["\\M-[\\M-,", "\u06ec"], ["\\M-[\\M--", "\u06ed"], ["\\M-[\\M-.", "\u06ee"], ["\\M-[\\M-/", "\u06ef"], ["\\M-[\\M-0", "\u06f0"], ["\\M-[\\M-1", "\u06f1"], ["\\M-[\\M-2", "\u06f2"], ["\\M-[\\M-3", "\u06f3"], ["\\M-[\\M-4", "\u06f4"], ["\\M-[\\M-5", "\u06f5"], ["\\M-[\\M-6", "\u06f6"], ["\\M-[\\M-7", "\u06f7"], ["\\M-[\\M-8", "\u06f8"], ["\\M-[\\M-9", "\u06f9"], ["\\M-[\\M-:", "\u06fa"], ["\\M-[\\M-;", "\u06fb"], ["\\M-[\\M-<", "\u06fc"], ["\\M-[\\M-=", "\u06fd"], ["\\M-[\\M->", "\u06fe"], ["\\M-[\\M-?", "\u06ff"], ["\\M-b\\M-:\\M^@", "\u2e80"], ["\\M-b\\M-:\\M^A", "\u2e81"], ["\\M-b\\M-:\\M^B", "\u2e82"], ["\\M-b\\M-:\\M^C", "\u2e83"], ["\\M-b\\M-:\\M^D", "\u2e84"], ["\\M-b\\M-:\\M^E", "\u2e85"], ["\\M-b\\M-:\\M^F", "\u2e86"], ["\\M-b\\M-:\\M^G", "\u2e87"], ["\\M-b\\M-:\\M^H", "\u2e88"], ["\\M-b\\M-:\\M^I", "\u2e89"], ["\\M-b\\M-:\\M^J", "\u2e8a"], ["\\M-b\\M-:\\M^K", "\u2e8b"], ["\\M-b\\M-:\\M^L", "\u2e8c"], ["\\M-b\\M-:\\M^M", "\u2e8d"], ["\\M-b\\M-:\\M^N", "\u2e8e"], ["\\M-b\\M-:\\M^O", "\u2e8f"], ["\\M-b\\M-:\\M^P", "\u2e90"], ["\\M-b\\M-:\\M^Q", "\u2e91"], ["\\M-b\\M-:\\M^R", "\u2e92"], ["\\M-b\\M-:\\M^S", "\u2e93"], ["\\M-b\\M-:\\M^T", "\u2e94"], ["\\M-b\\M-:\\M^U", "\u2e95"], ["\\M-b\\M-:\\M^V", "\u2e96"], ["\\M-b\\M-:\\M^W", "\u2e97"], ["\\M-b\\M-:\\M^X", "\u2e98"], ["\\M-b\\M-:\\M^Y", "\u2e99"], ["\\M-b\\M-:\\M^Z", "\u2e9a"], ["\\M-b\\M-:\\M^[", "\u2e9b"], ["\\M-b\\M-:\\M^\\", "\u2e9c"], ["\\M-b\\M-:\\M^]", "\u2e9d"], ["\\M-b\\M-:\\M^^", "\u2e9e"], ["\\M-b\\M-:\\M^_", "\u2e9f"], ["\\M-b\\M-:\\240", "\u2ea0"], ["\\M-b\\M-:\\M-!", "\u2ea1"], ["\\M-b\\M-:\\M-\"", "\u2ea2"], ["\\M-b\\M-:\\M-#", "\u2ea3"], ["\\M-b\\M-:\\M-$", "\u2ea4"], ["\\M-b\\M-:\\M-%", "\u2ea5"], ["\\M-b\\M-:\\M-&", "\u2ea6"], ["\\M-b\\M-:\\M-'", "\u2ea7"], ["\\M-b\\M-:\\M-(", "\u2ea8"], ["\\M-b\\M-:\\M-)", "\u2ea9"], ["\\M-b\\M-:\\M-*", "\u2eaa"], ["\\M-b\\M-:\\M-+", "\u2eab"], ["\\M-b\\M-:\\M-,", "\u2eac"], ["\\M-b\\M-:\\M--", "\u2ead"], ["\\M-b\\M-:\\M-.", "\u2eae"], ["\\M-b\\M-:\\M-/", "\u2eaf"], ["\\M-b\\M-:\\M-0", "\u2eb0"], ["\\M-b\\M-:\\M-1", "\u2eb1"], ["\\M-b\\M-:\\M-2", "\u2eb2"], ["\\M-b\\M-:\\M-3", "\u2eb3"], ["\\M-b\\M-:\\M-4", "\u2eb4"], ["\\M-b\\M-:\\M-5", "\u2eb5"], ["\\M-b\\M-:\\M-6", "\u2eb6"], ["\\M-b\\M-:\\M-7", "\u2eb7"], ["\\M-b\\M-:\\M-8", "\u2eb8"], ["\\M-b\\M-:\\M-9", "\u2eb9"], ["\\M-b\\M-:\\M-:", "\u2eba"], ["\\M-b\\M-:\\M-;", "\u2ebb"], ["\\M-b\\M-:\\M-<", "\u2ebc"], ["\\M-b\\M-:\\M-=", "\u2ebd"], ["\\M-b\\M-:\\M->", "\u2ebe"], ["\\M-b\\M-:\\M-?", "\u2ebf"], ["\\M-b\\M-;\\M^@", "\u2ec0"], ["\\M-b\\M-;\\M^A", "\u2ec1"], ["\\M-b\\M-;\\M^B", "\u2ec2"], ["\\M-b\\M-;\\M^C", "\u2ec3"], ["\\M-b\\M-;\\M^D", "\u2ec4"], ["\\M-b\\M-;\\M^E", "\u2ec5"], ["\\M-b\\M-;\\M^F", "\u2ec6"], ["\\M-b\\M-;\\M^G", "\u2ec7"], ["\\M-b\\M-;\\M^H", "\u2ec8"], ["\\M-b\\M-;\\M^I", "\u2ec9"], ["\\M-b\\M-;\\M^J", "\u2eca"], ["\\M-b\\M-;\\M^K", "\u2ecb"], ["\\M-b\\M-;\\M^L", "\u2ecc"], ["\\M-b\\M-;\\M^M", "\u2ecd"], ["\\M-b\\M-;\\M^N", "\u2ece"], ["\\M-b\\M-;\\M^O", "\u2ecf"], ["\\M-b\\M-;\\M^P", "\u2ed0"], ["\\M-b\\M-;\\M^Q", "\u2ed1"], ["\\M-b\\M-;\\M^R", "\u2ed2"], ["\\M-b\\M-;\\M^S", "\u2ed3"], ["\\M-b\\M-;\\M^T", "\u2ed4"], ["\\M-b\\M-;\\M^U", "\u2ed5"], ["\\M-b\\M-;\\M^V", "\u2ed6"], ["\\M-b\\M-;\\M^W", "\u2ed7"], ["\\M-b\\M-;\\M^X", "\u2ed8"], ["\\M-b\\M-;\\M^Y", "\u2ed9"], ["\\M-b\\M-;\\M^Z", "\u2eda"], ["\\M-b\\M-;\\M^[", "\u2edb"], ["\\M-b\\M-;\\M^\\", "\u2edc"], ["\\M-b\\M-;\\M^]", "\u2edd"], ["\\M-b\\M-;\\M^^", "\u2ede"], ["\\M-b\\M-;\\M^_", "\u2edf"], ["\\M-b\\M-;\\240", "\u2ee0"], ["\\M-b\\M-;\\M-!", "\u2ee1"], ["\\M-b\\M-;\\M-\"", "\u2ee2"], ["\\M-b\\M-;\\M-#", "\u2ee3"], ["\\M-b\\M-;\\M-$", "\u2ee4"], ["\\M-b\\M-;\\M-%", "\u2ee5"], ["\\M-b\\M-;\\M-&", "\u2ee6"], ["\\M-b\\M-;\\M-'", "\u2ee7"], ["\\M-b\\M-;\\M-(", "\u2ee8"], ["\\M-b\\M-;\\M-)", "\u2ee9"], ["\\M-b\\M-;\\M-*", "\u2eea"], ["\\M-b\\M-;\\M-+", "\u2eeb"], ["\\M-b\\M-;\\M-,", "\u2eec"], ["\\M-b\\M-;\\M--", "\u2eed"], ["\\M-b\\M-;\\M-.", "\u2eee"], ["\\M-b\\M-;\\M-/", "\u2eef"], ["\\M-b\\M-;\\M-0", "\u2ef0"], ["\\M-b\\M-;\\M-1", "\u2ef1"], ["\\M-b\\M-;\\M-2", "\u2ef2"], ["\\M-b\\M-;\\M-3", "\u2ef3"], ["\\M-b\\M-;\\M-4", "\u2ef4"], ["\\M-b\\M-;\\M-5", "\u2ef5"], ["\\M-b\\M-;\\M-6", "\u2ef6"], ["\\M-b\\M-;\\M-7", "\u2ef7"], ["\\M-b\\M-;\\M-8", "\u2ef8"], ["\\M-b\\M-;\\M-9", "\u2ef9"], ["\\M-b\\M-;\\M-:", "\u2efa"], ["\\M-b\\M-;\\M-;", "\u2efb"], ["\\M-b\\M-;\\M-<", "\u2efc"], ["\\M-b\\M-;\\M-=", "\u2efd"], ["\\M-b\\M-;\\M->", "\u2efe"], ["\\M-b\\M-;\\M-?", "\u2eff"], ["\\M-c\\M^A\\M^@", "\u3040"], ["\\M-c\\M^A\\M^A", "\u3041"], ["\\M-c\\M^A\\M^B", "\u3042"], ["\\M-c\\M^A\\M^C", "\u3043"], ["\\M-c\\M^A\\M^D", "\u3044"], ["\\M-c\\M^A\\M^E", "\u3045"], ["\\M-c\\M^A\\M^F", "\u3046"], ["\\M-c\\M^A\\M^G", "\u3047"], ["\\M-c\\M^A\\M^H", "\u3048"], ["\\M-c\\M^A\\M^I", "\u3049"], ["\\M-c\\M^A\\M^J", "\u304a"], ["\\M-c\\M^A\\M^K", "\u304b"], ["\\M-c\\M^A\\M^L", "\u304c"], ["\\M-c\\M^A\\M^M", "\u304d"], ["\\M-c\\M^A\\M^N", "\u304e"], ["\\M-c\\M^A\\M^O", "\u304f"], ["\\M-c\\M^A\\M^P", "\u3050"], ["\\M-c\\M^A\\M^Q", "\u3051"], ["\\M-c\\M^A\\M^R", "\u3052"], ["\\M-c\\M^A\\M^S", "\u3053"], ["\\M-c\\M^A\\M^T", "\u3054"], ["\\M-c\\M^A\\M^U", "\u3055"], ["\\M-c\\M^A\\M^V", "\u3056"], ["\\M-c\\M^A\\M^W", "\u3057"], ["\\M-c\\M^A\\M^X", "\u3058"], ["\\M-c\\M^A\\M^Y", "\u3059"], ["\\M-c\\M^A\\M^Z", "\u305a"], ["\\M-c\\M^A\\M^[", "\u305b"], ["\\M-c\\M^A\\M^\\", "\u305c"], ["\\M-c\\M^A\\M^]", "\u305d"], ["\\M-c\\M^A\\M^^", "\u305e"], ["\\M-c\\M^A\\M^_", "\u305f"], ["\\M-c\\M^A\\240", "\u3060"], ["\\M-c\\M^A\\M-!", "\u3061"], ["\\M-c\\M^A\\M-\"", "\u3062"], ["\\M-c\\M^A\\M-#", "\u3063"], ["\\M-c\\M^A\\M-$", "\u3064"], ["\\M-c\\M^A\\M-%", "\u3065"], ["\\M-c\\M^A\\M-&", "\u3066"], ["\\M-c\\M^A\\M-'", "\u3067"], ["\\M-c\\M^A\\M-(", "\u3068"], ["\\M-c\\M^A\\M-)", "\u3069"], ["\\M-c\\M^A\\M-*", "\u306a"], ["\\M-c\\M^A\\M-+", "\u306b"], ["\\M-c\\M^A\\M-,", "\u306c"], ["\\M-c\\M^A\\M--", "\u306d"], ["\\M-c\\M^A\\M-.", "\u306e"], ["\\M-c\\M^A\\M-/", "\u306f"], ["\\M-c\\M^A\\M-0", "\u3070"], ["\\M-c\\M^A\\M-1", "\u3071"], ["\\M-c\\M^A\\M-2", "\u3072"], ["\\M-c\\M^A\\M-3", "\u3073"], ["\\M-c\\M^A\\M-4", "\u3074"], ["\\M-c\\M^A\\M-5", "\u3075"], ["\\M-c\\M^A\\M-6", "\u3076"], ["\\M-c\\M^A\\M-7", "\u3077"], ["\\M-c\\M^A\\M-8", "\u3078"], ["\\M-c\\M^A\\M-9", "\u3079"], ["\\M-c\\M^A\\M-:", "\u307a"], ["\\M-c\\M^A\\M-;", "\u307b"], ["\\M-c\\M^A\\M-<", "\u307c"], ["\\M-c\\M^A\\M-=", "\u307d"], ["\\M-c\\M^A\\M->", "\u307e"], ["\\M-c\\M^A\\M-?", "\u307f"], ["\\M-c\\M^B\\M^@", "\u3080"], ["\\M-c\\M^B\\M^A", "\u3081"], ["\\M-c\\M^B\\M^B", "\u3082"], ["\\M-c\\M^B\\M^C", "\u3083"], ["\\M-c\\M^B\\M^D", "\u3084"], ["\\M-c\\M^B\\M^E", "\u3085"], ["\\M-c\\M^B\\M^F", "\u3086"], ["\\M-c\\M^B\\M^G", "\u3087"], ["\\M-c\\M^B\\M^H", "\u3088"], ["\\M-c\\M^B\\M^I", "\u3089"], ["\\M-c\\M^B\\M^J", "\u308a"], ["\\M-c\\M^B\\M^K", "\u308b"], ["\\M-c\\M^B\\M^L", "\u308c"], ["\\M-c\\M^B\\M^M", "\u308d"], ["\\M-c\\M^B\\M^N", "\u308e"], ["\\M-c\\M^B\\M^O", "\u308f"], ["\\M-c\\M^B\\M^P", "\u3090"], ["\\M-c\\M^B\\M^Q", "\u3091"], ["\\M-c\\M^B\\M^R", "\u3092"], ["\\M-c\\M^B\\M^S", "\u3093"], ["\\M-c\\M^B\\M^T", "\u3094"], ["\\M-c\\M^B\\M^U", "\u3095"], ["\\M-c\\M^B\\M^V", "\u3096"], ["\\M-c\\M^B\\M^W", "\u3097"], ["\\M-c\\M^B\\M^X", "\u3098"], ["\\M-c\\M^B\\M^Y", "\u3099"], ["\\M-c\\M^B\\M^Z", "\u309a"], ["\\M-c\\M^B\\M^[", "\u309b"], ["\\M-c\\M^B\\M^\\", "\u309c"], ["\\M-c\\M^B\\M^]", "\u309d"], ["\\M-c\\M^B\\M^^", "\u309e"], ["\\M-c\\M^B\\M^_", "\u309f"], ["\\M-c\\M^B\\240", "\u30a0"], ["\\M-c\\M^B\\M-!", "\u30a1"], ["\\M-c\\M^B\\M-\"", "\u30a2"], ["\\M-c\\M^B\\M-#", "\u30a3"], ["\\M-c\\M^B\\M-$", "\u30a4"], ["\\M-c\\M^B\\M-%", "\u30a5"], ["\\M-c\\M^B\\M-&", "\u30a6"], ["\\M-c\\M^B\\M-'", "\u30a7"], ["\\M-c\\M^B\\M-(", "\u30a8"], ["\\M-c\\M^B\\M-)", "\u30a9"], ["\\M-c\\M^B\\M-*", "\u30aa"], ["\\M-c\\M^B\\M-+", "\u30ab"], ["\\M-c\\M^B\\M-,", "\u30ac"], ["\\M-c\\M^B\\M--", "\u30ad"], ["\\M-c\\M^B\\M-.", "\u30ae"], ["\\M-c\\M^B\\M-/", "\u30af"], ["\\M-c\\M^B\\M-0", "\u30b0"], ["\\M-c\\M^B\\M-1", "\u30b1"], ["\\M-c\\M^B\\M-2", "\u30b2"], ["\\M-c\\M^B\\M-3", "\u30b3"], ["\\M-c\\M^B\\M-4", "\u30b4"], ["\\M-c\\M^B\\M-5", "\u30b5"], ["\\M-c\\M^B\\M-6", "\u30b6"], ["\\M-c\\M^B\\M-7", "\u30b7"], ["\\M-c\\M^B\\M-8", "\u30b8"], ["\\M-c\\M^B\\M-9", "\u30b9"], ["\\M-c\\M^B\\M-:", "\u30ba"], ["\\M-c\\M^B\\M-;", "\u30bb"], ["\\M-c\\M^B\\M-<", "\u30bc"], ["\\M-c\\M^B\\M-=", "\u30bd"], ["\\M-c\\M^B\\M->", "\u30be"], ["\\M-c\\M^B\\M-?", "\u30bf"], ["\\M-c\\M^C\\M^@", "\u30c0"], ["\\M-c\\M^C\\M^A", "\u30c1"], ["\\M-c\\M^C\\M^B", "\u30c2"], ["\\M-c\\M^C\\M^C", "\u30c3"], ["\\M-c\\M^C\\M^D", "\u30c4"], ["\\M-c\\M^C\\M^E", "\u30c5"], ["\\M-c\\M^C\\M^F", "\u30c6"], ["\\M-c\\M^C\\M^G", "\u30c7"], ["\\M-c\\M^C\\M^H", "\u30c8"], ["\\M-c\\M^C\\M^I", "\u30c9"], ["\\M-c\\M^C\\M^J", "\u30ca"], ["\\M-c\\M^C\\M^K", "\u30cb"], ["\\M-c\\M^C\\M^L", "\u30cc"], ["\\M-c\\M^C\\M^M", "\u30cd"], ["\\M-c\\M^C\\M^N", "\u30ce"], ["\\M-c\\M^C\\M^O", "\u30cf"], ["\\M-c\\M^C\\M^P", "\u30d0"], ["\\M-c\\M^C\\M^Q", "\u30d1"], ["\\M-c\\M^C\\M^R", "\u30d2"], ["\\M-c\\M^C\\M^S", "\u30d3"], ["\\M-c\\M^C\\M^T", "\u30d4"], ["\\M-c\\M^C\\M^U", "\u30d5"], ["\\M-c\\M^C\\M^V", "\u30d6"], ["\\M-c\\M^C\\M^W", "\u30d7"], ["\\M-c\\M^C\\M^X", "\u30d8"], ["\\M-c\\M^C\\M^Y", "\u30d9"], ["\\M-c\\M^C\\M^Z", "\u30da"], ["\\M-c\\M^C\\M^[", "\u30db"], ["\\M-c\\M^C\\M^\\", "\u30dc"], ["\\M-c\\M^C\\M^]", "\u30dd"], ["\\M-c\\M^C\\M^^", "\u30de"], ["\\M-c\\M^C\\M^_", "\u30df"], ["\\M-c\\M^C\\240", "\u30e0"], ["\\M-c\\M^C\\M-!", "\u30e1"], ["\\M-c\\M^C\\M-\"", "\u30e2"], ["\\M-c\\M^C\\M-#", "\u30e3"], ["\\M-c\\M^C\\M-$", "\u30e4"], ["\\M-c\\M^C\\M-%", "\u30e5"], ["\\M-c\\M^C\\M-&", "\u30e6"], ["\\M-c\\M^C\\M-'", "\u30e7"], ["\\M-c\\M^C\\M-(", "\u30e8"], ["\\M-c\\M^C\\M-)", "\u30e9"], ["\\M-c\\M^C\\M-*", "\u30ea"], ["\\M-c\\M^C\\M-+", "\u30eb"], ["\\M-c\\M^C\\M-,", "\u30ec"], ["\\M-c\\M^C\\M--", "\u30ed"], ["\\M-c\\M^C\\M-.", "\u30ee"], ["\\M-c\\M^C\\M-/", "\u30ef"], ["\\M-c\\M^C\\M-0", "\u30f0"], ["\\M-c\\M^C\\M-1", "\u30f1"], ["\\M-c\\M^C\\M-2", "\u30f2"], ["\\M-c\\M^C\\M-3", "\u30f3"], ["\\M-c\\M^C\\M-4", "\u30f4"], ["\\M-c\\M^C\\M-5", "\u30f5"], ["\\M-c\\M^C\\M-6", "\u30f6"], ["\\M-c\\M^C\\M-7", "\u30f7"], ["\\M-c\\M^C\\M-8", "\u30f8"], ["\\M-c\\M^C\\M-9", "\u30f9"], ["\\M-c\\M^C\\M-:", "\u30fa"], ["\\M-c\\M^C\\M-;", "\u30fb"], ["\\M-c\\M^C\\M-<", "\u30fc"], ["\\M-c\\M^C\\M-=", "\u30fd"], ["\\M-c\\M^C\\M->", "\u30fe"], ["\\M-c\\M^C\\M-?", "\u30ff"], ["foo\\040bar", "foo bar"], ["foo\\^Jbar", "foo\nbar"], ["$bar\\040=\\040'baz';", "$bar = 'baz';"], ["$foo\\040=\\040\"\\\\x20\\\\\\\\x20\\\\\\\\\\\\x20\\\\\\\\\\\\\\\\x20\"", "$foo = \"\\x20\\\\x20\\\\\\x20\\\\\\\\x20\""], ["$foo\\040=\\040function($bar)\\040use($baz)\\040{\\^J\\^Ireturn\\040$baz->getFoo()\\^J};", "$foo = function($bar) use($baz) {\n\treturn $baz->getFoo()\n};"], ["", ""]]
    \ No newline at end of file
    diff --git a/core/vendor/psy/psysh/test/tools/gen_unvis_fixtures.py b/core/vendor/psy/psysh/test/tools/gen_unvis_fixtures.py
    new file mode 100644
    index 0000000..e02a741
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/tools/gen_unvis_fixtures.py
    @@ -0,0 +1,94 @@
    +#! /usr/bin/env python3
    +import sys
    +from os.path import abspath, expanduser, dirname, join
    +from itertools import chain
    +import json
    +import argparse
    +
    +from vis import vis, unvis, VIS_WHITE
    +
    +
    +__dir__ = dirname(abspath(__file__))
    +
    +OUTPUT_FILE = join(__dir__, '..', 'fixtures', 'unvis_fixtures.json')
    +
    +# Add custom fixtures here
    +CUSTOM_FIXTURES = [
    +    # test long multibyte string
    +    ''.join(chr(cp) for cp in range(1024)),
    +    'foo bar',
    +    'foo\nbar',
    +    "$bar = 'baz';",
    +    r'$foo = "\x20\\x20\\\x20\\\\x20"',
    +    '$foo = function($bar) use($baz) {\n\treturn $baz->getFoo()\n};'
    +]
    +
    +RANGES = {
    +    # All valid codepoints in the BMP
    +    'bmp': chain(range(0x0000, 0xD800), range(0xE000, 0xFFFF)),
    +    # Smaller set of pertinent? codepoints inside BMP
    +    # see: http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
    +    'small': chain(
    +        # latin blocks
    +        range(0x0000, 0x0250),
    +        # Greek, Cyrillic
    +        range(0x0370, 0x0530),
    +        # Hebrew, Arabic
    +        range(0x590, 0x0700),
    +        # CJK radicals
    +        range(0x2E80, 0x2F00),
    +        # Hiragana, Katakana
    +        range(0x3040, 0x3100)
    +    )
    +}
    +
    +
    +if __name__ == '__main__':
    +
    +    argp = argparse.ArgumentParser(
    +        description='Generates test data for Psy\\Test\\Util\\StrTest')
    +    argp.add_argument('-f', '--format-output', action='store_true',
    +                      help='Indent JSON output to ease debugging')
    +    argp.add_argument('-a', '--all', action='store_true',
    +                      help="""Generates test data for all codepoints of the BMP.
    +                      (same as --range=bmp). WARNING: You will need quite
    +                      a lot of RAM to run the testsuite !
    +                      """)
    +    argp.add_argument('-r', '--range',
    +                      help="""Choose the range of codepoints used to generate
    +                      test data.""",
    +                      choices=list(RANGES.keys()),
    +                      default='small')
    +    argp.add_argument('-o', '--output-file',
    +                      help="""Write test data to OUTPUT_FILE
    +                      (defaults to PSYSH_DIR/test/fixtures)""")
    +    args = argp.parse_args()
    +
    +    cp_range = RANGES['bmp'] if args.all else RANGES[args.range]
    +    indent = 2 if args.format_output else None
    +    if args.output_file:
    +        OUTPUT_FILE = abspath(expanduser(args.output_file))
    +
    +    fixtures = []
    +
    +    # use SMALL_RANGE by default, it should be enough.
    +    # use BMP_RANGE for a more complete smoke test
    +    for codepoint in cp_range:
    +        char = chr(codepoint)
    +        encoded = vis(char, VIS_WHITE)
    +        decoded = unvis(encoded)
    +        fixtures.append((encoded, decoded))
    +
    +    # Add our own custom fixtures at the end,
    +    # since they would fail anyway if one of the previous did.
    +    for fixture in CUSTOM_FIXTURES:
    +        encoded = vis(fixture, VIS_WHITE)
    +        decoded = unvis(encoded)
    +        fixtures.append((encoded, decoded))
    +
    +    with open(OUTPUT_FILE, 'w') as fp:
    +        # dump as json to avoid backslashin and quotin nightmare
    +        # between php and python
    +        json.dump(fixtures, fp, indent=indent)
    +
    +    sys.exit(0)
    diff --git a/core/vendor/psy/psysh/test/tools/vis.py b/core/vendor/psy/psysh/test/tools/vis.py
    new file mode 100644
    index 0000000..4e45c4c
    --- /dev/null
    +++ b/core/vendor/psy/psysh/test/tools/vis.py
    @@ -0,0 +1,126 @@
    +"""
    +vis.py
    +======
    +
    +Ctypes based module to access libbsd's strvis & strunvis functions.
    +
    +The `vis` function is the equivalent of strvis.
    +The `unvis` function is the equivalent of strunvis.
    +All functions accept unicode string as input and return a unicode string.
    +
    +Constants:
    +----------
    +
    +* to select alternate encoding format
    +  `VIS_OCTAL`:      use octal \ddd format
    +  `VIS_CSTYLE`:     use \[nrft0..] where appropiate
    +
    +* to alter set of characters encoded
    +  (default is to encode all non-graphic except space, tab, and newline).
    +  `VIS_SP`:         also encode space
    +  `VIS_TAB`:        also encode tab
    +  `VIS_NL`:         also encode newline
    +  `VIS_WHITE`:      same as (VIS_SP | VIS_TAB | VIS_NL)
    +  `VIS_SAFE`:       only encode "unsafe" characters
    +
    +* other
    +  `VIS_NOSLASH`:    inhibit printing '\'
    +  `VIS_HTTP1808`:   http-style escape % hex hex
    +  `VIS_HTTPSTYLE`:  http-style escape % hex hex
    +  `VIS_MIMESTYLE`:  mime-style escape = HEX HEX
    +  `VIS_HTTP1866`:   http-style &#num; or &string;
    +  `VIS_NOESCAPE`:   don't decode `\'
    +  `VIS_GLOB`:       encode glob(3) magic characters
    +
    +:Authors:
    +    - ju1ius (http://github.com/ju1ius)
    +:Version: 1
    +:Date: 2014-01-05
    +"""
    +from ctypes import CDLL, c_char_p, c_int
    +from ctypes.util import find_library
    +
    +
    +__all__ = [
    +    'vis', 'unvis',
    +    'VIS_OCTAL', 'VIS_CSTYLE',
    +    'VIS_SP', 'VIS_TAB', 'VIS_NL', 'VIS_WHITE', 'VIS_SAFE',
    +    'VIS_NOSLASH', 'VIS_HTTP1808', 'VIS_HTTPSTYLE', 'VIS_MIMESTYLE',
    +    'VIS_HTTP1866', 'VIS_NOESCAPE', 'VIS_GLOB'
    +]
    +
    +
    +#############################################################
    +# Constants from bsd/vis.h
    +#############################################################
    +
    +#to select alternate encoding format
    +VIS_OCTAL = 0x0001
    +VIS_CSTYLE = 0x0002
    +# to alter set of characters encoded
    +# (default is to encode all non-graphic except space, tab, and newline).
    +VIS_SP = 0x0004
    +VIS_TAB = 0x0008
    +VIS_NL = 0x0010
    +VIS_WHITE = VIS_SP | VIS_TAB | VIS_NL
    +VIS_SAFE = 0x0020
    +# other
    +VIS_NOSLASH = 0x0040
    +VIS_HTTP1808 = 0x0080
    +VIS_HTTPSTYLE = 0x0080
    +VIS_MIMESTYLE = 0x0100
    +VIS_HTTP1866 = 0x0200
    +VIS_NOESCAPE = 0x0400
    +VIS_GLOB = 0x1000
    +
    +#############################################################
    +# Import libbsd/vis functions
    +#############################################################
    +
    +_libbsd = CDLL(find_library('bsd'))
    +
    +_strvis = _libbsd.strvis
    +_strvis.argtypes = [c_char_p, c_char_p, c_int]
    +_strvis.restype = c_int
    +
    +_strunvis = _libbsd.strunvis
    +_strvis.argtypes = [c_char_p, c_char_p]
    +_strvis.restype = c_int
    +
    +
    +def vis(src, flags=VIS_WHITE):
    +    """
    +    Encodes the string `src` into libbsd's vis encoding.
    +    `flags` must be one of the VIS_* constants
    +
    +    C definition:
    +    int strvis(char *dst, char *src, int flags);
    +    """
    +    src = bytes(src, 'utf-8')
    +    dst_p = c_char_p(bytes(len(src) * 4))
    +    src_p = c_char_p(src)
    +    flags = c_int(flags)
    +
    +    bytes_written = _strvis(dst_p, src_p, flags)
    +    if -1 == bytes_written:
    +        raise RuntimeError('vis failed to encode string "{}"'.format(src))
    +
    +    return dst_p.value.decode('utf-8')
    +
    +
    +def unvis(src):
    +    """
    +    Decodes a string encoded by vis.
    +
    +    C definition:
    +    int strunvis(char *dst, char *src);
    +    """
    +    src = bytes(src, 'utf-8')
    +    dst_p = c_char_p(bytes(len(src)))
    +    src_p = c_char_p(src)
    +
    +    bytes_written = _strunvis(dst_p, src_p)
    +    if -1 == bytes_written:
    +        raise RuntimeError('unvis failed to decode string "{}"'.format(src))
    +
    +    return dst_p.value.decode('utf-8')
    diff --git a/core/vendor/swiftmailer/swiftmailer/.gitattributes b/core/vendor/swiftmailer/swiftmailer/.gitattributes
    new file mode 100644
    index 0000000..33b8efd
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/.gitattributes
    @@ -0,0 +1,9 @@
    +*.crt -crlf
    +*.key -crlf
    +*.srl -crlf
    +*.pub -crlf
    +*.priv -crlf
    +*.txt -crlf
    +
    +# ignore /notes in the git-generated distributed .zip archive
    +/notes export-ignore
    diff --git a/core/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md b/core/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md
    new file mode 100644
    index 0000000..5db6524
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md
    @@ -0,0 +1,19 @@
    +
    +
    +| Q                   | A
    +| ------------------- | -----
    +| Bug report?         | yes/no
    +| Feature request?    | yes/no
    +| RFC?                | yes/no
    +| How used?           | Standalone/Symfony/3party
    +| Swiftmailer version | x.y.z
    +| PHP version         | x.y.z
    +
    +### Observed behaviour
    +
    +
    +### Expected behaviour
    +
    +
    +### Example
    +
    diff --git a/core/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md b/core/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md
    new file mode 100644
    index 0000000..4b39510
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md
    @@ -0,0 +1,14 @@
    +
    +
    +| Q             | A
    +| ------------- | ---
    +| Bug fix?      | yes/no
    +| New feature?  | yes/no
    +| Doc update?   | yes/no
    +| BC breaks?    | yes/no
    +| Deprecations? | yes/no
    +| Fixed tickets | #... 
    +| License       | MIT
    +
    +
    +
    diff --git a/core/vendor/swiftmailer/swiftmailer/.gitignore b/core/vendor/swiftmailer/swiftmailer/.gitignore
    new file mode 100644
    index 0000000..20d389a
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/.gitignore
    @@ -0,0 +1,8 @@
    +/.php_cs.cache
    +/.phpunit
    +/build/*
    +/composer.lock
    +/phpunit.xml
    +/tests/acceptance.conf.php
    +/tests/smoke.conf.php
    +/vendor/
    diff --git a/core/vendor/swiftmailer/swiftmailer/.php_cs.dist b/core/vendor/swiftmailer/swiftmailer/.php_cs.dist
    new file mode 100644
    index 0000000..f18d65d
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/.php_cs.dist
    @@ -0,0 +1,15 @@
    +setRules(array(
    +        '@Symfony' => true,
    +        '@Symfony:risky' => true,
    +        'array_syntax' => array('syntax' => 'long'),
    +        'no_unreachable_default_argument_value' => false,
    +        'braces' => array('allow_single_line_closure' => true),
    +        'heredoc_to_nowdoc' => false,
    +        'phpdoc_annotation_without_dot' => false,
    +    ))
    +    ->setRiskyAllowed(true)
    +    ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__))
    +;
    diff --git a/core/vendor/swiftmailer/swiftmailer/.travis.yml b/core/vendor/swiftmailer/swiftmailer/.travis.yml
    new file mode 100644
    index 0000000..fc24d05
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/.travis.yml
    @@ -0,0 +1,31 @@
    +language: php
    +
    +sudo: false
    +
    +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 mime-types -v 2.99.1
    +    - gem install mailcatcher
    +    - mailcatcher --smtp-port 4456
    +
    +script: ./vendor/bin/simple-phpunit
    +
    +matrix:
    +    include:
    +        - php: 5.3
    +        - php: 5.4
    +        - php: 5.5
    +        - php: 5.6
    +        - php: 7.0
    +        - php: 7.1
    +        - php: hhvm
    +    allow_failures:
    +        - php: hhvm
    +    fast_finish: true
    +
    +cache:
    +    directories:
    +        - .phpunit
    diff --git a/core/vendor/swiftmailer/swiftmailer/CHANGES b/core/vendor/swiftmailer/swiftmailer/CHANGES
    new file mode 100644
    index 0000000..3532ec2
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/CHANGES
    @@ -0,0 +1,287 @@
    +Changelog
    +=========
    +
    +5.4.12 (2018-07-31)
    +-------------------
    +
    + * fixed typo
    +
    +5.4.11 (2018-07-31)
    +-------------------
    +
    + * fixed startTLS support for PHP 5.6-
    +
    +5.4.10 (2018-07-27)
    +-------------------
    +
    + * fixed startTLS only allowed tls1.0, now allowed: tls1.0, tls1.1, tls1.2
    +
    +5.4.9 (2018-01-23)
    +------------------
    +
    + * no changes, last version of the 5.x series
    +
    +5.4.8 (2017-05-01)
    +------------------
    +
    + * fixed encoding inheritance in addPart()
    + * fixed sorting MIME children when their types are equal
    +
    +5.4.7 (2017-04-20)
    +------------------
    +
    + * fixed NTLMAuthenticator clobbering bcmath scale
    +
    +5.4.6 (2017-02-13)
    +------------------
    +
    + * removed exceptions thrown in destructors as they lead to fatal errors
    + * switched to use sha256 by default in DKIM as per the RFC
    + * fixed an 'Undefined variable: pipes' PHP notice
    + * fixed long To headers when using the mail transport
    + * fixed NTLMAuthenticator when no domain is passed with the username
    + * prevented fatal error during unserialization of a message
    + * fixed a PHP warning when sending a message that has a length of a multiple of 8192
    +
    +5.4.5 (2016-12-29)
    +------------------
    +
    + * SECURITY FIX:  fixed CVE-2016-10074 by disallowing potentially unsafe shell characters
    +
    +   Prior to 5.4.5, the mail transport (Swift_Transport_MailTransport) was vulnerable to passing
    +   arbitrary shell arguments if the "From", "ReturnPath" or "Sender" header came
    +   from a non-trusted source, potentially allowing Remote Code Execution
    + * deprecated the mail transport
    +
    +5.4.4 (2016-11-23)
    +------------------
    +
    + * reverted escaping command-line args to mail (PHP mail() function already does it)
    +
    +5.4.3 (2016-07-08)
    +------------------
    +
    + * fixed SimpleHeaderSet::has()/get() when the 0 index is removed
    + * removed the need to have mcrypt installed
    + * fixed broken MIME header encoding with quotes/colons and non-ascii chars
    + * allowed mail transport send for messages without To header
    + * fixed PHP 7 support
    +
    +5.4.2 (2016-05-01)
    +------------------
    +
    + * fixed support for IPv6 sockets
    + * added auto-retry when sending messages from the memory spool
    + * fixed consecutive read calls in Swift_ByteStream_FileByteStream
    + * added support for iso-8859-15 encoding
    + * fixed PHP mail extra params on missing reversePath
    + * added methods to set custom stream context options
    + * fixed charset changes in QpContentEncoderProxy
    + * added return-path header to the ignoredHeaders list of DKIMSigner
    + * fixed crlf for subject using mail
    + * fixed add soft line break only when necessary
    + * fixed escaping command-line args to mail
    +
    +5.4.1 (2015-06-06)
    +------------------
    +
    + * made Swiftmailer exceptions confirm to PHP base exception constructor signature
    + * fixed MAIL FROM & RCPT TO headers to be RFC compliant
    +
    +5.4.0 (2015-03-14)
    +------------------
    +
    + * added the possibility to add extra certs to PKCS#7 signature
    + * fix base64 encoding with streams
    + * added a new RESULT_SPOOLED status for SpoolTransport
    + * fixed getBody() on attachments when called more than once
    + * removed dots from generated filenames in filespool
    +
    +5.3.1 (2014-12-05)
    +------------------
    +
    + * fixed cloning of messages with attachments
    +
    +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/core/vendor/swiftmailer/swiftmailer/LICENSE b/core/vendor/swiftmailer/swiftmailer/LICENSE
    new file mode 100644
    index 0000000..485f1d6
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/LICENSE
    @@ -0,0 +1,19 @@
    +Copyright (c) 2013-2016 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/core/vendor/swiftmailer/swiftmailer/README b/core/vendor/swiftmailer/swiftmailer/README
    new file mode 100644
    index 0000000..52c0757
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/README
    @@ -0,0 +1,15 @@
    +Swift Mailer
    +------------
    +
    +Swift Mailer is a component based mailing solution for PHP 5.
    +It is released under the MIT license.
    +
    +Homepage:      https://swiftmailer.symfony.com/
    +Documentation: https://swiftmailer.symfony.com/docs/introduction.html
    +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/core/vendor/swiftmailer/swiftmailer/VERSION b/core/vendor/swiftmailer/swiftmailer/VERSION
    new file mode 100644
    index 0000000..82a2d1d
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/VERSION
    @@ -0,0 +1 @@
    +Swift-5.4.12
    diff --git a/core/vendor/swiftmailer/swiftmailer/composer.json b/core/vendor/swiftmailer/swiftmailer/composer.json
    new file mode 100644
    index 0000000..44a1050
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/composer.json
    @@ -0,0 +1,37 @@
    +{
    +    "name": "swiftmailer/swiftmailer",
    +    "type": "library",
    +    "description": "Swiftmailer, free feature-rich PHP mailer",
    +    "keywords": ["mail","mailer","email"],
    +    "homepage": "https://swiftmailer.symfony.com",
    +    "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",
    +        "symfony/phpunit-bridge": "~3.2"
    +    },
    +    "autoload": {
    +        "files": ["lib/swift_required.php"]
    +    },
    +    "autoload-dev": {
    +        "psr-0": {
    +            "Swift_": "tests/unit"
    +        }
    +    },
    +    "extra": {
    +        "branch-alias": {
    +            "dev-master": "5.4-dev"
    +        }
    +    }
    +}
    diff --git a/core/vendor/swiftmailer/swiftmailer/doc/headers.rst b/core/vendor/swiftmailer/swiftmailer/doc/headers.rst
    new file mode 100644
    index 0000000..2c11c18
    --- /dev/null
    +++ b/core/vendor/swiftmailer/swiftmailer/doc/headers.rst
    @@ -0,0 +1,739 @@
    +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 ID header is done by calling its +``setId()`` method:: + + $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/core/vendor/swiftmailer/swiftmailer/doc/help-resources.rst b/core/vendor/swiftmailer/swiftmailer/doc/help-resources.rst new file mode 100644 index 0000000..4208935 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst b/core/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst new file mode 100644 index 0000000..978dca2 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/doc/index.rst b/core/vendor/swiftmailer/swiftmailer/doc/index.rst new file mode 100644 index 0000000..a1a0a92 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/doc/installing.rst b/core/vendor/swiftmailer/swiftmailer/doc/installing.rst new file mode 100644 index 0000000..557211d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/doc/introduction.rst b/core/vendor/swiftmailer/swiftmailer/doc/introduction.rst new file mode 100644 index 0000000..a85336b --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/doc/japanese.rst b/core/vendor/swiftmailer/swiftmailer/doc/japanese.rst new file mode 100644 index 0000000..34afa7b --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/doc/messages.rst b/core/vendor/swiftmailer/swiftmailer/doc/messages.rst new file mode 100644 index 0000000..b058b77 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/doc/messages.rst @@ -0,0 +1,1058 @@ +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: + +* `Swift_Mime_SimpleMessage::PRIORITY_HIGHEST`: 1 +* `Swift_Mime_SimpleMessage::PRIORITY_HIGH`: 2 +* `Swift_Mime_SimpleMessage::PRIORITY_NORMAL`: 3 +* `Swift_Mime_SimpleMessage::PRIORITY_LOW`: 4 +* `Swift_Mime_SimpleMessage::PRIORITY_LOWEST`: 5 + +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); + + // Or use the constant to be more explicit + $message->setPriority(Swift_Mime_SimpleMessage::PRIORITY_HIGH); diff --git a/core/vendor/swiftmailer/swiftmailer/doc/overview.rst b/core/vendor/swiftmailer/swiftmailer/doc/overview.rst new file mode 100644 index 0000000..ebfe008 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/doc/overview.rst @@ -0,0 +1,159 @@ +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.3.3 or higher. + +The library aims to work with as many PHP 5 projects as possible: + +* PHP 5.3.3 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_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/core/vendor/swiftmailer/swiftmailer/doc/plugins.rst b/core/vendor/swiftmailer/swiftmailer/doc/plugins.rst new file mode 100644 index 0000000..6cec6be --- /dev/null +++ b/core/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) { + global $db; // Your PDO instance with a connection to your database + $query = $db->prepare( + "SELECT * FROM `users` WHERE `email` = ?" + ); + + $query->execute([$address]); + + if ($row = $query->fetch(PDO::FETCH_ASSOC)) { + 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/core/vendor/swiftmailer/swiftmailer/doc/sending.rst b/core/vendor/swiftmailer/swiftmailer/doc/sending.rst new file mode 100644 index 0000000..f340404 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/doc/sending.rst @@ -0,0 +1,571 @@ +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`` + 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: + + // Sendmail + $transport = Swift_SendmailTransport::newInstance('/usr/sbin/sendmail -bs'); + */ + + // 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! + +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``, + 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``, + 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/core/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle b/core/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle new file mode 100644 index 0000000..f895752 Binary files /dev/null and b/core/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle differ diff --git a/core/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle b/core/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle new file mode 100644 index 0000000..e1e33cb Binary files /dev/null and b/core/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle differ diff --git a/core/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle b/core/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle new file mode 100644 index 0000000..5670e2b Binary files /dev/null and b/core/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle differ diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php new file mode 100644 index 0000000..82c381b --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php new file mode 100644 index 0000000..a7b0e3a --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php @@ -0,0 +1,181 @@ +_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 + * + * @throws Swift_IoException + * + * @return int + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php new file mode 100644 index 0000000..ef05a6d --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php @@ -0,0 +1,182 @@ +_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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php new file mode 100644 index 0000000..9ed8523 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php @@ -0,0 +1,231 @@ +_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 + * + * @throws Swift_IoException + * + * @return string|bool + */ + 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)) { + $pointer = @fopen($this->_path, 'rb'); + if (!$pointer) { + throw new Swift_IoException( + 'Unable to open file for reading ['.$this->_path.']' + ); + } + $this->_reader = $pointer; + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php new file mode 100644 index 0000000..1c9a80c --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php new file mode 100644 index 0000000..4267adb --- /dev/null +++ b/core/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 int[] $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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php new file mode 100644 index 0000000..6a18e1d --- /dev/null +++ b/core/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 = $ignored ? 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php new file mode 100644 index 0000000..67da48f --- /dev/null +++ b/core/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; + } + + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php new file mode 100644 index 0000000..22746bd --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php @@ -0,0 +1,176 @@ + + */ +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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php new file mode 100644 index 0000000..15b6c69 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php new file mode 100644 index 0000000..717924f --- /dev/null +++ b/core/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 int[] + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php new file mode 100644 index 0000000..58bd140 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php @@ -0,0 +1,267 @@ + + */ +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: + $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 int[] + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php new file mode 100644 index 0000000..4ae5bac --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php new file mode 100644 index 0000000..befec9a --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php @@ -0,0 +1,373 @@ +_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 + * + * @throws Swift_DependencyException If the dependency is not found + * + * @return mixed + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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) + ); + } + + 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; + } + + return $this->lookup($item); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php new file mode 100644 index 0000000..799d38d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php new file mode 100644 index 0000000..2073abc --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php @@ -0,0 +1,28 @@ += $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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php new file mode 100644 index 0000000..edec10c --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php @@ -0,0 +1,300 @@ + '=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); + + $i = strpos($enc, '=0D=0A'); + $newLineLength = $lineLen + ($i === false ? $size : $i); + + if ($currentLine && $newLineLength >= $thisLineLength) { + $lines[$lNo] = ''; + $currentLine = &$lines[$lNo++]; + $thisLineLength = $maxLineLength; + $lineLen = 0; + } + + $currentLine .= $enc; + + if ($i === false) { + $lineLen += $size; + } else { + // 6 is the length of '=0D=0A'. + $lineLen = $size - strrpos($enc, '=0D=0A') - 6; + } + } + + 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 int[] $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 int[] + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php new file mode 100644 index 0000000..b0215e8 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php new file mode 100644 index 0000000..2458787 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php @@ -0,0 +1,62 @@ +lookup($key); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php new file mode 100644 index 0000000..674e6b5 --- /dev/null +++ b/core/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 int[] + */ + public function getSuccessCodes() + { + return $this->_successCodes; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php new file mode 100644 index 0000000..7545404 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php new file mode 100644 index 0000000..2e92ba9 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php new file mode 100644 index 0000000..c40919d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php new file mode 100644 index 0000000..d922e1b --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php new file mode 100644 index 0000000..a8972fd --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php @@ -0,0 +1,27 @@ +getSource(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php new file mode 100644 index 0000000..253165d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php new file mode 100644 index 0000000..cc3c099 --- /dev/null +++ b/core/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 self + */ + public static function newInstance($transports = array()) + { + return new self($transports); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php new file mode 100644 index 0000000..c82c5db --- /dev/null +++ b/core/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(sprintf('Unable to create path "%s".', $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 + * + * @throws Swift_IoException + * + * @return bool + */ + 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(sprintf('Unable to create a file for enqueuing Message in "%s".', $this->_path)); + } + + /** + * 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php new file mode 100644 index 0000000..0b24db1 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php @@ -0,0 +1,24 @@ +setFile(new Swift_ByteStream_FileByteStream($path)); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php new file mode 100644 index 0000000..56efc75 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php new file mode 100644 index 0000000..453f50a --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php @@ -0,0 +1,321 @@ +_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 + * + * @throws Swift_IoException + * + * @return string + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php new file mode 100644 index 0000000..af80bdc --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php new file mode 100644 index 0000000..e151b8a --- /dev/null +++ b/core/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 self + */ + public static function newInstance($transports = array()) + { + return new self($transports); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php new file mode 100644 index 0000000..1855698 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php @@ -0,0 +1,47 @@ +createDependenciesFor('transport.mail') + ); + + $this->setExtraParams($extraParams); + } + + /** + * Create a new MailTransport instance. + * + * @param string $extraParams To be passed to mail() + * + * @return self + */ + public static function newInstance($extraParams = '-f%s') + { + return new self($extraParams); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php new file mode 100644 index 0000000..8314fe8 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php @@ -0,0 +1,114 @@ +_transport = $transport; + } + + /** + * Create a new Mailer instance. + * + * @param Swift_Transport $transport + * + * @return self + */ + 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 The number of successful recipients. Can be 0 which indicates failure + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php new file mode 100644 index 0000000..e3e6cad --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php new file mode 100644 index 0000000..650f3ec --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php new file mode 100644 index 0000000..2cafb67 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php @@ -0,0 +1,110 @@ + + * + * 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(); + private $flushRetries = 3; + + /** + * 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() + { + } + + /** + * @param int $retries + */ + public function setFlushRetries($retries) + { + $this->flushRetries = $retries; + } + + /** + * 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; + $retries = $this->flushRetries; + while ($retries--) { + try { + while ($message = array_pop($this->messages)) { + $count += $transport->send($message, $failedRecipients); + } + } catch (Swift_TransportException $exception) { + if ($retries) { + // re-queue the message at the end of the queue to give a chance + // to the other messages to be sent, in case the failure was due to + // this message and not just the transport failing + array_unshift($this->messages, $message); + + // wait half a second before we try again + usleep(500000); + } else { + throw $exception; + } + } + } + + return $count; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php new file mode 100644 index 0000000..242cbf3 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php @@ -0,0 +1,289 @@ +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 $this + */ + 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 $this + */ + public function addPart($body, $contentType = null, $charset = null) + { + return $this->attach(Swift_MimePart::newInstance($body, $contentType, $charset)->setEncoder($this->getEncoder())); + } + + /** + * Detach a signature handler from a message. + * + * @param Swift_Signer $signer + * + * @return $this + */ + 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 $this + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php new file mode 100644 index 0000000..d5ba14b --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php @@ -0,0 +1,149 @@ +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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php new file mode 100644 index 0000000..b49c3a8 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php @@ -0,0 +1,24 @@ += $maxLineLength || 76 < $maxLineLength) { + $maxLineLength = 76; + } + + $remainder = 0; + $base64ReadBufferRemainderBytes = null; + + // To reduce memory usage, the output buffer is streamed to the input buffer like so: + // Output Stream => base64encode => wrap line length => Input Stream + // HOWEVER it's important to note that base64_encode() should only be passed whole triplets of data (except for the final chunk of data) + // otherwise it will assume the input data has *ended* and it will incorrectly pad/terminate the base64 data mid-stream. + // We use $base64ReadBufferRemainderBytes to carry over 1-2 "remainder" bytes from the each chunk from OutputStream and pre-pend those onto the + // chunk of bytes read in the next iteration. + // When the OutputStream is empty, we must flush any remainder bytes. + while (true) { + $readBytes = $os->read(8192); + $atEOF = ($readBytes === false); + + if ($atEOF) { + $streamTheseBytes = $base64ReadBufferRemainderBytes; + } else { + $streamTheseBytes = $base64ReadBufferRemainderBytes.$readBytes; + } + $base64ReadBufferRemainderBytes = null; + $bytesLength = strlen($streamTheseBytes); + + if ($bytesLength === 0) { // no data left to encode + break; + } + + // if we're not on the last block of the ouput stream, make sure $streamTheseBytes ends with a complete triplet of data + // and carry over remainder 1-2 bytes to the next loop iteration + if (!$atEOF) { + $excessBytes = $bytesLength % 3; + if ($excessBytes !== 0) { + $base64ReadBufferRemainderBytes = substr($streamTheseBytes, -$excessBytes); + $streamTheseBytes = substr($streamTheseBytes, 0, $bytesLength - $excessBytes); + } + } + + $encoded = base64_encode($streamTheseBytes); + $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); + + if ($atEOF) { + break; + } + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'base64'. + * + * @return string + */ + public function getName() + { + return 'base64'; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php new file mode 100644 index 0000000..710b5ac --- /dev/null +++ b/core/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 + * + * @throws RuntimeException + * + * @return string + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php new file mode 100644 index 0000000..5cc907b --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php @@ -0,0 +1,134 @@ +_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); + + $i = strpos($enc, '=0D=0A'); + $newLineLength = $lineLen + ($i === false ? $size : $i); + + if ($currentLine && $newLineLength >= $thisLineLength) { + $is->write($prepend.$this->_standardize($currentLine)); + $currentLine = ''; + $prepend = "=\r\n"; + $thisLineLength = $maxLineLength; + $lineLen = 0; + } + + $currentLine .= $enc; + + if ($i === false) { + $lineLen += $size; + } else { + // 6 is the length of '=0D=0A'. + $lineLen = $size - strrpos($enc, '=0D=0A') - 6; + } + } + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php new file mode 100644 index 0000000..3214e1c --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php @@ -0,0 +1,98 @@ + + */ +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; + $this->safeEncoder->charsetChanged($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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php new file mode 100644 index 0000000..0b8526e --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php @@ -0,0 +1,64 @@ + + */ +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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php new file mode 100644 index 0000000..6af7571 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php new file mode 100644 index 0000000..cc44a6e --- /dev/null +++ b/core/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]; + } + + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php new file mode 100644 index 0000000..a8ddd27 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php new file mode 100644 index 0000000..510dd66 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php new file mode 100644 index 0000000..c65f26d --- /dev/null +++ b/core/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. + * + * @throws Swift_RfcComplianceException + * + * @return string + */ + 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(); + } + + /** + * 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 (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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php new file mode 100644 index 0000000..4075cbf --- /dev/null +++ b/core/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 (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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php new file mode 100644 index 0000000..b114506 --- /dev/null +++ b/core/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() + * + * @throws Swift_RfcComplianceException + * + * @return string + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php new file mode 100644 index 0000000..e4567fc --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php @@ -0,0 +1,351 @@ +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()} + * + * @throws Swift_RfcComplianceException + * + * @return array + */ + 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() + * + * @throws Swift_RfcComplianceException + * + * @return string[] + */ + 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() + * + * @throws Swift_RfcComplianceException + * + * @return string + */ + public function getFieldBody() + { + // Compute the string value of the header only if needed + if (null === $this->getCachedValue()) { + $this->setCachedValue($this->createMailboxListString($this->_mailboxes)); + } + + return $this->getCachedValue(); + } + + /** + * 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 + * + * @throws Swift_RfcComplianceException + * + * @return string + */ + protected function createMailboxListString(array $mailboxes) + { + return implode(', ', $this->_createNameAddressStrings($mailboxes)); + } + + /** + * Redefine the encoding requirements for mailboxes. + * + * All "specials" must be encoded as the full header value will not be quoted + * + * @see RFC 2822 3.2.1 + * + * @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 (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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php new file mode 100644 index 0000000..d749550 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php @@ -0,0 +1,133 @@ + + */ +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; + + /** + * @param string $name + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php new file mode 100644 index 0000000..c1777d3 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php @@ -0,0 +1,258 @@ +_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 (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 (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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php new file mode 100644 index 0000000..4a814b1 --- /dev/null +++ b/core/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 (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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php new file mode 100644 index 0000000..86177f1 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php new file mode 100644 index 0000000..9b36d21 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php new file mode 100644 index 0000000..30f460c --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php @@ -0,0 +1,117 @@ +setContentType('text/plain'); + if (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 $this + */ + 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 $this + */ + 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 $this + */ + 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 'yes' == $this->_getHeaderParameter('Content-Type', 'delsp') ? true : false; + } + + /** + * Turn delsp on or off for this entity. + * + * @param bool $delsp + * + * @return $this + */ + 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', 'iso-8859-15', ''))) { + // 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php new file mode 100644 index 0000000..e15c6ef --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php new file mode 100644 index 0000000..a06ce72 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php @@ -0,0 +1,414 @@ +_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); + + if (!array_key_exists($lowerName, $this->_headers)) { + return false; + } + + if (func_num_args() < 2) { + // index was not specified, so we only need to check that there is at least one header value set + return (bool) count($this->_headers[$lowerName]); + } + + return 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) + { + $name = strtolower($name); + + if (func_num_args() < 2) { + if ($this->has($name)) { + $values = array_values($this->_headers[$name]); + + return array_shift($values); + } + } else { + if ($this->has($name, $index)) { + return $this->_headers[$name][$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 self + */ + 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 (-1 === $aPos && -1 === $bPos) { + // just be sure to be determinist here + return $a > $b ? -1 : 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php new file mode 100644 index 0000000..72d40ce --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php @@ -0,0 +1,655 @@ +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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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|array $addresses + * @param string $name optional + * + * @return $this + */ + 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 $this + */ + 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 mixed $addresses + * @param string $name optional + * + * @return $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + public function setPriority($priority) + { + $priorityMap = array( + self::PRIORITY_HIGHEST => 'Highest', + self::PRIORITY_HIGH => 'High', + self::PRIORITY_NORMAL => 'Normal', + self::PRIORITY_LOW => 'Low', + self::PRIORITY_LOWEST => '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 $this + */ + 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 $this + */ + 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 $this + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php new file mode 100644 index 0000000..a13f1b2 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php @@ -0,0 +1,846 @@ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + public function setMaxLineLength($length) + { + $this->_maxLineLength = $length; + + return $this; + } + + /** + * Get all children added to this entity. + * + * @return Swift_Mime_MimeEntity[] + */ + public function getChildren() + { + return $this->_children; + } + + /** + * Set all children of this entity. + * + * @param Swift_Mime_MimeEntity[] $children + * @param int $compoundLevel For internal use only + * + * @return $this + */ + 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 ($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 $this + */ + 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 $this + */ + 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 + * + * @throws Swift_RfcComplianceException + * + * @return $this + */ + 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; + } + + 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; + } + + 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; + } + + $os->setReadPointer(0); + + 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]; + } + + 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) { + // Group the messages by order of preference + $sorted = array(); + foreach ($this->_immediateChildren as $child) { + $type = $child->getContentType(); + $level = array_key_exists($type, $this->_alternativePartOrder) ? $this->_alternativePartOrder[$type] : max($this->_alternativePartOrder) + 1; + + if (empty($sorted[$level])) { + $sorted[$level] = array(); + } + + $sorted[$level][] = $child; + } + + ksort($sorted); + + $this->_immediateChildren = array_reduce($sorted, 'array_merge', array()); + } + } + + /** + * Empties it's own contents from the cache. + */ + public function __destruct() + { + if ($this->_cache instanceof Swift_KeyCache) { + $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 = md5(uniqid(getmypid().mt_rand(), true)); + $children = array(); + foreach ($this->_children as $pos => $child) { + $children[$pos] = clone $child; + } + $this->setChildren($children); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php new file mode 100644 index 0000000..525b7ec --- /dev/null +++ b/core/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 self + */ + public static function newInstance($body = null, $contentType = null, $charset = null) + { + return new self($body, $contentType, $charset); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php new file mode 100644 index 0000000..ddde335 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php @@ -0,0 +1,36 @@ + + * + * 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 +{ + 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 self + */ + public static function newInstance() + { + return new self(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php new file mode 100644 index 0000000..1f26f9b --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php new file mode 100644 index 0000000..f7e18d0 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php new file mode 100644 index 0000000..9f9f08b --- /dev/null +++ b/core/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); + } + + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php new file mode 100644 index 0000000..5834440 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php @@ -0,0 +1,69 @@ +_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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php new file mode 100644 index 0000000..d9bce89 --- /dev/null +++ b/core/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(); + $code = $e->getCode(); + $this->_logger->add(sprintf('!! %s (code: %s)', $message, $code)); + $message .= PHP_EOL; + $message .= 'Log data:'.PHP_EOL; + $message .= $this->_logger->dump(); + $evt->cancelBubble(); + throw new Swift_TransportException($message, $code, $e->getPrevious()); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php new file mode 100644 index 0000000..865bb0a --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php new file mode 100644 index 0000000..3583297 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php new file mode 100644 index 0000000..5ff1d93 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php @@ -0,0 +1,74 @@ +messages = array(); + } + + /** + * Get the message list. + * + * @return Swift_Mime_Message[] + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php new file mode 100644 index 0000000..fb99e4c --- /dev/null +++ b/core/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 self + */ + 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 $this + */ + 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 $this + */ + public function setTimeout($timeout) + { + $this->_timeout = (int) $timeout; + + return $this; + } + + /** + * Set the username to use when connecting (if needed). + * + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->_username = $username; + + return $this; + } + + /** + * Set the password to use when connecting (if needed). + * + * @param string $password + * + * @return $this + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php new file mode 100644 index 0000000..c3a1f86 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php @@ -0,0 +1,213 @@ +_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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php new file mode 100644 index 0000000..0f21b7d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php new file mode 100644 index 0000000..cad9d16 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php new file mode 100644 index 0000000..c625935 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php new file mode 100644 index 0000000..595c0f6 --- /dev/null +++ b/core/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(); + } + + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php new file mode 100644 index 0000000..9c8deb3 --- /dev/null +++ b/core/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 $this + */ + 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 $this + */ + 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 $this + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php new file mode 100644 index 0000000..2897474 --- /dev/null +++ b/core/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 self + */ + public static function newInstance($command = '/usr/sbin/sendmail -bs') + { + return new self($command); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php new file mode 100644 index 0000000..2e7a872 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php @@ -0,0 +1,23 @@ + + * + * @deprecated + */ +class Swift_SignedMessage extends Swift_Message +{ +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php new file mode 100644 index 0000000..2d8176d --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php @@ -0,0 +1,20 @@ + + */ +interface Swift_Signer +{ + public function reset(); +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php new file mode 100644 index 0000000..8e66e18 --- /dev/null +++ b/core/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 self + */ + public function signMessage(Swift_Message $message); + + /** + * Return the list of header a signer might tamper. + * + * @return array + */ + public function getAlteredHeaders(); +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php new file mode 100644 index 0000000..454e84b --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php @@ -0,0 +1,712 @@ + + */ +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. + * + * @see RFC6376 3.3: Signers MUST implement and SHOULD sign using rsa-sha256. + * + * @var string + */ + protected $_hashAlgorithm = 'rsa-sha256'; + + /** + * 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('return-path' => true); + + /** + * Signer identity. + * + * @var string + */ + 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, otherwise 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 debugData here. + * + * @var string + */ + private $_debugHeadersData = ''; + + /** + * Stores the bodyHash. + * + * @var string + */ + private $_bodyHash = ''; + + /** + * Stores the signature header. + * + * @var Swift_Mime_Headers_ParameterizedHeader + */ + protected $_dkimHeader; + + 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; + + // keep fallback hash algorithm sha1 if php version is lower than 5.4.8 + if (PHP_VERSION_ID < 50408) { + $this->_hashAlgorithm = 'rsa-sha1'; + } + } + + /** + * Instanciate DKIMSigner. + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + * + * @return self + */ + 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->_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 + * + * @throws Swift_IoException + * + * @return int + */ + // TODO fix return + 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. + */ + 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; + } + } + } + + /** + * 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. + * + * @param string $hash 'rsa-sha1' or 'rsa-sha256' + * + * @throws Swift_SwiftException + * + * @return $this + */ + public function setHashAlgorithm($hash) + { + switch ($hash) { + case 'rsa-sha1': + $this->_hashAlgorithm = 'rsa-sha1'; + break; + case 'rsa-sha256': + $this->_hashAlgorithm = 'rsa-sha256'; + if (!defined('OPENSSL_ALGO_SHA256')) { + throw new Swift_SwiftException('Unable to set sha256 as it is not supported by OpenSSL.'); + } + break; + default: + throw new Swift_SwiftException('Unable to set the hash algorithm, must be one of rsa-sha1 or rsa-sha256 (%s given).', $hash); + } + + return $this; + } + + /** + * Set the body canonicalization algorithm. + * + * @param string $canon + * + * @return $this + */ + 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 $this + */ + public function setHeaderCanon($canon) + { + if ($canon == 'relaxed') { + $this->_headerCanon = 'relaxed'; + } else { + $this->_headerCanon = 'simple'; + } + + return $this; + } + + /** + * Set the signer identity. + * + * @param string $identity + * + * @return $this + */ + public function setSignerIdentity($identity) + { + $this->_signerIdentity = $identity; + + return $this; + } + + /** + * Set the length of the body to sign. + * + * @param mixed $len (bool or int) + * + * @return $this + */ + 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 int $time A timestamp + * + * @return $this + */ + public function setSignatureTimestamp($time) + { + $this->_signatureTimestamp = $time; + + return $this; + } + + /** + * Set the signature expiration timestamp. + * + * @param int $time A timestamp + * + * @return $this + */ + 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); + } + + /** + * @deprecated This method is currently useless in this class but it must be + * kept for BC reasons due to its "protected" scope. This method + * might be overridden by custom client code. + */ + protected function _endOfHeaders() + { + } + + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php new file mode 100644 index 0000000..0365363 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php @@ -0,0 +1,524 @@ + + */ +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 self + */ + public static function newInstance($privateKey, $domainName, $selector) + { + return new static($privateKey, $domainName, $selector); + } + + /** + * Resets internal states. + * + * @return $this + */ + 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 + * + * @throws Swift_IoException + * + * @return $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + 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]); + + break; + } + } + + return $this; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + * + * @return $this + */ + 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 $this + */ + public function setHashAlgorithm($hash) + { + $this->_hashAlgorithm = 'rsa-sha1'; + + return $this; + } + + /** + * Set the canonicalization algorithm. + * + * @param string $canon simple | nofws defaults to simple + * + * @return $this + */ + public function setCanon($canon) + { + if ($canon == 'nofws') { + $this->_canon = 'nofws'; + } else { + $this->_canon = 'simple'; + } + + return $this; + } + + /** + * Set the signer identity. + * + * @param string $identity + * + * @return $this + */ + public function setSignerIdentity($identity) + { + $this->_signerIdentity = $identity; + + return $this; + } + + /** + * Enable / disable the DebugHeaders. + * + * @param bool $debug + * + * @return $this + */ + 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'); + } + + return array('DomainKey-Signature'); + } + + /** + * Adds an ignored Header. + * + * @param string $header_name + * + * @return $this + */ + public function ignoreHeader($header_name) + { + $this->_ignoredHeaders[strtolower($header_name)] = true; + + return $this; + } + + /** + * Set the headers to sign. + * + * @param Swift_Mime_HeaderSet $headers + * + * @return $this + */ + 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 $this + */ + 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->_bodyCanonLine = ''; + } + + /** + * @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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php new file mode 100644 index 0000000..ef8832f --- /dev/null +++ b/core/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 self + */ + public function ignoreHeader($header_name); + + /** + * Prepare the Signer to get a new Body. + * + * @return self + */ + public function startBody(); + + /** + * Give the signal that the body has finished streaming. + * + * @return self + */ + public function endBody(); + + /** + * Give the headers already given. + * + * @param Swift_Mime_SimpleHeaderSet $headers + * + * @return self + */ + public function setHeaders(Swift_Mime_HeaderSet $headers); + + /** + * Add the header(s) to the headerSet. + * + * @param Swift_Mime_HeaderSet $headers + * + * @return self + */ + public function addSignature(Swift_Mime_HeaderSet $headers); + + /** + * Return the list of header a signer might tamper. + * + * @return array + */ + public function getAlteredHeaders(); +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php new file mode 100644 index 0000000..8fdbaa4 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php @@ -0,0 +1,190 @@ + + */ +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')) { + throw new Swift_SwiftException('php-opendkim extension not found'); + } + + $this->_peclLoaded = true; + + 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 int $time + * + * @return $this + */ + public function setSignatureTimestamp($time) + { + $this->_signatureTimestamp = $time; + + return $this; + } + + /** + * Set the signature expiration timestamp. + * + * @param int $time + * + * @return $this + */ + public function setSignatureExpiration($time) + { + $this->_signatureExpiration = $time; + + return $this; + } + + /** + * Enable / disable the DebugHeaders. + * + * @param bool $debug + * + * @return $this + */ + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php new file mode 100644 index 0000000..d13c02e --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php @@ -0,0 +1,436 @@ + + */ +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; + protected $extraCerts = null; + + /** + * @var Swift_StreamFilters_StringReplacementFilterFactory + */ + protected $replacementFactory; + + /** + * @var Swift_Mime_HeaderFactory + */ + protected $headerFactory; + + /** + * Constructor. + * + * @param string|null $signCertificate + * @param string|null $signPrivateKey + * @param string|null $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 self + */ + public static function newInstance($certificate = null, $privateKey = null) + { + return new self($certificate, $privateKey); + } + + /** + * Set the certificate location to use for signing. + * + * @see 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() + * @param string $extraCerts A file containing intermediate certificates needed by the signing certificate + * + * @return $this + */ + public function setSignCertificate($certificate, $privateKey = null, $signOptions = PKCS7_DETACHED, $extraCerts = null) + { + $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; + if (null !== $extraCerts) { + $this->extraCerts = str_replace('\\', '/', realpath($extraCerts)); + } + + return $this; + } + + /** + * Set the certificate location to use for encryption. + * + * @see http://www.php.net/manual/en/openssl.pkcs7.flags.php + * @see 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 $this + */ + 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 bool $signThenEncrypt + * + * @return $this + */ + public function setSignThenEncrypt($signThenEncrypt = true) + { + $this->signThenEncrypt = $signThenEncrypt; + + return $this; + } + + /** + * @return bool + */ + public function isSignThenEncrypt() + { + return $this->signThenEncrypt; + } + + /** + * Resets internal states. + * + * @return $this + */ + public function reset() + { + return $this; + } + + /** + * Change the Swift_Message to apply the signing. + * + * @param Swift_Message $message + * + * @return $this + */ + 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(); + + $args = array($outputStream->getPath(), $signedMessageStream->getPath(), $this->signCertificate, $this->signPrivateKey, array(), $this->signOptions); + if (null !== $this->extraCerts) { + $args[] = $this->extraCerts; + } + + if (!call_user_func_array('openssl_pkcs7_sign', $args)) { + 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'], '"'); + + // 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php new file mode 100644 index 0000000..b97f01e --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php @@ -0,0 +1,58 @@ +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 self + */ + public static function newInstance($host = 'localhost', $port = 25, $security = null) + { + return new self($host, $port, $security); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php new file mode 100644 index 0000000..c16ab4b --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php new file mode 100644 index 0000000..79c9b1f --- /dev/null +++ b/core/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 self + */ + public static function newInstance(Swift_Spool $spool) + { + return new self($spool); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php new file mode 100644 index 0000000..362be2e --- /dev/null +++ b/core/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); + $last_size = 0; + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php new file mode 100644 index 0000000..f64144a --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php @@ -0,0 +1,70 @@ +_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) + { + if ('' === $buffer) { + return false; + } + + $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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php new file mode 100644 index 0000000..e98240b --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php new file mode 100644 index 0000000..db3d310 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php @@ -0,0 +1,29 @@ +_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 $this + */ + 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)) { + $this->_throwException(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'])) { + // Set the address literal tag (See RFC 5321, section: 4.1.3) + if (false === strpos($_SERVER['SERVER_ADDR'], ':')) { + $prefix = ''; // IPv4 addresses are not tagged. + } else { + $prefix = 'IPv6:'; // Adding prefix in case of IPv6. + } + + $this->_domain = sprintf('[%s%s]', $prefix, $_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); + } + + return false; + } + + /** + * Destructor. + */ + public function __destruct() + { + try { + $this->stop(); + } catch (Exception $e) { + } + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php new file mode 100644 index 0000000..53f721d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php new file mode 100644 index 0000000..6ab6e33 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php new file mode 100644 index 0000000..8392658 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php @@ -0,0 +1,725 @@ + + */ +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('openssl_random_pseudo_bytes') || !function_exists('openssl_encrypt')) { + throw new LogicException('The OpenSSL extension must be enabled to use the NTLM authenticator.'); + } + + if (!function_exists('bcmul')) { + throw new LogicException('The BCMath 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); + } + + if (false !== strpos($name, '@')) { + list($user, $domain) = explode('@', $name); + + return array($domain, $user); + } + + // no domain passed + return array('', $name); + } + + /** + * 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!) + $time = number_format($time, 0, '.', ''); // save microtime to string + $time = bcadd($time, '11644473600000', 0); // add epoch time + $time = bcmul($time, 10000, 0); // 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 An 8-byte string + * @param string $key + * + * @return string + */ + protected function desEncrypt($value, $key) + { + // 1 == OPENSSL_RAW_DATA - but constant is only available as of PHP 5.4. + return substr(openssl_encrypt($value, 'DES-ECB', $key, 1), 0, 8); + } + + /** + * 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php new file mode 100644 index 0000000..43219f9 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php new file mode 100644 index 0000000..ca35e7b --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php @@ -0,0 +1,70 @@ + + * $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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php new file mode 100644 index 0000000..cb36133 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php new file mode 100644 index 0000000..12a9abf --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php new file mode 100644 index 0000000..156e2cf --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php @@ -0,0 +1,411 @@ + 'tcp', + 'host' => 'localhost', + 'port' => 25, + 'timeout' => 30, + 'blocking' => 1, + 'tls' => false, + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'stream_context_options' => array(), + ); + + /** + * 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 $this + */ + 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 $this + */ + 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 $this + */ + 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 $this + */ + public function setEncryption($encryption) + { + $encryption = strtolower($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 stream context options. + * + * @param array $options + * + * @return $this + */ + public function setStreamOptions($options) + { + $this->_params['stream_context_options'] = $options; + + return $this; + } + + /** + * Returns the stream context options. + * + * @return array + */ + public function getStreamOptions() + { + return $this->_params['stream_context_options']; + } + + /** + * Sets the source IP. + * + * @param string $source + * + * @return $this + */ + 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 isset($this->_params['sourceIp']) ? $this->_params['sourceIp'] : null; + } + + /** + * Set ESMTP extension handlers. + * + * @param Swift_Transport_EsmtpHandler[] $handlers + * + * @return $this + */ + 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 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 (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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php new file mode 100644 index 0000000..311a0f2 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php @@ -0,0 +1,88 @@ +_transports); + $sent = 0; + $this->_lastUsedTransport = null; + + for ($i = 0; $i < $maxTransports + && $transport = $this->_getNextTransport(); ++$i) { + try { + if (!$transport->isStarted()) { + $transport->start(); + } + + if ($sent = $transport->send($message, $failedRecipients)) { + $this->_lastUsedTransport = $transport; + + return $sent; + } + } 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php new file mode 100644 index 0000000..af97adf --- /dev/null +++ b/core/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); + } + + /** + * Get the Transport used in the last successful send operation. + * + * @return Swift_Transport + */ + public function getLastUsedTransport() + { + return $this->_lastUsedTransport; + } + + /** + * 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; + $this->_lastUsedTransport = null; + + for ($i = 0; $i < $maxTransports + && $transport = $this->_getNextTransport(); ++$i) { + try { + if (!$transport->isStarted()) { + $transport->start(); + } + if ($sent = $transport->send($message, $failedRecipients)) { + $this->_lastUsedTransport = $transport; + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php new file mode 100644 index 0000000..77489ce --- /dev/null +++ b/core/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 $this + */ + 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 (0 === $count) { + $this->_throwException(new Swift_TransportException('Cannot send message without a recipient')); + } + $to = $toHeader ? $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(); + + if ($toHeader) { + $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); + $subject = str_replace("\r\n", PHP_EOL, $subject); + $body = str_replace("\r\n", PHP_EOL, $body); + $to = str_replace("\r\n", PHP_EOL, $to); + } else { + // Windows, using SMTP + $headers = str_replace("\r\n.", "\r\n..", $headers); + $subject = str_replace("\r\n.", "\r\n..", $subject); + $body = str_replace("\r\n.", "\r\n..", $body); + $to = str_replace("\r\n.", "\r\n..", $to); + } + + if ($this->_invoker->mail($to, $subject, $body, $headers, $this->_formatExtraParams($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); + } + + /** 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; + } + } + + /** 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; + } + + /** + * Fix CVE-2016-10074 by disallowing potentially unsafe shell characters. + * + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @param string $string The string to be validated + * + * @return bool + */ + private function _isShellSafe($string) + { + // Future-proof + if (escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))) { + return false; + } + + $length = strlen($string); + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + // All other characters have a special meaning in at least one common shell, including = and +. + // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + // Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Return php mail extra params to use for invoker->mail. + * + * @param $extraParams + * @param $reversePath + * + * @return string|null + */ + private function _formatExtraParams($extraParams, $reversePath) + { + if (false !== strpos($extraParams, '-f%s')) { + if (empty($reversePath) || false === $this->_isShellSafe($reversePath)) { + $extraParams = str_replace('-f%s', '', $extraParams); + } else { + $extraParams = sprintf($extraParams, $reversePath); + } + } + + return !empty($extraParams) ? $extraParams : null; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php new file mode 100644 index 0000000..ad20e0e --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php new file mode 100644 index 0000000..6430d5f --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php @@ -0,0 +1,160 @@ + 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 $this + */ + 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(); + $count = 0; + + 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php new file mode 100644 index 0000000..4cab66b --- /dev/null +++ b/core/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 $this + */ + 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_SPOOLED : 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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php new file mode 100644 index 0000000..3a9fe76 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php @@ -0,0 +1,334 @@ +_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() + { + // STREAM_CRYPTO_METHOD_TLS_CLIENT only allow tls1.0 connections (some php versions) + // To support modern tls we allow explicit tls1.0, tls1.1, tls1.2 + // Ssl3 and older are not allowed because they are vulnerable + // @TODO make tls arguments configurable + $cryptoType = STREAM_CRYPTO_METHOD_TLS_CLIENT; + if (PHP_VERSION_ID >= 50600) { + $cryptoType = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + + return stream_socket_enable_crypto($this->_stream, true, $cryptoType); + } + + /** + * 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 + * + * @throws Swift_IoException + * + * @return string + */ + 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 + * + * @throws Swift_IoException + * + * @return string|bool + */ + 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'; + } + if (isset($this->_params['stream_context_options'])) { + $options = array_merge($options, $this->_params['stream_context_options']); + } + $streamContext = stream_context_create($options); + $this->_stream = @stream_socket_client($host.':'.$this->_params['port'], $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $streamContext); + 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'), + ); + $pipes = array(); + $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/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php new file mode 100644 index 0000000..4ae2412 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php @@ -0,0 +1,29 @@ + + */ +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/core/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php b/core/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php new file mode 100644 index 0000000..6023448 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php b/core/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php new file mode 100644 index 0000000..64d69d2 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php b/core/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php new file mode 100644 index 0000000..d575e4f --- /dev/null +++ b/core/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(PHP_VERSION_ID >= 50407 ? 'mime.qpcontentencoderproxy' : 'mime.safeqpcontentencoder') +; + +unset($swift_mime_types); diff --git a/core/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php b/core/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php new file mode 100644 index 0000000..77e432c --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/mime_types.php b/core/vendor/swiftmailer/swiftmailer/lib/mime_types.php new file mode 100644 index 0000000..b42c1cc --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/lib/preferences.php b/core/vendor/swiftmailer/swiftmailer/lib/preferences.php new file mode 100644 index 0000000..0b430e6 --- /dev/null +++ b/core/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 (PHP_VERSION_ID < 50407) { + $preferences->setQPDotEscape(false); +} diff --git a/core/vendor/swiftmailer/swiftmailer/lib/swift_init.php b/core/vendor/swiftmailer/swiftmailer/lib/swift_init.php new file mode 100644 index 0000000..ff71963 --- /dev/null +++ b/core/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', + '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/vnd.ms-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/core/vendor/swiftmailer/swiftmailer/phpunit.xml.dist b/core/vendor/swiftmailer/swiftmailer/phpunit.xml.dist new file mode 100644 index 0000000..606c5b4 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/phpunit.xml.dist @@ -0,0 +1,39 @@ + + + + + + + + + + + + tests/unit + + + tests/acceptance + + + tests/bug + + + tests/smoke + + + + + + + + diff --git a/core/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php b/core/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php new file mode 100644 index 0000000..069d11a --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php @@ -0,0 +1,62 @@ +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/core/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php b/core/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php new file mode 100644 index 0000000..7f079d9 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php @@ -0,0 +1,11 @@ +content .= $arg; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php b/core/vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php new file mode 100644 index 0000000..71c5713 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php b/core/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php new file mode 100644 index 0000000..f0e2736 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/core/vendor/swiftmailer/swiftmailer/tests/_samples/files/swiftmailer.png b/core/vendor/swiftmailer/swiftmailer/tests/_samples/files/swiftmailer.png new file mode 100644 index 0000000..1b95f61 Binary files /dev/null and b/core/vendor/swiftmailer/swiftmailer/tests/_samples/files/swiftmailer.png differ diff --git a/core/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip b/core/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip new file mode 100644 index 0000000..5a580ec Binary files /dev/null and b/core/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip differ diff --git a/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl new file mode 100644 index 0000000..dd9818a --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl @@ -0,0 +1 @@ +D42DA34CF90FA0DE diff --git a/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt new file mode 100644 index 0000000..695f814 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key new file mode 100644 index 0000000..df67470 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh new file mode 100644 index 0000000..0454f20 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh @@ -0,0 +1,40 @@ +#!/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 intermediate.key 2048 +openssl req -new -key intermediate.key -subj '/CN=Swiftmailer Intermediate/O=Swiftmailer/L=Paris/C=FR' -out intermediate.csr +openssl x509 -req -in intermediate.csr -CA CA.crt -CAkey CA.key -set_serial 01 -out intermediate.crt -days 1460 +openssl x509 -in intermediate.crt -clrtrust -out intermediate.crt + +rm intermediate.csr + +openssl genrsa -out sign2.key 2048 +openssl req -new -key sign2.key -subj '/CN=Swiftmailer-User2/O=Swiftmailer/L=Paris/C=FR' -out sign2.csr +openssl x509 -req -in sign2.csr -CA intermediate.crt -CAkey intermediate.key -set_serial 01 -out sign2.crt -days 1460 -addtrust emailProtection +openssl x509 -in sign2.crt -clrtrust -out sign2.crt + +rm sign2.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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt new file mode 100644 index 0000000..7435855 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key new file mode 100644 index 0000000..aa620ca --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt new file mode 100644 index 0000000..6908165 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key new file mode 100644 index 0000000..e322a8f --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.crt b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.crt new file mode 100644 index 0000000..012f734 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFjCCAf4CAQEwDQYJKoZIhvcNAQEFBQAwTDEXMBUGA1UEAwwOU3dpZnRtYWls +ZXIgQ0ExFDASBgNVBAoMC1N3aWZ0bWFpbGVyMQ4wDAYDVQQHDAVQYXJpczELMAkG +A1UEBhMCRlIwHhcNMTQxMTIwMTMyNTQxWhcNMTgxMTE5MTMyNTQxWjBWMSEwHwYD +VQQDDBhTd2lmdG1haWxlciBJbnRlcm1lZGlhdGUxFDASBgNVBAoMC1N3aWZ0bWFp +bGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDSgEhftX6f1wV+uqWl4J+zwCn8fHaLZT6GZ0Gs9ThE +4e+4mkLG1rvSEIJon8U0ic8Zph1UGa1Grveh5bgbldHlFxYSsCCyDGgixRvRWNhI +KuO+SxaIZChqqKwVn3aNQ4BZOSo/MjJ/jQyr9BMgMmdxlHR3e1wkkeAkW//sOsfu +xQGF1h9yeQvuu/GbG6K7vHSGOGd5O3G7bftfQ7l78TMqeJ7jV32AdJeuO5MD4dRn +W4CQLTaeribLN0MKn35UdSiFoZxKHqqWcgtl5xcJWPOmq6CsAJ2Eo90kW/BHOrLv +10h6Oan9R1PdXSvSCvVnXY3Kz30zofw305oA/KJk/hVzAgMBAAEwDQYJKoZIhvcN +AQEFBQADggEBABijZ2NNd05Js5VFNr4uyaydam9Yqu/nnrxbPRbAXPlCduydu2Gd +d1ekn3nblMJ87Bc7zVyHdAQD8/AfS1LOKuoWHpTzmlpIL+8T5sbCYG5J1jKdeLkh +7L/UD5v1ACgA33oKtN8GzcrIq8Zp73r0n+c3hFCfDYRSZRCxGyIf3qgU2LBOD0A3 +wTff/N8E/p3WaJX9VnuQ7xyRMOubDuqJnlo5YsFv7wjyGOIAz9afZzcEbH6czt/t +g0Xc/kGr/fkAjUu+z3ZfE4247Gut5m3hEVwWkpEEzQo4osX/BEX20Q2nPz9WBq4a +pK3qNNGwAqS4gdE3ihOExMWxAKgr9d2CcU4= +-----END CERTIFICATE----- diff --git a/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.key b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.key new file mode 100644 index 0000000..569eb0c --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/intermediate.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA0oBIX7V+n9cFfrqlpeCfs8Ap/Hx2i2U+hmdBrPU4ROHvuJpC +xta70hCCaJ/FNInPGaYdVBmtRq73oeW4G5XR5RcWErAgsgxoIsUb0VjYSCrjvksW +iGQoaqisFZ92jUOAWTkqPzIyf40Mq/QTIDJncZR0d3tcJJHgJFv/7DrH7sUBhdYf +cnkL7rvxmxuiu7x0hjhneTtxu237X0O5e/EzKnie41d9gHSXrjuTA+HUZ1uAkC02 +nq4myzdDCp9+VHUohaGcSh6qlnILZecXCVjzpqugrACdhKPdJFvwRzqy79dIejmp +/UdT3V0r0gr1Z12Nys99M6H8N9OaAPyiZP4VcwIDAQABAoIBAQDLJiKyu2XIvKsA +8wCKZY262+mpUjTVso/1BhHL6Zy0XZgMgFORsgrxYB16+zZGzfiguD/1uhIP9Svn +gtt7Q8udW/phbrkfG/okFDYUg7m3bCz+qVjFqGOZC8+Hzq2LB2oGsbSj6L3zexyP +lq4elIZghvUfml4CrQW0EVWbld79/kF7XHABcIOk2+3f63XAQWkjdFNxj5+z6TR0 +52Rv7SmRioAsukW9wr77G3Luv/0cEzDFXgGW5s0wO+rJg28smlsIaj+Y0KsptTig +reQvReAT/S5ZxEp4H6WtXQ1WmaliMB0Gcu4TKB0yE8DoTeCePuslo9DqGokXYT66 +oqtcVMqBAoGBAPoOL9byNNU/bBNDWSCiq8PqhSjl0M4vYBGqtgMXM4GFOJU+W2nX +YRJbbxoSd/DKjnxEsR6V0vDTDHj4ZSkgmpEmVhEdAiwUwaZ0T8YUaCPhdiAENo5+ +zRBWVJcvAC2XKTK1hy5D7Z5vlC32HHygYqitU+JsK4ylvhrdeOcGx5cfAoGBANeB +X0JbeuqBEwwEHZqYSpzmtB+IEiuYc9ARTttHEvIWgCThK4ldAzbXhDUIQy3Hm0sL +PzDA33furNl2WwB+vmOuioYMNjArKrfg689Aim1byg4AHM5XVQcqoDSOABtI55iP +E0hYDe/d4ema2gk1uR/mT4pnLnk2VzRKsHUbP9stAoGBAKjyIuJwPMADnMqbC0Hg +hnrVHejW9TAJlDf7hgQqjdMppmQ3gF3PdjeH7VXJOp5GzOQrKRxIEABEJ74n3Xlf +HO+K3kWrusb7syb6mNd0/DOZ5kyVbCL0iypJmdeXmuAyrFQlj9LzdD1Cl/RBv1d4 +qY/bo7xsZzQc24edMU2uJ/XzAoGBAMHChA95iK5HlwR6vtM8kfk4REMFaLDhxV8R +8MCeyp33NQfzm91JT5aDd07nOt9yVGHInuwKveFrKuXq0C9FxZCCYfHcEOyGI0Zo +aBxTfyKMIMMtvriXNM/Yt2oJMndVuUUlfsTQxtcfu/r5S4h0URopTOK3msVI4mcV +sEnaUjORAoGAGDnslKYtROQMXTe4sh6CoJ32J8UZVV9M+8NLus9rO0v/eZ/pIFxo +MXGrrrl51ScqahCQ+DXHzpLvInsdlAJvDP3ymhb7H2xGsyvb3x2YgsLmr1YVOnli +ISbCssno3vZyFU1TDjeEIKqZHc92byHNMjMuhmlaA25g8kb0cCO76EA= +-----END RSA PRIVATE KEY----- diff --git a/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt new file mode 100644 index 0000000..15fd65d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key new file mode 100644 index 0000000..b3d3c53 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.crt b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.crt new file mode 100644 index 0000000..44f4d9b --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGTCCAgECAQEwDQYJKoZIhvcNAQEFBQAwVjEhMB8GA1UEAwwYU3dpZnRtYWls +ZXIgSW50ZXJtZWRpYXRlMRQwEgYDVQQKDAtTd2lmdG1haWxlcjEOMAwGA1UEBwwF +UGFyaXMxCzAJBgNVBAYTAkZSMB4XDTE0MTEyMDEzMjYyNloXDTE4MTExOTEzMjYy +NlowTzEaMBgGA1UEAwwRU3dpZnRtYWlsZXItVXNlcjIxFDASBgNVBAoMC1N3aWZ0 +bWFpbGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDbr1m4z/rzFS/DxUUQIhKNx19oAeGYLt3niaEP +twfvBMNB80gMgM9d+XtqrPAMPeY/2C8t5NlChNPKMcR70JBKdmlSH4/aTjaIfWmD +PoZJjvRRXINZgSHNKIt4ZGAN/EPFr19CBisV4iPxzu+lyIbbkaZJ/qtyatlP7m/q +8TnykFRlyxNEveCakpcXeRd3YTFGKWoED+/URhVc0cCPZVjoeSTtPHAYBnC29lG5 +VFbq6NBQiyF4tpjOHRarq6G8PtQFH9CpAZg5bPk3bqka9C8mEr5jWfrM4EHtUkTl +CwVLOQRBsz/nMBT27pXZh18GU0hc3geNDN4kqaeqgNBo0mblAgMBAAEwDQYJKoZI +hvcNAQEFBQADggEBAAHDMuv6oxWPsTQWWGWWFIk7QZu3iogMqFuxhhQxg8BE37CT +Vt1mBVEjYGMkWhMSwWBMWuP6yuOZecWtpp6eOie/UKGg1XoW7Y7zq2aQaP7YPug0 +8Lgq1jIo7iO2b6gZeMtLiTZrxyte0z1XzS3wy7ZC9mZjYd7QE7mZ+/rzQ0x5zjOp +G8b3msS/yYYJCMN+HtHln++HOGmm6uhvbsHTfvvZvtl7F5vJ5WhGGlUfjhanSEtZ +1RKx+cbgIv1eFOGO1OTuZfEuKdLb0T38d/rjLeI99nVVKEIGtLmX4dj327GHe/D3 +aPr2blF2gOvlzkfN9Vz6ZUE2s3rVBeCg2AVseYQ= +-----END CERTIFICATE----- diff --git a/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.key b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.key new file mode 100644 index 0000000..ffb189b --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign2.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA269ZuM/68xUvw8VFECISjcdfaAHhmC7d54mhD7cH7wTDQfNI +DIDPXfl7aqzwDD3mP9gvLeTZQoTTyjHEe9CQSnZpUh+P2k42iH1pgz6GSY70UVyD +WYEhzSiLeGRgDfxDxa9fQgYrFeIj8c7vpciG25GmSf6rcmrZT+5v6vE58pBUZcsT +RL3gmpKXF3kXd2ExRilqBA/v1EYVXNHAj2VY6Hkk7TxwGAZwtvZRuVRW6ujQUIsh +eLaYzh0Wq6uhvD7UBR/QqQGYOWz5N26pGvQvJhK+Y1n6zOBB7VJE5QsFSzkEQbM/ +5zAU9u6V2YdfBlNIXN4HjQzeJKmnqoDQaNJm5QIDAQABAoIBAAM2FvuqnqJ7Bs23 +zoCj3t2PsodUr7WHydqemmoeZNFLoocORVlZcK6Q/QrcKE4lgX4hbN8g30QnqOjl +vVeJ/vH3tSZsK7AnQIjSPH6cpV3h5xRhY9IlHxdepltGLFlH/L2hCKVwbaTOP3RD +cCFeQwpmoKWoQV1UzoRqmdw3Vn+DMaUULomLVR9aSW9PnKeFL+tPWShf7GmVISfM +2H6xKw/qT0XAX59ZHA1laxSFVvbV5ZcKrBOFMV407Vzw2d3ojmfEzNsHjUVBXX8j +B5nK1VeJiTVmcoVhnRX7tXESDaZy+Kv38pqOmc8Svn70lDJ35SM2EpWnX39w5LsQ +29NsIUECgYEA/vNKiMfVmmZNQrpcuHQe5adlmz9+I4xJ4wbRzrS7czpbKF0/iaPf +dKoVz67yYHOJCBHTVaXWkElQsq1mkyuFt/cc0ReJXO8709+t+6ULsE50cLQm/HN5 +npg3gw0Ls/9dy/cHM5SdVIHMBm9oQ65rXup/dqWC8Dz2cAAOQhIPwx0CgYEA3Jbk +DPdUlrj4sXcE3V/CtmBuK9Xq1xolJt026fYCrle0YhdMKmchRBDCc6BzM+F/vDyC +llPfQu8TDXK40Oan7GbxMdoLqKK9gSIq1dvfG1YMMz8OrBcX8xKe61KFRWd7QSBJ +BcY575NzYHapOHVGnUJ68j8zCow0gfb7q6iK4GkCgYEAz2mUuKSCxYL21hORfUqT +HFjMU7oa38axEa6pn9XvLjZKlRMPruWP1HTPG9ADRa6Yy+TcnrA1V9sdeM+TRKXC +usCiRAU27lF+xccS30gNs1iQaGRX10gGqJzDhK1nWP+nClmlFTSRrn+OQan/FBjh +Jy31lsveM54VC1cwQlY5Vo0CgYEArtjfnLNzFiE55xjq/znHUd4vlYlzItrzddHE +lEBOsbiNH29ODRI/2P7b0uDsT8Q/BoqEC/ohLqHn3TIA8nzRv91880HdGecdBL17 +bJZiSv2yn/AshhWsAxzQYMDBKFk05lNb7jrIc3DR9DU6PqketsoaP+f+Yi7t89I8 +fD0VD3kCgYAaJCoQshng/ijiHF/RJXLrXXHJSUmaOfbweX/mzFup0YR1LxUjcv85 +cxvwc41Y2iI5MwUXyX97/GYKeoobzWZy3XflNWtg04rcInVaPsb/OOFDDqI+MkzT +B4PcCurOmjzcxHMVE34CYvl3YVwWrPb5JO1rYG9T2gKUJnLU6qG4Bw== +-----END RSA PRIVATE KEY----- diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default b/core/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default new file mode 100644 index 0000000..5717c98 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default @@ -0,0 +1,37 @@ +_testFile = sys_get_temp_dir().'/swift-test-file'.__CLASS__; + file_put_contents($this->_testFile, 'abcdefghijklm'); + } + + protected 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 testWritingWithFulleMessageLengthOfAMultipleOf8192() + { + $file = $this->_createFileStream($this->_testFile, true); + $file->addFilter($this->_createFilter(array("\r\n", "\r"), "\n"), 'allToLF'); + $file->write(''); + $file->flushBuffers(); + $this->assertEquals('', 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'); + } + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + + private function _createMockInputStream() + { + return $this->getMockBuilder('Swift_InputByteStream')->getMock(); + } + + private function _createFileStream($file, $writable = false) + { + return new Swift_ByteStream_FileByteStream($file, $writable); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php new file mode 100644 index 0000000..c13e570 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php new file mode 100644 index 0000000..e83c2bf --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php @@ -0,0 +1,24 @@ +listItems() as $itemName) { + try { + // to be removed in 6.0 + if ('transport.mail' === $itemName) { + continue; + } + $di->lookup($itemName); + } catch (Swift_DependencyException $e) { + $this->fail($e->getMessage()); + } + } + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php new file mode 100644 index 0000000..fc5a814 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php new file mode 100644 index 0000000..442d9a9 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php @@ -0,0 +1,54 @@ +_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); + + foreach (explode("\r\n", $encodedText) as $line) { + $this->assertLessThanOrEqual(76, strlen($line)); + } + + $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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php new file mode 100644 index 0000000..bcb6b95 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php new file mode 100644 index 0000000..6a4d05d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php new file mode 100644 index 0000000..5fab14c --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php new file mode 100644 index 0000000..0e027c2 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php @@ -0,0 +1,173 @@ +_key1 = uniqid(microtime(true), true); + $this->_key2 = uniqid(microtime(true), true); + $this->_cache = new Swift_KeyCache_DiskKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream(), sys_get_temp_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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php new file mode 100644 index 0000000..5f4e983 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php @@ -0,0 +1,55 @@ +_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() + ); + } + + protected function _createMessage() + { + Swift_DependencyContainer::getInstance() + ->register('properties.charset')->asValue(null); + + return Swift_Message::newInstance(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php new file mode 100644 index 0000000..7353d9d --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php @@ -0,0 +1,123 @@ +_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() + ); + } + + protected function _createAttachment() + { + $entity = new Swift_Mime_Attachment( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..a72f5ff --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..0dfc4e2 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php @@ -0,0 +1,88 @@ +_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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..5eff4e2 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..a383b58 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php @@ -0,0 +1,160 @@ +_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + protected function tearDown() + { + Swift_Preferences::getInstance()->setQPDotEscape(false); + } + + 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(); + } + + private function _createEncoderFromContainer() + { + return Swift_DependencyContainer::getInstance() + ->lookup('mime.qpcontentencoder') + ; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php new file mode 100644 index 0000000..0f7aa72 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php @@ -0,0 +1,136 @@ +_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-ID: '."\r\n". + 'Content-Disposition: inline'."\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-ID: <'.$id.'>'."\r\n". + 'Content-Disposition: attachment'."\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-ID: <'.$id.'>'."\r\n". + 'Content-Disposition: inline; filename=foo.pdf'."\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-ID: <'.$id.'>'."\r\n". + 'Content-Disposition: inline; size=12340'."\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-ID: <'.$id.'>'."\r\n". + 'Content-Disposition: inline; filename=foo.pdf; size=12340'."\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-ID: <'.$id.'>'."\r\n". + 'Content-Disposition: inline; filename=foo.pdf; size=12340'."\r\n". + "\r\n". + base64_encode('abcd'), + $file->toString() + ); + } + + protected function _createEmbeddedFile() + { + $entity = new Swift_Mime_EmbeddedFile( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php new file mode 100644 index 0000000..e3fad6d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php new file mode 100644 index 0000000..a7f6fc5 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php @@ -0,0 +1,127 @@ +_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() + ); + } + + protected function _createMimePart() + { + $entity = new Swift_Mime_MimePart( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php new file mode 100644 index 0000000..912768e --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php @@ -0,0 +1,1249 @@ +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-ID: <'.$cid.'>'."\r\n". + 'Content-Disposition: inline; filename=myimage.jpg'."\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-ID: <'.$cid.'>'."\r\n". + 'Content-Disposition: inline; filename=myimage.jpg'."\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-ID: <'.$cid.'>'."\r\n". + 'Content-Disposition: inline; filename=myimage.jpg'."\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() + ); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php new file mode 100644 index 0000000..f42405d --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php @@ -0,0 +1,15 @@ +register('properties.charset')->asValue(null); + + return Swift_MimePart::newInstance(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php new file mode 100644 index 0000000..21abc13 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php @@ -0,0 +1,131 @@ +markTestSkipped( + 'Will fail on travis-ci if not skipped due to travis blocking '. + 'socket mailing tcp connections.' + ); + } + + $this->_buffer = new Swift_Transport_StreamBuffer( + $this->getMockBuilder('Swift_ReplacementFilterFactory')->getMock() + ); + } + + 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'); + } + + private function _createMockInputStream() + { + return $this->getMockBuilder('Swift_InputByteStream')->getMock(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php new file mode 100644 index 0000000..4c3c7d3 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php @@ -0,0 +1,33 @@ +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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php new file mode 100644 index 0000000..a37439d --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php @@ -0,0 +1,26 @@ +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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php new file mode 100644 index 0000000..59362b0 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php @@ -0,0 +1,67 @@ +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->getMockBuilder('Swift_ReplacementFilterFactory')->getMock() + ); + } + + 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()); + } + + protected function tearDown() + { + if ($this->_server) { + stream_socket_shutdown($this->_server, STREAM_SHUT_RDWR); + } + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php new file mode 100644 index 0000000..32e0fe8 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php @@ -0,0 +1,40 @@ +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/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php new file mode 100644 index 0000000..1053a87 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php @@ -0,0 +1,39 @@ +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/core/vendor/swiftmailer/swiftmailer/tests/bootstrap.php b/core/vendor/swiftmailer/swiftmailer/tests/bootstrap.php new file mode 100644 index 0000000..27091a2 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/bootstrap.php @@ -0,0 +1,21 @@ +allowMockingNonExistentMethods(true); + +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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php new file mode 100644 index 0000000..ba29ba8 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php new file mode 100644 index 0000000..40b5a77 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php new file mode 100644 index 0000000..7563f4d --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php new file mode 100644 index 0000000..f5f057a --- /dev/null +++ b/core/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 testNonEmptyFileNameAsAttachment() + { + $message = new Swift_Message(); + try { + $message->attach(Swift_Attachment::fromPath(__FILE__)); + } catch (Exception $e) { + $this->fail('Path should not be empty'); + } + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php new file mode 100644 index 0000000..768bf3d --- /dev/null +++ b/core/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-ID: <'.$cidVal.'>'."\r\n". + 'Content-Disposition: inline; filename=image.gif'."\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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php new file mode 100644 index 0000000..98999f0 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php new file mode 100644 index 0000000..9deae4f --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php @@ -0,0 +1,192 @@ +_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-ID: <'.preg_quote($imgId, '~').'>'."\r\n". + 'Content-Disposition: inline; filename=image.gif'."\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-ID: <'.preg_quote($imgId, '~').'>'."\r\n". + 'Content-Disposition: inline; filename=image.gif'."\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); + } + + public function assertPatternInStream($pattern, $stream, $message = '%s') + { + $string = ''; + while (false !== $bytes = $stream->read(8192)) { + $string .= $bytes; + } + $this->assertRegExp($pattern, $string, $message); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php new file mode 100644 index 0000000..b83984f --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php new file mode 100644 index 0000000..48074f0 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php @@ -0,0 +1,110 @@ +_attachmentFile = sys_get_temp_dir().'/attach.rand.bin'; + file_put_contents($this->_attachmentFile, ''); + + $this->_outputFile = sys_get_temp_dir().'/attach.out.bin'; + file_put_contents($this->_outputFile, ''); + } + + protected 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 + ); + } + } + + 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)); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug534Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug534Test.php new file mode 100644 index 0000000..263cae5 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug534Test.php @@ -0,0 +1,38 @@ +setFrom('from@example.com') + ->setTo('to@example.com') + ->setSubject('test') + ; + $cid = $message->embed(Swift_Image::fromPath(__DIR__.'/../../_samples/files/swiftmailer.png')); + $message->setBody('', 'text/html'); + + $that = $this; + $messageValidation = function (Swift_Mime_Message $message) use ($that) { + preg_match('/cid:(.*)"/', $message->toString(), $matches); + $cid = $matches[1]; + preg_match('/Content-ID: <(.*)>/', $message->toString(), $matches); + $contentId = $matches[1]; + $that->assertEquals($cid, $contentId, 'cid in body and mime part Content-ID differ'); + + return true; + }; + + $failedRecipients = array(); + + $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); + $memorySpool->flushQueue($transport, $failedRecipients); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug650Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug650Test.php new file mode 100644 index 0000000..3393fb8 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug650Test.php @@ -0,0 +1,36 @@ +setCharset('utf-8'); + + $header->setNameAddresses(array( + 'test@example.com' => $name, + )); + + $this->assertSame('To: '.$expectedEncodedName." \r\n", $header->toString()); + } + + public function encodingDataProvider() + { + return array( + array('this is " a test ö', 'this is =?utf-8?Q?=22?= a test =?utf-8?Q?=C3=B6?='), + array(': this is a test ö', '=?utf-8?Q?=3A?= this is a test =?utf-8?Q?=C3=B6?='), + array('( test ö', '=?utf-8?Q?=28?= test =?utf-8?Q?=C3=B6?='), + array('[ test ö', '=?utf-8?Q?=5B?= test =?utf-8?Q?=C3=B6?='), + array('@ test ö)', '=?utf-8?Q?=40?= test =?utf-8?Q?=C3=B6=29?='), + ); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php new file mode 100644 index 0000000..d58242f --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php new file mode 100644 index 0000000..899083c --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php @@ -0,0 +1,71 @@ +_inputFile = sys_get_temp_dir().'/in.bin'; + file_put_contents($this->_inputFile, ''); + + $this->_outputFile = sys_get_temp_dir().'/out.bin'; + file_put_contents($this->_outputFile, ''); + + $this->_encoder = $this->_createEncoder(); + } + + protected 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' + ); + } + + public function assertMaxLineLength($length, $filePath, $message = '%s') + { + $lines = file($filePath); + foreach ($lines as $line) { + $this->assertTrue((strlen(trim($line)) <= 76), $message); + } + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/BugFileByteStreamConsecutiveReadCallsTest.php b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/BugFileByteStreamConsecutiveReadCallsTest.php new file mode 100644 index 0000000..35733ec --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/bug/Swift/BugFileByteStreamConsecutiveReadCallsTest.php @@ -0,0 +1,19 @@ +read(100); + } catch (\Swift_IoException $exc) { + $fbs->read(100); + } + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/fixtures/MimeEntityFixture.php b/core/vendor/swiftmailer/swiftmailer/tests/fixtures/MimeEntityFixture.php new file mode 100644 index 0000000..159c2ae --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/fixtures/MimeEntityFixture.php @@ -0,0 +1,67 @@ +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/core/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default b/core/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default new file mode 100644 index 0000000..0de2763 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php b/core/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php new file mode 100644 index 0000000..c7501d4 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php b/core/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php new file mode 100644 index 0000000..3b13cc5 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php @@ -0,0 +1,31 @@ +_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/core/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php b/core/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php new file mode 100644 index 0000000..b9ebef5 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php @@ -0,0 +1,40 @@ +_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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php new file mode 100644 index 0000000..60ebb66 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php @@ -0,0 +1,201 @@ +_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->assertFalse($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->assertFalse($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->getMockBuilder('Swift_InputByteStream')->getMock(); + $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock(); + + $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->getMockBuilder('Swift_InputByteStream')->getMock(); + $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock(); + + $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->getMockBuilder('Swift_InputByteStream')->getMock(); + $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock(); + + $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'); + } + + private function _createArrayStream($input) + { + return new Swift_ByteStream_ArrayByteStream($input); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php new file mode 100644 index 0000000..3f7a46c --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php new file mode 100644 index 0000000..0d56736 --- /dev/null +++ b/core/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; + + protected 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php new file mode 100644 index 0000000..ec17eeb --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php new file mode 100644 index 0000000..977051e --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php @@ -0,0 +1,358 @@ +_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->assertFalse($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->assertFalse($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->assertFalse($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->assertFalse($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->assertFalse($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->assertFalse($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->assertFalse($stream->read(1)); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php new file mode 100644 index 0000000..ccd14f6 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php @@ -0,0 +1,176 @@ +arg1 = $arg1; + $this->arg2 = $arg2; + } +} + +class Swift_DependencyContainerTest extends \PHPUnit_Framework_TestCase +{ + private $_container; + + protected 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php new file mode 100644 index 0000000..b89eb9f --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php new file mode 100644 index 0000000..6740f22 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php @@ -0,0 +1,400 @@ +_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' + ); + } + + public function testTextIsPreWrapped() + { + $encoder = $this->createEncoder(); + + $input = str_repeat('a', 70)."\r\n". + str_repeat('a', 70)."\r\n". + str_repeat('a', 70); + + $this->assertEquals( + $input, $encoder->encodeString($input) + ); + } + + private function _createCharStream() + { + return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing(); + } + + private function createEncoder() + { + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $charStream = new Swift_CharacterStream_NgCharacterStream($factory, 'utf-8'); + + return new Swift_Encoder_QpEncoder($charStream); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php new file mode 100644 index 0000000..28eae6f --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php new file mode 100644 index 0000000..a78bc3a --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php @@ -0,0 +1,34 @@ +_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); + } + + private function _createEvent(Swift_Transport $source, $command, $successCodes = array()) + { + return new Swift_Events_CommandEvent($source, $command, $successCodes); + } + + private function _createTransport() + { + return $this->getMockBuilder('Swift_Transport')->getMock(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php new file mode 100644 index 0000000..0cfe3ca --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php @@ -0,0 +1,32 @@ +_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()); + } + + private function _createEvent($source) + { + return new Swift_Events_EventObject($source); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php new file mode 100644 index 0000000..6f611ac --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php @@ -0,0 +1,38 @@ +_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); + } + + private function _createEvent(Swift_Transport $source, $response, $result) + { + return new Swift_Events_ResponseEvent($source, $response, $result); + } + + private function _createTransport() + { + return $this->getMockBuilder('Swift_Transport')->getMock(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php new file mode 100644 index 0000000..c4a6a7e --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php @@ -0,0 +1,97 @@ +_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()); + } + + private function _createEvent(Swift_Transport $source, + Swift_Mime_Message $message) + { + return new Swift_Events_SendEvent($source, $message); + } + + private function _createTransport() + { + return $this->getMockBuilder('Swift_Transport')->getMock(); + } + + private function _createMessage() + { + return $this->getMockBuilder('Swift_Mime_Message')->getMock(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php new file mode 100644 index 0000000..3f063ff --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php @@ -0,0 +1,142 @@ +_dispatcher = new Swift_Events_SimpleEventDispatcher(); + } + + public function testSendEventCanBeCreated() + { + $transport = $this->getMockBuilder('Swift_Transport')->getMock(); + $message = $this->getMockBuilder('Swift_Mime_Message')->getMock(); + $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->getMockBuilder('Swift_Transport')->getMock(); + $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->getMockBuilder('Swift_Transport')->getMock(); + $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->getMockBuilder('Swift_Transport')->getMock(); + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + $this->assertInstanceOf('Swift_Events_TransportChangeEvent', $evt); + $this->assertSame($transport, $evt->getSource()); + } + + public function testTransportExceptionEventCanBeCreated() + { + $transport = $this->getMockBuilder('Swift_Transport')->getMock(); + $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->getMockBuilder('Swift_Transport')->getMock(); + + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + + $listenerA = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock(); + $listenerB = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock(); + + $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->getMockBuilder('Swift_Transport')->getMock(); + $message = $this->getMockBuilder('Swift_Mime_Message')->getMock(); + + $evt = $this->_dispatcher->createSendEvent($transport, $message); + + $targetListener = $this->getMockBuilder('Swift_Events_SendListener')->getMock(); + $otherListener = $this->getMockBuilder('DummyListener')->getMock(); + + $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->getMockBuilder('Swift_Transport')->getMock(); + $message = $this->getMockBuilder('Swift_Mime_Message')->getMock(); + + $evt = $this->_dispatcher->createSendEvent($transport, $message); + + $listenerA = $this->getMockBuilder('Swift_Events_SendListener')->getMock(); + $listenerB = $this->getMockBuilder('Swift_Events_SendListener')->getMock(); + + $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); + } +} + +class DummyListener implements Swift_Events_EventListener +{ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php new file mode 100644 index 0000000..a260ccb --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php @@ -0,0 +1,30 @@ +_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); + } + + private function _createEvent(Swift_Transport $source) + { + return new Swift_Events_TransportChangeEvent($source); + } + + private function _createTransport() + { + return $this->getMockBuilder('Swift_Transport')->getMock(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php new file mode 100644 index 0000000..731dfad --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php @@ -0,0 +1,41 @@ +_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()' + ); + } + + private function _createEvent(Swift_Transport $transport, Swift_TransportException $ex) + { + return new Swift_Events_TransportExceptionEvent($transport, $ex); + } + + private function _createTransport() + { + return $this->getMockBuilder('Swift_Transport')->getMock(); + } + + private function _createException() + { + return new Swift_TransportException(''); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php new file mode 100644 index 0000000..f2ed5dd --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php @@ -0,0 +1,240 @@ +_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')); + } + + private function _createCache($is) + { + return new Swift_KeyCache_ArrayKeyCache($is); + } + + private function _createKeyCacheInputStream() + { + return $this->getMockBuilder('Swift_KeyCache_KeyCacheInputStream')->getMock(); + } + + private function _createOutputStream() + { + return $this->getMockBuilder('Swift_OutputByteStream')->getMock(); + } + + private function _createInputStream() + { + return $this->getMockBuilder('Swift_InputByteStream')->getMock(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php new file mode 100644 index 0000000..38fbc0d --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php @@ -0,0 +1,73 @@ +getMockBuilder('Swift_KeyCache')->getMock(); + $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->getMockBuilder('Swift_KeyCache')->getMock(); + $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->getMockBuilder('Swift_KeyCache')->getMock(); + $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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php new file mode 100644 index 0000000..ff0bce4 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php new file mode 100644 index 0000000..74951a7 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php @@ -0,0 +1,145 @@ +_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); + } + + 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 _createMailer(Swift_Transport $transport) + { + return new Swift_Mailer($transport); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php new file mode 100644 index 0000000..35a568c --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php @@ -0,0 +1,129 @@ +_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->assertEquals($id_1, $id_2, 'Message Ids differ'); + $id_2 = $message2->generateId(); + $this->assertNotEquals($id_1, $id_2, 'Message Ids are the same'); + } + + 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($arr1_value, $arr2_value, $arr1_clone_value); + } + } + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php new file mode 100644 index 0000000..3efe6ec --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php @@ -0,0 +1,1092 @@ +_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', 'text/plain' + ); + + $child2 = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/html\r\n". + "\r\n". + 'foobar', 'text/html' + ); + + $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 testOrderingEqualContentTypesMaintainsOriginalOrdering() + { + $firstChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n". + "\r\n". + 'PART 1', + 'text/plain' + ); + $secondChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n". + "\r\n". + 'PART 2', + '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($firstChild, $secondChild)); + + $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". + 'PART 1'. + "\r\n\r\n--xxx\r\n". + "Content-Type: text/plain\r\n". + "\r\n". + 'PART 2'. + "\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()) + ); + } + + 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->getMockBuilder('Swift_Mime_ContentEncoder')->getMock(); + $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; + }); + $os->shouldReceive('setReadPointer') + ->zeroOrMoreTimes(); + } + + return $os; + } + + protected function _createInputStream($stub = true) + { + return $this->getMockBuilder('Swift_InputByteStream')->getMock(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php new file mode 100644 index 0000000..2c1e581 --- /dev/null +++ b/core/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', '')) + ); + } + + 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; + }); + $file->shouldReceive('setReadPointer') + ->zeroOrMoreTimes(); + + return $file; + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php new file mode 100644 index 0000000..1571fce --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php new file mode 100644 index 0000000..ca44e11 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php @@ -0,0 +1,171 @@ +_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 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php new file mode 100644 index 0000000..7762bbe --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php @@ -0,0 +1,516 @@ +_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'); + } + + public function testTextIsPreWrapped() + { + $encoder = $this->createEncoder(); + + $input = str_repeat('a', 70)."\r\n". + str_repeat('a', 70)."\r\n". + str_repeat('a', 70); + + $os = new Swift_ByteStream_ArrayByteStream(); + $is = new Swift_ByteStream_ArrayByteStream(); + $is->write($input); + + $encoder->encodeByteStream($is, $os); + + $this->assertEquals( + $input, $os->read(PHP_INT_MAX) + ); + } + + private function _createCharacterStream($stub = false) + { + return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing(); + } + + private function createEncoder() + { + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $charStream = new Swift_CharacterStream_NgCharacterStream($factory, 'utf-8'); + + return new Swift_Mime_ContentEncoder_QpContentEncoder($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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php new file mode 100644 index 0000000..3a1fc51 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php @@ -0,0 +1,55 @@ +_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() + ); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php new file mode 100644 index 0000000..3580155 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php @@ -0,0 +1,13 @@ +assertEquals('B', $encoder->getName()); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php new file mode 100644 index 0000000..b5a10fe --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php @@ -0,0 +1,221 @@ +_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)); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php new file mode 100644 index 0000000..1822ea6 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php new file mode 100644 index 0000000..93b3f60 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php new file mode 100644 index 0000000..0713ff4 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php new file mode 100644 index 0000000..cd027cc --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php @@ -0,0 +1,398 @@ +_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 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php new file mode 100644 index 0000000..a9f35e9 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php new file mode 100644 index 0000000..2e1dc8c --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php new file mode 100644 index 0000000..738ac68 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php @@ -0,0 +1,231 @@ +_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->assertFalse($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) + ); + } + + //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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php new file mode 100644 index 0000000..6a87abf --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php @@ -0,0 +1,166 @@ +_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'); + } + + 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->getMockBuilder('Swift_Mime_HeaderEncoder')->getMock(); + } + + private function _createParamEncoder() + { + return $this->getMockBuilder('Swift_Encoder')->getMock(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php new file mode 100644 index 0000000..bed1c13 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php @@ -0,0 +1,737 @@ +_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', 0); + $this->assertFalse($set->has('Message-ID', 0)); + $this->assertTrue($set->has('Message-ID', 1)); + $this->assertTrue($set->has('Message-ID')); + $set->remove('Message-ID', 1); + $this->assertFalse($set->has('Message-ID', 1)); + $this->assertFalse($set->has('Message-ID')); + } + + 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'); + } + + private function _createSet($factory) + { + return new Swift_Mime_SimpleHeaderSet($factory); + } + + private function _createFactory() + { + return $this->getMockBuilder('Swift_Mime_HeaderFactory')->getMock(); + } + + private function _createHeader($name, $body = '') + { + $header = $this->getMockBuilder('Swift_Mime_Header')->getMock(); + $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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php new file mode 100644 index 0000000..e5d225c --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php @@ -0,0 +1,827 @@ +_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($message::PRIORITY_LOWEST); + } + + 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($message::PRIORITY_LOW); + } + + 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($message::PRIORITY_LOW) + ->setReadReceiptTo('a@b') + ->attach($child) + ->detach($child) + ); + } + + //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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php new file mode 100644 index 0000000..fa2a8d4 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php @@ -0,0 +1,9 @@ +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->getMockBuilder('Swift_Plugins_Sleeper')->getMock(); + $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); + } + } + + private function _createTransport() + { + return $this->getMockBuilder('Swift_Transport')->getMock(); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php new file mode 100644 index 0000000..869cfc8 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php @@ -0,0 +1,128 @@ +_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(23, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEquals(46, $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(23, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEquals(46, $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()); + } + + 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->getMockBuilder('Swift_Mime_Message')->getMock(); + $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; + } + + public function _write($is) + { + for ($i = 0; $i < $this->_bytes; ++$i) { + $is->write('x'); + } + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php new file mode 100644 index 0000000..8019dfb --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php @@ -0,0 +1,267 @@ +_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); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php new file mode 100644 index 0000000..bfe4cb7 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php @@ -0,0 +1,188 @@ +_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) { + } + } + + private function _createLogger() + { + return $this->getMockBuilder('Swift_Plugins_Logger')->getMock(); + } + + 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->getMockBuilder('Swift_Transport')->getMock(); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php new file mode 100644 index 0000000..880bb32 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php new file mode 100644 index 0000000..6134fe6 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php new file mode 100644 index 0000000..cbd368f --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php @@ -0,0 +1,101 @@ +_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); + } + + private function _createTransport() + { + return $this->getMockBuilder('Swift_Transport')->getMock(); + } + + 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->getMockBuilder('Swift_Plugins_Pop_Pop3Connection')->getMock(); + } + + public function _createPlugin($host, $port, $crypto = null) + { + return new Swift_Plugins_PopBeforeSmtpPlugin($host, $port, $crypto); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php new file mode 100644 index 0000000..bfd5669 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php @@ -0,0 +1,183 @@ +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')); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php new file mode 100644 index 0000000..5ba5d5c --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php @@ -0,0 +1,86 @@ +_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); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php new file mode 100644 index 0000000..20aae57 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php @@ -0,0 +1,64 @@ +_hitReporter = new Swift_Plugins_Reporters_HitReporter(); + $this->_message = $this->getMockBuilder('Swift_Mime_Message')->getMock(); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php new file mode 100644 index 0000000..fb0bc97 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php @@ -0,0 +1,54 @@ +_html = new Swift_Plugins_Reporters_HtmlReporter(); + $this->_message = $this->getMockBuilder('Swift_Mime_Message')->getMock(); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php new file mode 100644 index 0000000..309f506 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php @@ -0,0 +1,102 @@ +_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); + } + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php new file mode 100644 index 0000000..5eda223 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php @@ -0,0 +1,225 @@ +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); + } + + // SHA1 Signing + public function testSigningSHA1() + { + $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-sha1'); + $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='); + } + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php new file mode 100644 index 0000000..ce99bc6 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php new file mode 100644 index 0000000..5069c1f --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php @@ -0,0 +1,554 @@ +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 testSingedMessageExtraCerts() + { + $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/sign2.crt', $this->samplesDir.'smime/sign2.key', PKCS7_DETACHED, $this->samplesDir.'smime/intermediate.crt'); + $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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php new file mode 100644 index 0000000..c85bdc1 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php @@ -0,0 +1,129 @@ +_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)) + ); + } + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_ByteArrayReplacementFilter($search, $replace); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php new file mode 100644 index 0000000..c14d5dc --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php @@ -0,0 +1,36 @@ +_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' + ); + } + + private function _createFactory() + { + return new Swift_StreamFilters_StringReplacementFilterFactory(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php new file mode 100644 index 0000000..681e235 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php @@ -0,0 +1,59 @@ +_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"' + ); + } + + public function testShouldBufferReturnsFalseOnEmptyBuffer() + { + $filter = $this->_createFilter("\r\n", "\n"); + $this->assertFalse($filter->shouldBuffer('')); + } + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php new file mode 100644 index 0000000..81bda4f --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php @@ -0,0 +1,558 @@ +_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(); + } + + protected function _createEventDispatcher($stub = true) + { + return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php new file mode 100644 index 0000000..f49b489 --- /dev/null +++ b/core/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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php new file mode 100644 index 0000000..aca03a9 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php @@ -0,0 +1,64 @@ +_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 function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_CramMd5Authenticator(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php new file mode 100644 index 0000000..13f0209 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php @@ -0,0 +1,64 @@ +_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 function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_LoginAuthenticator(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php new file mode 100644 index 0000000..911d258 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php @@ -0,0 +1,213 @@ +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 testGetDomainAndUsernameWithoutDomain() + { + $username = 'user'; + + $login = $this->_getAuthenticator(); + list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username)); + + $this->assertEquals('', $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 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php new file mode 100644 index 0000000..73a9062 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php @@ -0,0 +1,67 @@ +_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 function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_PlainAuthenticator(); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php new file mode 100644 index 0000000..d52328a --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php @@ -0,0 +1,165 @@ +_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 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php new file mode 100644 index 0000000..166e160 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php @@ -0,0 +1,529 @@ +_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(1); + $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 Swift_Transport_EsmtpTransport($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 Swift_Transport_EsmtpTransport($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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php new file mode 100644 index 0000000..e6cca15 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php @@ -0,0 +1,297 @@ +_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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php new file mode 100644 index 0000000..e56e37f --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php @@ -0,0 +1,518 @@ +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 function _getTransport(array $transports) + { + $transport = new Swift_Transport_FailoverTransport(); + $transport->setTransports($transports); + + return $transport; + } + + private function _createPlugin() + { + return $this->getMockery('Swift_Events_EventListener'); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php new file mode 100644 index 0000000..f6bb819 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php @@ -0,0 +1,749 @@ +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 function _getTransport(array $transports) + { + $transport = new Swift_Transport_LoadBalancedTransport(); + $transport->setTransports($transports); + + return $transport; + } + + private function _createPlugin() + { + return $this->getMockery('Swift_Events_EventListener'); + } +} diff --git a/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php new file mode 100644 index 0000000..6672a3d --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php @@ -0,0 +1,533 @@ +_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessageWithRecipient($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->_createMessageWithRecipient($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->_createMessageWithRecipient($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->_createMessageWithRecipient($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 testTransportSettingUsingReturnPathForExtraParams() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessageWithRecipient($headers); + + $message->shouldReceive('getReturnPath') + ->zeroOrMoreTimes() + ->andReturn( + 'foo@bar' + ); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-ffoo@bar'); + + $transport->send($message); + } + + public function testTransportSettingEmptyExtraParams() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessageWithRecipient($headers); + + $message->shouldReceive('getReturnPath') + ->zeroOrMoreTimes() + ->andReturn(null); + $message->shouldReceive('getSender') + ->zeroOrMoreTimes() + ->andReturn(null); + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(null); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), null); + + $transport->send($message); + } + + public function testTransportSettingSettingExtraParamsWithF() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + $transport->setExtraParams('-x\'foo\' -f%s'); + + $headers = $this->_createHeaders(); + $message = $this->_createMessageWithRecipient($headers); + + $message->shouldReceive('getReturnPath') + ->zeroOrMoreTimes() + ->andReturn( + 'foo@bar' + ); + $message->shouldReceive('getSender') + ->zeroOrMoreTimes() + ->andReturn(null); + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(null); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-x\'foo\' -ffoo@bar'); + + $transport->send($message); + } + + public function testTransportSettingSettingExtraParamsWithoutF() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + $transport->setExtraParams('-x\'foo\''); + + $headers = $this->_createHeaders(); + $message = $this->_createMessageWithRecipient($headers); + + $message->shouldReceive('getReturnPath') + ->zeroOrMoreTimes() + ->andReturn( + 'foo@bar' + ); + $message->shouldReceive('getSender') + ->zeroOrMoreTimes() + ->andReturn(null); + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(null); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-x\'foo\''); + + $transport->send($message); + } + + public function testTransportSettingInvalidFromEmail() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessageWithRecipient($headers); + + $message->shouldReceive('getReturnPath') + ->zeroOrMoreTimes() + ->andReturn( + '"attacker\" -oQ/tmp/ -X/var/www/cache/phpcode.php "@email.com' + ); + $message->shouldReceive('getSender') + ->zeroOrMoreTimes() + ->andReturn(null); + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(null); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), null); + + $transport->send($message); + } + + public function testTransportUsesHeadersFromMessage() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessageWithRecipient($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->_createMessageWithRecipient($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->_createMessageWithRecipient($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->_createMessageWithRecipient($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->_createMessageWithRecipient($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); + } + + public function testMessageHeadersOnlyHavePHPEolsDuringSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subject = $this->_createHeader(); + $subject->shouldReceive('getFieldBody')->andReturn("Foo\r\nBar"); + + $headers = $this->_createHeaders(array( + 'Subject' => $subject, + )); + $message = $this->_createMessageWithRecipient($headers); + $message->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn( + "From: Foo\r\n\r\n". + "\r\n". + "This\r\n". + 'body' + ); + + if ("\r\n" != PHP_EOL) { + $expectedHeaders = "From: Foo\n\n"; + $expectedSubject = "Foo\nBar"; + $expectedBody = "This\nbody"; + } else { + $expectedHeaders = "From: Foo\r\n\r\n"; + $expectedSubject = "Foo\r\nBar"; + $expectedBody = "This\r\nbody"; + } + + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), $expectedSubject, $expectedBody, $expectedHeaders, \Mockery::any()); + + $transport->send($message); + } + + /** + * @expectedException \Swift_TransportException + * @expectedExceptionMessage Cannot send message without a recipient + */ + public function testExceptionWhenNoRecipients() + { + $invoker = $this->_createInvoker(); + $invoker->shouldReceive('mail'); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $transport->send($message); + } + + public function noExceptionWhenRecipientsExistProvider() + { + return array( + array('To'), + array('Cc'), + array('Bcc'), + ); + } + + /** + * @dataProvider noExceptionWhenRecipientsExistProvider + * + * @param string $header + */ + public function testNoExceptionWhenRecipientsExist($header) + { + $invoker = $this->_createInvoker(); + $invoker->shouldReceive('mail'); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + $message->shouldReceive(sprintf('get%s', $header))->andReturn(array('foo@bar' => 'Foo')); + + $transport->send($message); + } + + 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 _createMessageWithRecipient($headers, $recipient = array('foo@bar' => 'Foo')) + { + $message = $this->_createMessage($headers); + $message->shouldReceive('getTo')->andReturn($recipient); + + 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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php new file mode 100644 index 0000000..9040f9e --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php @@ -0,0 +1,151 @@ +_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/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php new file mode 100644 index 0000000..5109b56 --- /dev/null +++ b/core/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php @@ -0,0 +1,43 @@ +_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')); + } + + private function _createBuffer($replacementFactory) + { + return new Swift_Transport_StreamBuffer($replacementFactory); + } + + private function _createFactory() + { + return $this->getMockBuilder('Swift_ReplacementFilterFactory')->getMock(); + } + + public function _createFilter() + { + return $this->getMockBuilder('Swift_StreamFilter')->getMock(); + } +} diff --git a/core/vendor/symfony/console/.gitignore b/core/vendor/symfony/console/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/core/vendor/symfony/console/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/core/vendor/symfony/console/Application.php b/core/vendor/symfony/console/Application.php new file mode 100644 index 0000000..5ad89c1 --- /dev/null +++ b/core/vendor/symfony/console/Application.php @@ -0,0 +1,1145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Helper\DialogHelper; +use Symfony\Component\Console\Helper\ProgressHelper; +use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Debug\Exception\FatalThrowableError; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * An Application is the container for a collection of commands. + * + * It is the main entry point of a Console application. + * + * This class is optimized for a standard CLI environment. + * + * Usage: + * + * $app = new Application('myapp', '1.0 (stable)'); + * $app->add(new SimpleCommand()); + * $app->run(); + * + * @author Fabien Potencier + */ +class Application +{ + private $commands = array(); + private $wantHelps = false; + private $runningCommand; + private $name; + private $version; + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $helperSet; + private $dispatcher; + private $terminalDimensions; + private $defaultCommand; + private $initialized; + + /** + * @param string $name The name of the application + * @param string $version The version of the application + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->defaultCommand = 'list'; + } + + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + * + * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. + */ + public function run(InputInterface $input = null, OutputInterface $output = null) + { + if (null === $input) { + $input = new ArgvInput(); + } + + if (null === $output) { + $output = new ConsoleOutput(); + } + + $this->configureIO($input, $output); + + try { + $e = null; + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + } + + if (null !== $e) { + if (!$this->catchExceptions) { + throw $e; + } + + if ($output instanceof ConsoleOutputInterface) { + $this->renderException($e, $output->getErrorOutput()); + } else { + $this->renderException($e, $output); + } + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--version', '-V'))) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + if (true === $input->hasParameterOption(array('--help', '-h'))) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(array('command' => 'help')); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $definition = $this->getDefinition(); + $definition->setArguments(array_merge( + $definition->getArguments(), + array( + 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), + ) + )); + } + + $this->runningCommand = null; + // the command name MUST be the first element of the input + $command = $this->find($name); + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + * + * @return HelperSet The HelperSet instance associated with this command + */ + public function getHelperSet() + { + if (!$this->helperSet) { + $this->helperSet = $this->getDefaultHelperSet(); + } + + return $this->helperSet; + } + + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + * + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + if (!$this->definition) { + $this->definition = $this->getDefaultInputDefinition(); + } + + return $this->definition; + } + + /** + * Gets the help message. + * + * @return string A help message + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * Sets whether to catch exceptions or not during commands execution. + * + * @param bool $boolean Whether to catch exceptions or not during commands execution + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param bool $boolean Whether to automatically exit after a command execution or not + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + } + + /** + * Gets the name of the application. + * + * @return string The application name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the application name. + * + * @param string $name The application name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the application version. + * + * @return string The application version + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the application version. + * + * @param string $version The application version + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + * + * @return string The long application version + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName()) { + if ('UNKNOWN' !== $this->getVersion()) { + return sprintf('%s version %s', $this->getName(), $this->getVersion()); + } + + return sprintf('%s', $this->getName()); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + * + * @param string $name The command name + * + * @return Command The newly created command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * If a Command is not enabled it will not be added. + * + * @param Command[] $commands An array of commands + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) { + $this->add($command); + } + } + + /** + * Adds a command object. + * + * If a command with the same name already exists, it will be overridden. + * If the command is not enabled it will not be added. + * + * @return Command|null The registered command if enabled or null + */ + public function add(Command $command) + { + $this->init(); + + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return; + } + + if (null === $command->getDefinition()) { + throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * Returns a registered command by name or alias. + * + * @param string $name The command name or alias + * + * @return Command A Command object + * + * @throws \InvalidArgumentException When given command name does not exist + */ + public function get($name) + { + $this->init(); + + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * Returns true if the command exists, false otherwise. + * + * @param string $name The command name or alias + * + * @return bool true if the command exists, false otherwise + */ + public function has($name) + { + $this->init(); + + return isset($this->commands[$name]); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not return the global namespace which always exists. + * + * @return string[] An array of namespaces + */ + public function getNamespaces() + { + $namespaces = array(); + foreach ($this->all() as $command) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @param string $namespace A namespace or abbreviation to search for + * + * @return string A registered namespace + * + * @throws \InvalidArgumentException When namespace is incorrect or ambiguous + */ + public function findNamespace($namespace) + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); + $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + $exact = in_array($namespace, $namespaces, true); + if (count($namespaces) > 1 && !$exact) { + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * Finds a command by name or alias. + * + * Contrary to get, this command tries to find the best + * match if you give it an abbreviation of a name or alias. + * + * @param string $name A command name or a command alias + * + * @return Command A Command instance + * + * @throws \InvalidArgumentException When command name is incorrect or ambiguous + */ + public function find($name) + { + $this->init(); + $aliases = array(); + $allCommands = array_keys($this->commands); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); + $commands = preg_grep('{^'.$expr.'}', $allCommands); + + if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + // check if a namespace exists and contains commands + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + // filter out aliases for commands which are already on the list + if (count($commands) > 1) { + $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) { + $commandName = $commandList[$nameOrAlias]->getName(); + $aliases[$nameOrAlias] = $commandName; + + return $commandName === $nameOrAlias || !in_array($commandName, $commands); + }); + } + + $exact = in_array($name, $commands, true) || isset($aliases[$name]); + if (!$exact && count($commands) > 1) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); + } + + return $this->get($exact ? $name : reset($commands)); + } + + /** + * Gets the commands (registered in the given namespace if provided). + * + * The array keys are the full names and the values the command instances. + * + * @param string $namespace A namespace name + * + * @return Command[] An array of Command instances + */ + public function all($namespace = null) + { + $this->init(); + + if (null === $namespace) { + return $this->commands; + } + + $commands = array(); + foreach ($this->commands as $name => $command) { + if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * Returns an array of possible abbreviations given a set of names. + * + * @param array $names An array of names + * + * @return array An array of abbreviations + */ + public static function getAbbreviations($names) + { + $abbrevs = array(); + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * Returns a text representation of the Application. + * + * @param string $namespace An optional namespace name + * @param bool $raw Whether to return raw command list + * + * @return string A string representing the Application + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asText($namespace = null, $raw = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw); + $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the Application. + * + * @param string $namespace An optional namespace name + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the Application + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($namespace = null, $asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getApplicationDocument($this, $namespace); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this, array('namespace' => $namespace)); + + return $output->fetch(); + } + + /** + * Renders a caught exception. + */ + public function renderException($e, $output) + { + $output->writeln(''); + + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = Helper::strlen($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = array(); + foreach (preg_split('/\r?\n/', trim($e->getMessage())) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = Helper::strlen($line) + 4; + $lines[] = array($line, $lineLength); + + $len = max($lineLength, $len); + } + } + + $messages = array(); + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + + $output->writeln($messages); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('Exception trace:'); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, array( + 'function' => '', + 'file' => null !== $e->getFile() ? $e->getFile() : 'n/a', + 'line' => null !== $e->getLine() ? $e->getLine() : 'n/a', + 'args' => array(), + )); + + for ($i = 0, $count = count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line)); + } + + $output->writeln(''); + } + } while ($e = $e->getPrevious()); + + if (null !== $this->runningCommand) { + $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName()))); + $output->writeln(''); + } + } + + /** + * Tries to figure out the terminal width in which this application runs. + * + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * Tries to figure out the terminal height in which this application runs. + * + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * Tries to figure out the terminal dimensions based on the current environment. + * + * @return array Array containing width and height + */ + public function getTerminalDimensions() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + // extract [w, H] from "wxh (WxH)" + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return array((int) $matches[1], (int) $matches[2]); + } + // extract [w, h] from "wxh" + if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) { + return array((int) $matches[1], (int) $matches[2]); + } + } + + if ($sttyString = $this->getSttyColumns()) { + // extract [w, h] from "rows h; columns w;" + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return array((int) $matches[2], (int) $matches[1]); + } + // extract [w, h] from "; h rows; w columns" + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return array((int) $matches[2], (int) $matches[1]); + } + } + + return array(null, null); + } + + /** + * Sets terminal dimensions. + * + * Can be useful to force terminal dimensions for functional tests. + * + * @param int $width The width + * @param int $height The height + * + * @return $this + */ + public function setTerminalDimensions($width, $height) + { + $this->terminalDimensions = array($width, $height); + + return $this; + } + + /** + * Configures the input and output instances based on the user arguments and options. + */ + protected function configureIO(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--ansi'))) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(array('--no-ansi'))) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) { + $input->setInteractive(false); + } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) { + $inputStream = $this->getHelperSet()->get('question')->getInputStream(); + if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { + $input->setInteractive(false); + } + } + + if (true === $input->hasParameterOption(array('--quiet', '-q'))) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $input->setInteractive(false); + } else { + if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || 3 === $input->getParameterOption('--verbose')) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || 2 === $input->getParameterOption('--verbose')) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + } + } + } + + /** + * Runs the current command. + * + * If an event dispatcher has been attached to the application, + * events are also dispatched during the life-cycle of the command. + * + * @return int 0 if everything went fine, or an error code + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $e = null; + + try { + $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + + if ($event->commandShouldRun()) { + $exitCode = $command->run($input, $output); + } else { + $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + } + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + if (null !== $e) { + $x = $e instanceof \Exception ? $e : new FatalThrowableError($e); + $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode()); + $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); + + if ($x !== $event->getException()) { + $e = $event->getException(); + } + $exitCode = $e->getCode(); + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + + if (null !== $e) { + throw $e; + } + + return $event->getExitCode(); + } + + /** + * Gets the name of the command based on input. + * + * @return string The command name + */ + protected function getCommandName(InputInterface $input) + { + return $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition(array( + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + )); + } + + /** + * Gets the default commands that should always be available. + * + * @return Command[] An array of default Command instances + */ + protected function getDefaultCommands() + { + return array(new HelpCommand(), new ListCommand()); + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + return new HelperSet(array( + new FormatterHelper(), + new DialogHelper(false), + new ProgressHelper(false), + new TableHelper(false), + new DebugFormatterHelper(), + new ProcessHelper(), + new QuestionHelper(), + )); + } + + /** + * Runs and parses stty -a if it's available, suppressing any error output. + * + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + } + + /** + * Runs and parses mode CON if it's available, suppressing any error output. + * + * @return string|null x or null if it could not be parsed + */ + private function getConsoleMode() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2].'x'.$matches[1]; + } + } + } + + /** + * Returns abbreviated suggestions in string format. + * + * @param array $abbrevs Abbreviated suggestions to convert + * + * @return string A formatted string of abbreviated suggestions + */ + private function getAbbreviationSuggestions($abbrevs) + { + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); + } + + /** + * Returns the namespace part of the command name. + * + * This method is not part of public API and should not be used directly. + * + * @param string $name The full name of the command + * @param string $limit The maximum number of parts of the namespace + * + * @return string The namespace of the command + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * Finds alternative of $name among $collection, + * if nothing is found in $collection, try in $abbrevs. + * + * @param string $name The string + * @param iterable $collection The collection + * + * @return string[] A sorted array of similar string + */ + private function findAlternatives($name, $collection) + { + $threshold = 1e3; + $alternatives = array(); + + $collectionParts = array(); + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @param string $commandName The Command name + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + } + + private function splitStringByWidth($string, $width) + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = array(); + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char, 'utf8') <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + + $lines[] = count($lines) ? str_pad($line, $width) : $line; + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + /** + * Returns all namespaces of the command name. + * + * @param string $name The full name of the command + * + * @return string[] The namespaces of the command + */ + private function extractAllNamespaces($name) + { + // -1 as third argument is needed to skip the command short name when exploding + $parts = explode(':', $name, -1); + $namespaces = array(); + + foreach ($parts as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces).':'.$part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + + private function init() + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } +} diff --git a/core/vendor/symfony/console/CHANGELOG.md b/core/vendor/symfony/console/CHANGELOG.md new file mode 100644 index 0000000..07254c6 --- /dev/null +++ b/core/vendor/symfony/console/CHANGELOG.md @@ -0,0 +1,62 @@ +CHANGELOG +========= + +2.6.0 +----- + + * added a Process helper + * added a DebugFormatter helper + +2.5.0 +----- + + * deprecated the dialog helper (use the question helper instead) + * deprecated TableHelper in favor of Table + * deprecated ProgressHelper in favor of ProgressBar + * added ConsoleLogger + * added a question helper + * added a way to set the process name of a command + * added a way to set a default command instead of `ListCommand` + +2.4.0 +----- + + * added a way to force terminal dimensions + * added a convenient method to detect verbosity level + * [BC BREAK] made descriptors use output instead of returning a string + +2.3.0 +----- + + * added multiselect support to the select dialog helper + * added Table Helper for tabular data rendering + * added support for events in `Application` + * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` + * added a way to set the progress bar progress via the `setCurrent` method + * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` + * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG + +2.2.0 +----- + + * added support for colorization on Windows via ConEmu + * add a method to Dialog Helper to ask for a question and hide the response + * added support for interactive selections in console (DialogHelper::select()) + * added support for autocompletion as you type in Dialog Helper + +2.1.0 +----- + + * added ConsoleOutputInterface + * added the possibility to disable a command (Command::isEnabled()) + * added suggestions when a command does not exist + * added a --raw option to the list command + * added support for STDERR in the console output class (errors are now sent + to STDERR) + * made the defaults (helper set, commands, input definition) in Application + more easily customizable + * added support for the shell even if readline is not available + * added support for process isolation in Symfony shell via + `--process-isolation` switch + * added support for `--`, which disables options parsing after that point + (tokens will be parsed as arguments) diff --git a/core/vendor/symfony/console/Command/Command.php b/core/vendor/symfony/console/Command/Command.php new file mode 100644 index 0000000..3fe12e8 --- /dev/null +++ b/core/vendor/symfony/console/Command/Command.php @@ -0,0 +1,655 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Helper\HelperSet; + +/** + * Base class for all commands. + * + * @author Fabien Potencier + */ +class Command +{ + private $application; + private $name; + private $processTitle; + private $aliases = array(); + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $applicationDefinitionMerged = false; + private $applicationDefinitionMergedWithArgs = false; + private $code; + private $synopsis = array(); + private $usages = array(); + private $helperSet; + + /** + * @param string|null $name The name of the command; passing null means it must be set in configure() + * + * @throws \LogicException When the command name is empty + */ + public function __construct($name = null) + { + $this->definition = new InputDefinition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + public function setApplication(Application $application = null) + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + * + * @return Application An Application instance + */ + public function getApplication() + { + return $this->application; + } + + /** + * Checks whether the command is enabled or not in the current environment. + * + * Override this to check for x or y and return false if the command can not + * run properly under the current conditions. + * + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * Configures the current command. + */ + protected function configure() + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @return null|int null or 0 if everything went fine, or an error code + * + * @throws \LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + throw new \LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * Interacts with the user. + * + * This method is executed before the InputDefinition is validated. + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments. + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Initializes the command just after the input has been validated. + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and options. + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + /** + * Runs the command. + * + * The code to execute is either defined directly with the + * setCode() method or by overriding the execute() method + * in a sub-class. + * + * @return int The command exit code + * + * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}. + * + * @see setCode() + * @see execute() + */ + public function run(InputInterface $input, OutputInterface $output) + { + // force the creation of the synopsis before the merge with the app definition + $this->getSynopsis(true); + $this->getSynopsis(false); + + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->definition); + } catch (\Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (function_exists('cli_set_process_title')) { + if (!@cli_set_process_title($this->processTitle)) { + if ('Darwin' === PHP_OS) { + $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); + } else { + cli_set_process_title($this->processTitle); + } + } + } elseif (function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + // The command name argument is often omitted when a command is executed directly with its run() method. + // It would fail the validation if we didn't make sure the command argument is present, + // since it's required by the application. + if ($input->hasArgument('command') && null === $input->getArgument('command')) { + $input->setArgument('command', $this->getName()); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * Sets the code to execute when running this command. + * + * If this method is used, it overrides the code defined + * in the execute() method. + * + * @param callable $code A callable(InputInterface $input, OutputInterface $output) + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @see execute() + */ + public function setCode($code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + $this->code = $code; + + return $this; + } + + /** + * Merges the application definition with the command definition. + * + * This method is not part of public API and should not be used directly. + * + * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + */ + public function mergeApplicationDefinition($mergeArgs = true) + { + if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { + return; + } + + $this->definition->addOptions($this->application->getDefinition()->getOptions()); + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->application->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->applicationDefinitionMerged = true; + if ($mergeArgs) { + $this->applicationDefinitionMergedWithArgs = true; + } + } + + /** + * Sets an array of argument and option instances. + * + * @param array|InputDefinition $definition An array of argument and option instances or a definition instance + * + * @return $this + */ + public function setDefinition($definition) + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->applicationDefinitionMerged = false; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + * + * @return InputDefinition An InputDefinition instance + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * Gets the InputDefinition to be used to create XML and Text representations of this Command. + * + * Can be overridden to provide the original command representation when it would otherwise + * be changed by merging with the application InputDefinition. + * + * This method is not part of public API and should not be used directly. + * + * @return InputDefinition An InputDefinition instance + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * Adds an argument. + * + * @param string $name The argument name + * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param string $description A description text + * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) + * + * @return $this + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); + + return $this; + } + + /** + * Adds an option. + * + * @param string $name The option name + * @param string $shortcut The shortcut (can be null) + * @param int $mode The option mode: One of the InputOption::VALUE_* constants + * @param string $description A description text + * @param mixed $default The default value (must be null for InputOption::VALUE_NONE) + * + * @return $this + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * Sets the name of the command. + * + * This method can set both the namespace and the name if + * you separate them by a colon (:) + * + * $command->setName('foo:bar'); + * + * @param string $name The command name + * + * @return $this + * + * @throws \InvalidArgumentException When the name is invalid + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Sets the process title of the command. + * + * This feature should be used only when creating a long process command, + * like a daemon. + * + * PHP 5.5+ or the proctitle PECL library is required + * + * @param string $title The process title + * + * @return $this + */ + public function setProcessTitle($title) + { + $this->processTitle = $title; + + return $this; + } + + /** + * Returns the command name. + * + * @return string The command name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the description for the command. + * + * @param string $description The description for the command + * + * @return $this + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + * + * @return string The description for the command + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @param string $help The help for the command + * + * @return $this + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + * + * @return string The help for the command + */ + public function getHelp() + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + * + * @return string The processed help for the command + */ + public function getProcessedHelp() + { + $name = $this->name; + + $placeholders = array( + '%command.name%', + '%command.full_name%', + ); + $replacements = array( + $name, + $_SERVER['PHP_SELF'].' '.$name, + ); + + return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); + } + + /** + * Sets the aliases for the command. + * + * @param string[] $aliases An array of aliases for the command + * + * @return $this + * + * @throws \InvalidArgumentException When an alias is invalid + */ + public function setAliases($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * Returns the aliases for the command. + * + * @return array An array of aliases for the command + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @param bool $short Whether to show the short version of the synopsis (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * Add a command usage example. + * + * @param string $usage The usage, it'll be prefixed with the command name + * + * @return $this + */ + public function addUsage($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + * + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * Gets a helper instance by name. + * + * @param string $name The helper name + * + * @return mixed The helper value + * + * @throws \LogicException if no HelperSet is defined + * @throws \InvalidArgumentException if the helper is not defined + */ + public function getHelper($name) + { + if (null === $this->helperSet) { + throw new \LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); + } + + return $this->helperSet->get($name); + } + + /** + * Returns a text representation of the command. + * + * @return string A string representing the command + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $descriptor->describe($output, $this, array('raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the command. + * + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the command + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getCommandDocument($this); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this); + + return $output->fetch(); + } + + /** + * Validates a command name. + * + * It must be non-empty and parts can optionally be separated by ":". + * + * @param string $name + * + * @throws \InvalidArgumentException When the name is invalid + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/core/vendor/symfony/console/Command/HelpCommand.php b/core/vendor/symfony/console/Command/HelpCommand.php new file mode 100644 index 0000000..27e23e1 --- /dev/null +++ b/core/vendor/symfony/console/Command/HelpCommand.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * HelpCommand displays the help for a given command. + * + * @author Fabien Potencier + */ +class HelpCommand extends Command +{ + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition(array( + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + )) + ->setDescription('Displays help for a command') + ->setHelp(<<<'EOF' +The %command.name% command displays help for a given command: + + php %command.full_name% list + +You can also output the help in other formats by using the --format option: + + php %command.full_name% --format=xml list + +To display the list of available commands, please use the list command. +EOF + ) + ; + } + + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if (null === $this->command) { + $this->command = $this->getApplication()->find($input->getArgument('command_name')); + } + + if ($input->getOption('xml')) { + @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); + + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + )); + + $this->command = null; + } +} diff --git a/core/vendor/symfony/console/Command/ListCommand.php b/core/vendor/symfony/console/Command/ListCommand.php new file mode 100644 index 0000000..5e1b926 --- /dev/null +++ b/core/vendor/symfony/console/Command/ListCommand.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputDefinition; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier + */ +class ListCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('list') + ->setDefinition($this->createDefinition()) + ->setDescription('Lists commands') + ->setHelp(<<<'EOF' +The %command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +You can also output the information in other formats by using the --format option: + + php %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($input->getOption('xml')) { + @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); + + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + )); + } + + /** + * {@inheritdoc} + */ + private function createDefinition() + { + return new InputDefinition(array( + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + )); + } +} diff --git a/core/vendor/symfony/console/ConsoleEvents.php b/core/vendor/symfony/console/ConsoleEvents.php new file mode 100644 index 0000000..6dae6ce --- /dev/null +++ b/core/vendor/symfony/console/ConsoleEvents.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +/** + * Contains all events dispatched by an Application. + * + * @author Francesco Levorato + */ +final class ConsoleEvents +{ + /** + * The COMMAND event allows you to attach listeners before any command is + * executed by the console. It also allows you to modify the command, input and output + * before they are handled to the command. + * + * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent + * instance. + * + * @Event + */ + const COMMAND = 'console.command'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent + * instance. + * + * @Event + */ + const TERMINATE = 'console.terminate'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears. + * + * This event allows you to deal with the exception or + * to modify the thrown exception. The event listener method receives + * a Symfony\Component\Console\Event\ConsoleExceptionEvent + * instance. + * + * @Event + */ + const EXCEPTION = 'console.exception'; +} diff --git a/core/vendor/symfony/console/Descriptor/ApplicationDescription.php b/core/vendor/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 0000000..2100b94 --- /dev/null +++ b/core/vendor/symfony/console/Descriptor/ApplicationDescription.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; + +/** + * @author Jean-François Simon + * + * @internal + */ +class ApplicationDescription +{ + const GLOBAL_NAMESPACE = '_global'; + + private $application; + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + public function __construct(Application $application, $namespace = null) + { + $this->application = $application; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces() + { + if (null === $this->namespaces) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectApplication(); + } + + return $this->commands; + } + + /** + * @param string $name + * + * @return Command + * + * @throws \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectApplication() + { + $this->commands = array(); + $this->namespaces = array(); + + $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = array(); + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); + } + } + + /** + * @return array + */ + private function sortCommands(array $commands) + { + $namespacedCommands = array(); + $globalCommands = array(); + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (!$key) { + $globalCommands['_global'][$name] = $command; + } else { + $namespacedCommands[$key][$name] = $command; + } + } + ksort($namespacedCommands); + $namespacedCommands = array_merge($globalCommands, $namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/core/vendor/symfony/console/Descriptor/Descriptor.php b/core/vendor/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 0000000..469bbd7 --- /dev/null +++ b/core/vendor/symfony/console/Descriptor/Descriptor.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jean-François Simon + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + /** + * @var OutputInterface + */ + private $output; + + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Application: + $this->describeApplication($object, $options); + break; + default: + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * Writes content to output. + * + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Describes an InputArgument instance. + * + * @return string|mixed + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); + + /** + * Describes an InputOption instance. + * + * @return string|mixed + */ + abstract protected function describeInputOption(InputOption $option, array $options = array()); + + /** + * Describes an InputDefinition instance. + * + * @return string|mixed + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); + + /** + * Describes a Command instance. + * + * @return string|mixed + */ + abstract protected function describeCommand(Command $command, array $options = array()); + + /** + * Describes an Application instance. + * + * @return string|mixed + */ + abstract protected function describeApplication(Application $application, array $options = array()); +} diff --git a/core/vendor/symfony/console/Descriptor/DescriptorInterface.php b/core/vendor/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 0000000..5d3339a --- /dev/null +++ b/core/vendor/symfony/console/Descriptor/DescriptorInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + /** + * Describes an object if supported. + * + * @param OutputInterface $output + * @param object $object + * @param array $options + */ + public function describe(OutputInterface $output, $object, array $options = array()); +} diff --git a/core/vendor/symfony/console/Descriptor/JsonDescriptor.php b/core/vendor/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000..0a22274 --- /dev/null +++ b/core/vendor/symfony/console/Descriptor/JsonDescriptor.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * JSON descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->writeData($this->getInputOptionData($option), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $this->writeData($this->getCommandData($command), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + $commands = array(); + + foreach ($description->getCommands() as $command) { + $commands[] = $this->getCommandData($command); + } + + $data = $describedNamespace + ? array('commands' => $commands, 'namespace' => $describedNamespace) + : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces())); + + $this->writeData($data, $options); + } + + /** + * Writes data as json. + * + * @return array|string + */ + private function writeData(array $data, array $options) + { + $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); + } + + /** + * @return array + */ + private function getInputArgumentData(InputArgument $argument) + { + return array( + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), + 'default' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), + ); + } + + /** + * @return array + */ + private function getInputOptionData(InputOption $option) + { + return array( + 'name' => '--'.$option->getName(), + 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', + 'accept_value' => $option->acceptValue(), + 'is_value_required' => $option->isValueRequired(), + 'is_multiple' => $option->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), + 'default' => INF === $option->getDefault() ? 'INF' : $option->getDefault(), + ); + } + + /** + * @return array + */ + private function getInputDefinitionData(InputDefinition $definition) + { + $inputArguments = array(); + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->getInputArgumentData($argument); + } + + $inputOptions = array(); + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->getInputOptionData($option); + } + + return array('arguments' => $inputArguments, 'options' => $inputOptions); + } + + /** + * @return array + */ + private function getCommandData(Command $command) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + return array( + 'name' => $command->getName(), + 'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()), + 'description' => $command->getDescription(), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), + ); + } +} diff --git a/core/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/core/vendor/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000..76399ca --- /dev/null +++ b/core/vendor/symfony/console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->write( + '**'.$argument->getName().':**'."\n\n" + .'* Name: '.($argument->getName() ?: '')."\n" + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $argument->getDescription() ?: '')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->write( + '**'.$option->getName().':**'."\n\n" + .'* Name: `--'.$option->getName().'`'."\n" + .'* Shortcut: '.($option->getShortcut() ? '`-'.str_replace('|', '|-', $option->getShortcut()).'`' : '')."\n" + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $option->getDescription() ?: '')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + if ($showArguments = count($definition->getArguments()) > 0) { + $this->write('### Arguments:'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->write($this->describeInputArgument($argument)); + } + } + + if (count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options:'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + $this->write($this->describeInputOption($option)); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $this->write( + $command->getName()."\n" + .str_repeat('-', Helper::strlen($command->getName()))."\n\n" + .'* Description: '.($command->getDescription() ?: '')."\n" + .'* Usage:'."\n\n" + .array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) { + return $carry.' * `'.$usage.'`'."\n"; + }) + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + if ($command->getNativeDefinition()) { + $this->write("\n\n"); + $this->describeInputDefinition($command->getNativeDefinition()); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + + $this->write($application->getName()."\n".str_repeat('=', Helper::strlen($application->getName()))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->write("\n\n"); + $this->write('**'.$namespace['id'].':**'); + } + + $this->write("\n\n"); + $this->write(implode("\n", array_map(function ($commandName) { + return '* '.$commandName; + }, $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + $this->write($this->describeCommand($command)); + } + } +} diff --git a/core/vendor/symfony/console/Descriptor/TextDescriptor.php b/core/vendor/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000..1d64324 --- /dev/null +++ b/core/vendor/symfony/console/Descriptor/TextDescriptor.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Text descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()); + + $this->writeText(sprintf(' %s %s%s%s', + $argument->getName(), + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), + $default + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '='.strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '['.$value.']'; + } + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option)); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf('--%s%s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - Helper::strlen($synopsis); + + $this->writeText(sprintf(' %s %s%s%s%s', + $synopsis, + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, Helper::strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth))); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = array(); + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeApplicationDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' '.OutputFormatter::escape($usage), $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' '.str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $application->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $width = $this->getColumnWidth($description->getCommands()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' '.$namespace['id'].'', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - Helper::strlen($name); + $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = array()) + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } + + /** + * Formats input option/argument default value. + * + * @param mixed $default + * + * @return string + */ + private function formatDefaultValue($default) + { + if (INF === $default) { + return 'INF'; + } + + if (is_string($default)) { + $default = OutputFormatter::escape($default); + } elseif (is_array($default)) { + foreach ($default as $key => $value) { + if (is_string($value)) { + $default[$key] = OutputFormatter::escape($value); + } + } + } + + if (\PHP_VERSION_ID < 50400) { + return str_replace(array('\/', '\\\\'), array('/', '\\'), json_encode($default)); + } + + return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + } + + /** + * @param Command[] $commands + * + * @return int + */ + private function getColumnWidth(array $commands) + { + $widths = array(); + + foreach ($commands as $command) { + $widths[] = Helper::strlen($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::strlen($alias); + } + } + + return max($widths) + 2; + } + + /** + * @param InputOption[] $options + * + * @return int + */ + private function calculateTotalWidthForOptions(array $options) + { + $totalWidth = 0; + foreach ($options as $option) { + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); + + if ($option->acceptValue()) { + $valueLength = 1 + Helper::strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/core/vendor/symfony/console/Descriptor/XmlDescriptor.php b/core/vendor/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000..c42fa42 --- /dev/null +++ b/core/vendor/symfony/console/Descriptor/XmlDescriptor.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * XML descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + /** + * @return \DOMDocument + */ + public function getInputDefinitionDocument(InputDefinition $definition) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($definitionXML = $dom->createElement('definition')); + + $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); + foreach ($definition->getArguments() as $argument) { + $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } + + return $dom; + } + + /** + * @return \DOMDocument + */ + public function getCommandDocument(Command $command) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + + $commandXML->appendChild($usagesXML = $dom->createElement('usages')); + + foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + + return $dom; + } + + /** + * @param Application $application + * @param string|null $namespace + * + * @return \DOMDocument + */ + public function getApplicationDocument(Application $application, $namespace = null) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + + if ('UNKNOWN' !== $application->getName()) { + $rootXml->setAttribute('name', $application->getName()); + if ('UNKNOWN' !== $application->getVersion()) { + $rootXml->setAttribute('version', $application->getVersion()); + } + } + + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $description = new ApplicationDescription($application, $namespace); + + if ($namespace) { + $commandsXML->setAttribute('namespace', $namespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->getCommandDocument($command)); + } + + if (!$namespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespaceDescription) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); + + foreach ($namespaceDescription['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $dom; + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $this->writeDocument($this->getCommandDocument($command)); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); + } + + /** + * Appends document children to parent node. + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Writes DOM document. + * + * @return \DOMDocument|string + */ + private function writeDocument(\DOMDocument $dom) + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + /** + * @return \DOMDocument + */ + private function getInputArgumentDocument(InputArgument $argument) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('argument')); + $objectXML->setAttribute('name', $argument->getName()); + $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); + $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); + + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : (is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array())); + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + + return $dom; + } + + /** + * @return \DOMDocument + */ + private function getInputOptionDocument(InputOption $option) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--'.$option->getName()); + $pos = strpos($option->getShortcut(), '|'); + if (false !== $pos) { + $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); + $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); + } else { + $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); + } + $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); + $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); + $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); + + if ($option->acceptValue()) { + $defaults = is_array($option->getDefault()) ? $option->getDefault() : (is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array())); + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + + if (!empty($defaults)) { + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + } + } + + return $dom; + } +} diff --git a/core/vendor/symfony/console/Event/ConsoleCommandEvent.php b/core/vendor/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 0000000..2f517c1 --- /dev/null +++ b/core/vendor/symfony/console/Event/ConsoleCommandEvent.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or changing the input. + * + * @author Fabien Potencier + */ +class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + */ + private $commandShouldRun = true; + + /** + * Disables the command, so it won't be run. + * + * @return bool + */ + public function disableCommand() + { + return $this->commandShouldRun = false; + } + + /** + * Enables the command. + * + * @return bool + */ + public function enableCommand() + { + return $this->commandShouldRun = true; + } + + /** + * Returns true if the command is runnable, false otherwise. + * + * @return bool + */ + public function commandShouldRun() + { + return $this->commandShouldRun; + } +} diff --git a/core/vendor/symfony/console/Event/ConsoleEvent.php b/core/vendor/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 0000000..ab620c4 --- /dev/null +++ b/core/vendor/symfony/console/Event/ConsoleEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * Allows to inspect input and output of a command. + * + * @author Francesco Levorato + */ +class ConsoleEvent extends Event +{ + protected $command; + + private $input; + private $output; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output) + { + $this->command = $command; + $this->input = $input; + $this->output = $output; + } + + /** + * Gets the command that is executed. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * Gets the input instance. + * + * @return InputInterface An InputInterface instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance. + * + * @return OutputInterface An OutputInterface instance + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/core/vendor/symfony/console/Event/ConsoleExceptionEvent.php b/core/vendor/symfony/console/Event/ConsoleExceptionEvent.php new file mode 100644 index 0000000..603b7ee --- /dev/null +++ b/core/vendor/symfony/console/Event/ConsoleExceptionEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle exception thrown in a command. + * + * @author Fabien Potencier + */ +class ConsoleExceptionEvent extends ConsoleEvent +{ + private $exception; + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setException($exception); + $this->exitCode = (int) $exitCode; + } + + /** + * Returns the thrown exception. + * + * @return \Exception The thrown exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/core/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/core/vendor/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 0000000..b6a5d7c --- /dev/null +++ b/core/vendor/symfony/console/Event/ConsoleTerminateEvent.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to manipulate the exit code of a command after its execution. + * + * @author Francesco Levorato + */ +class ConsoleTerminateEvent extends ConsoleEvent +{ + /** + * The exit code of the command. + * + * @var int + */ + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setExitCode($exitCode); + } + + /** + * Sets the exit code. + * + * @param int $exitCode The command exit code + */ + public function setExitCode($exitCode) + { + $this->exitCode = (int) $exitCode; + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/core/vendor/symfony/console/Exception/RuntimeException.php b/core/vendor/symfony/console/Exception/RuntimeException.php new file mode 100644 index 0000000..1324558 --- /dev/null +++ b/core/vendor/symfony/console/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class RuntimeException extends \RuntimeException +{ +} diff --git a/core/vendor/symfony/console/Formatter/OutputFormatter.php b/core/vendor/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 0000000..c8a6a78 --- /dev/null +++ b/core/vendor/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov + */ +class OutputFormatter implements OutputFormatterInterface +{ + private $decorated; + private $styles = array(); + private $styleStack; + + /** + * Escapes "<" special char in given text. + * + * @param string $text Text to escape + * + * @return string Escaped text + */ + public static function escape($text) + { + $text = preg_replace('/([^\\\\]?) FormatterStyle" instances + */ + public function __construct($decorated = false, array $styles = array()) + { + $this->decorated = (bool) $decorated; + + $this->setStyle('error', new OutputFormatterStyle('white', 'red')); + $this->setStyle('info', new OutputFormatterStyle('green')); + $this->setStyle('comment', new OutputFormatterStyle('yellow')); + $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); + + foreach ($styles as $name => $style) { + $this->setStyle($name, $style); + } + + $this->styleStack = new OutputFormatterStyleStack(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * {@inheritdoc} + */ + public function setStyle($name, OutputFormatterStyleInterface $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * {@inheritdoc} + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * {@inheritdoc} + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * {@inheritdoc} + */ + public function format($message) + { + $message = (string) $message; + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*+'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + // add the text up to the next tag + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + strlen($text); + + // opening tag? + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + if (false !== strpos($output, "\0")) { + return strtr($output, array("\0" => '\\', '\\<' => '<')); + } + + return str_replace('\\<', '<', $output); + } + + /** + * @return OutputFormatterStyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * Tries to create new style instance from string. + * + * @param string $string + * + * @return OutputFormatterStyle|false false if string is not format string + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new OutputFormatterStyle(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + return $style; + } + + /** + * Applies current style from stack to text, if must be applied. + * + * @param string $text Input text + * + * @return string Styled text + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/core/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/core/vendor/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 0000000..281e240 --- /dev/null +++ b/core/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages or not + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + /** + * Sets a new style. + * + * @param string $name The style name + * @param OutputFormatterStyleInterface $style The style instance + */ + public function setStyle($name, OutputFormatterStyleInterface $style); + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * + * @return bool + */ + public function hasStyle($name); + + /** + * Gets style options from style with specified name. + * + * @param string $name + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle($name); + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * + * @return string The styled message + */ + public function format($message); +} diff --git a/core/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/core/vendor/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 0000000..e15b132 --- /dev/null +++ b/core/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private static $availableForegroundColors = array( + 'black' => array('set' => 30, 'unset' => 39), + 'red' => array('set' => 31, 'unset' => 39), + 'green' => array('set' => 32, 'unset' => 39), + 'yellow' => array('set' => 33, 'unset' => 39), + 'blue' => array('set' => 34, 'unset' => 39), + 'magenta' => array('set' => 35, 'unset' => 39), + 'cyan' => array('set' => 36, 'unset' => 39), + 'white' => array('set' => 37, 'unset' => 39), + 'default' => array('set' => 39, 'unset' => 39), + ); + private static $availableBackgroundColors = array( + 'black' => array('set' => 40, 'unset' => 49), + 'red' => array('set' => 41, 'unset' => 49), + 'green' => array('set' => 42, 'unset' => 49), + 'yellow' => array('set' => 43, 'unset' => 49), + 'blue' => array('set' => 44, 'unset' => 49), + 'magenta' => array('set' => 45, 'unset' => 49), + 'cyan' => array('set' => 46, 'unset' => 49), + 'white' => array('set' => 47, 'unset' => 49), + 'default' => array('set' => 49, 'unset' => 49), + ); + private static $availableOptions = array( + 'bold' => array('set' => 1, 'unset' => 22), + 'underscore' => array('set' => 4, 'unset' => 24), + 'blink' => array('set' => 5, 'unset' => 25), + 'reverse' => array('set' => 7, 'unset' => 27), + 'conceal' => array('set' => 8, 'unset' => 28), + ); + + private $foreground; + private $background; + private $options = array(); + + /** + * Initializes output formatter style. + * + * @param string|null $foreground The style foreground color name + * @param string|null $background The style background color name + * @param array $options The style options + */ + public function __construct($foreground = null, $background = null, array $options = array()) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * Sets style foreground color. + * + * @param string|null $color The color name + * + * @throws \InvalidArgumentException When the color name isn't defined + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid foreground color specified: "%s". Expected one of (%s)', + $color, + implode(', ', array_keys(static::$availableForegroundColors)) + )); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * Sets style background color. + * + * @param string|null $color The color name + * + * @throws \InvalidArgumentException When the color name isn't defined + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid background color specified: "%s". Expected one of (%s)', + $color, + implode(', ', array_keys(static::$availableBackgroundColors)) + )); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * Sets some specific style option. + * + * @param string $option The option name + * + * @throws \InvalidArgumentException When the option name isn't defined + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid option specified: "%s". Expected one of (%s)', + $option, + implode(', ', array_keys(static::$availableOptions)) + )); + } + + if (!in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * Unsets some specific style option. + * + * @param string $option The option name + * + * @throws \InvalidArgumentException When the option name isn't defined + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid option specified: "%s". Expected one of (%s)', + $option, + implode(', ', array_keys(static::$availableOptions)) + )); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->options = array(); + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text) + { + $setCodes = array(); + $unsetCodes = array(); + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/core/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/core/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 0000000..4c7dc41 --- /dev/null +++ b/core/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter style interface for defining styles. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + * + * @param string $color The color name + */ + public function setForeground($color = null); + + /** + * Sets style background color. + * + * @param string $color The color name + */ + public function setBackground($color = null); + + /** + * Sets some specific style option. + * + * @param string $option The option name + */ + public function setOption($option); + + /** + * Unsets some specific style option. + * + * @param string $option The option name + */ + public function unsetOption($option); + + /** + * Sets multiple style options at once. + */ + public function setOptions(array $options); + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text); +} diff --git a/core/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/core/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 0000000..b560087 --- /dev/null +++ b/core/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private $styles; + + private $emptyStyle; + + public function __construct(OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset() + { + $this->styles = array(); + } + + /** + * Pushes a style in the stack. + */ + public function push(OutputFormatterStyleInterface $style) + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style tags incorrectly nested + */ + public function pop(OutputFormatterStyleInterface $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new \InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * Computes current style with stacks top codes. + * + * @return OutputFormatterStyle + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles) - 1]; + } + + /** + * @return $this + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return OutputFormatterStyleInterface + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/core/vendor/symfony/console/Helper/DebugFormatterHelper.php b/core/vendor/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 0000000..1119b79 --- /dev/null +++ b/core/vendor/symfony/console/Helper/DebugFormatterHelper.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Helps outputting debug information when running an external program from a command. + * + * An external program can be a Process, an HTTP request, or anything else. + * + * @author Fabien Potencier + */ +class DebugFormatterHelper extends Helper +{ + private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'); + private $started = array(); + private $count = -1; + + /** + * Starts a debug formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param string $prefix The prefix to use + * + * @return string + */ + public function start($id, $message, $prefix = 'RUN') + { + $this->started[$id] = array('border' => ++$this->count % count($this->colors)); + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + * + * @param string $id The id of the formatting session + * @param string $buffer The message to display + * @param bool $error Whether to consider the buffer as error + * @param string $prefix The prefix for output + * @param string $errorPrefix The prefix for error output + * + * @return string + */ + public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') + { + $message = ''; + + if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } + if (!isset($this->started[$id]['err'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); + $this->started[$id]['err'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); + } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } + if (!isset($this->started[$id]['out'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); + $this->started[$id]['out'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); + } + + return $message; + } + + /** + * Stops a formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param bool $successful Whether to consider the result as success + * @param string $prefix The prefix for the end output + * + * @return string + */ + public function stop($id, $message, $successful, $prefix = 'RES') + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + /** + * @param string $id The id of the formatting session + * + * @return string + */ + private function getBorder($id) + { + return sprintf(' ', $this->colors[$this->started[$id]['border']]); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'debug_formatter'; + } +} diff --git a/core/vendor/symfony/console/Helper/DescriptorHelper.php b/core/vendor/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 0000000..17fffe5 --- /dev/null +++ b/core/vendor/symfony/console/Helper/DescriptorHelper.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private $descriptors = array(); + + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } + + /** + * Describes an object if supported. + * + * Available options are: + * * format: string, the output format name + * * raw_text: boolean, sets output type as raw + * + * @param OutputInterface $output + * @param object $object + * @param array $options + * + * @throws \InvalidArgumentException when the given format is not supported + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $options = array_merge(array( + 'raw_text' => false, + 'format' => 'txt', + ), $options); + + if (!isset($this->descriptors[$options['format']])) { + throw new \InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + $descriptor->describe($output, $object, $options); + } + + /** + * Registers a descriptor. + * + * @param string $format + * @param DescriptorInterface $descriptor + * + * @return $this + */ + public function register($format, DescriptorInterface $descriptor) + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'descriptor'; + } +} diff --git a/core/vendor/symfony/console/Helper/DialogHelper.php b/core/vendor/symfony/console/Helper/DialogHelper.php new file mode 100644 index 0000000..0a5939a --- /dev/null +++ b/core/vendor/symfony/console/Helper/DialogHelper.php @@ -0,0 +1,500 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +/** + * The Dialog class provides helpers to interact with the user. + * + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0. + * Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead. + */ +class DialogHelper extends InputAwareHelper +{ + private $inputStream; + private static $shell; + private static $stty; + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); + } + } + + /** + * Asks the user to select a value. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param array $choices List of choices to pick from + * @param bool|string $default The default answer if the user enters nothing + * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked + * @param bool $multiselect Select more than one value separated by comma + * + * @return int|string|array The selected value or values (the key of the choices array) + * + * @throws \InvalidArgumentException + */ + public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $width = max(array_map('strlen', array_keys($choices))); + + $messages = (array) $question; + foreach ($choices as $key => $value) { + $messages[] = sprintf(" [%-{$width}s] %s", $key, $value); + } + + $output->writeln($messages); + + $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $picked); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $picked)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = array($picked); + } + + $multiselectChoices = array(); + + foreach ($selectedChoices as $value) { + if (empty($choices[$value])) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + $multiselectChoices[] = $value; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return $picked; + }, $attempts, $default); + + return $result; + } + + /** + * Asks a question to the user. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param string $default The default answer if none is given by the user + * @param array $autocomplete List of values to autocomplete + * + * @return string The user answer + * + * @throws \RuntimeException If there is no data to read in the input stream + */ + public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null) + { + if ($this->input && !$this->input->isInteractive()) { + return $default; + } + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $output->write($question); + + $inputStream = $this->inputStream ?: STDIN; + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } else { + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + // Backspace Character + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + // Move cursor backwards + $output->write("\033[1D"); + } + + if (0 === $i) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + // Echo out remaining chars for current match + $output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + // Erase characters from cursor to end of line + $output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + // Save cursor position + $output->write("\0337"); + // Write highlighted text + $output->write(''.substr($matches[$ofs], $i).''); + // Restore cursor position + $output->write("\0338"); + } + } + + // Reset stty so it behaves normally again + shell_exec(sprintf('stty %s', $sttyMode)); + } + + return strlen($ret) > 0 ? $ret : $default; + } + + /** + * Asks a confirmation to the user. + * + * The question will be asked until the user answers by nothing, yes, or no. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param bool $default The default answer if the user enters nothing + * + * @return bool true if the user has confirmed, false otherwise + */ + public function askConfirmation(OutputInterface $output, $question, $default = true) + { + $answer = 'z'; + while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) { + $answer = $this->ask($output, $question); + } + + if (false === $default) { + return $answer && 'y' == strtolower($answer[0]); + } + + return !$answer || 'y' == strtolower($answer[0]); + } + + /** + * Asks a question to the user, the response is hidden. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question + * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not + * + * @return string The answer + * + * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden + */ + public function askHiddenResponse(OutputInterface $output, $question, $fallback = true) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if ('phar:' === substr(__FILE__, 0, 5)) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $output->write($question); + $value = rtrim(shell_exec($exe)); + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $output->write($question); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($this->inputStream ?: STDIN, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new \RuntimeException('Aborted'); + } + + $value = trim($value); + $output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $output->write($question); + $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $output->writeln(''); + + return $value; + } + + if ($fallback) { + return $this->ask($output, $question); + } + + throw new \RuntimeException('Unable to hide the response'); + } + + /** + * Asks for a value and validates the response. + * + * The validator receives the data to validate. It must return the + * validated data when the data is valid and throw an exception + * otherwise. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param callable $validator A PHP callback + * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $default The default answer if none is given by the user + * @param array $autocomplete List of values to autocomplete + * + * @return mixed + * + * @throws \Exception When any of the validators return an error + */ + public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null) + { + $that = $this; + + $interviewer = function () use ($output, $question, $default, $autocomplete, $that) { + return $that->ask($output, $question, $default, $autocomplete); + }; + + return $this->validateAttempts($interviewer, $output, $validator, $attempts); + } + + /** + * Asks for a value, hide and validates the response. + * + * The validator receives the data to validate. It must return the + * validated data when the data is valid and throw an exception + * otherwise. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param callable $validator A PHP callback + * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not + * + * @return string The response + * + * @throws \Exception When any of the validators return an error + * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden + */ + public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true) + { + $that = $this; + + $interviewer = function () use ($output, $question, $fallback, $that) { + return $that->askHiddenResponse($output, $question, $fallback); + }; + + return $this->validateAttempts($interviewer, $output, $validator, $attempts); + } + + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setInputStream($stream) + { + $this->inputStream = $stream; + } + + /** + * Returns the helper's input stream. + * + * @return resource|null The input stream or null if the default STDIN is used + */ + public function getInputStream() + { + return $this->inputStream; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'dialog'; + } + + /** + * Return a valid Unix shell. + * + * @return string|bool The valid shell name, false in case no valid shell is found + */ + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + // handle other OSs with bash/zsh/ksh/csh if available to hide the answer + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = 0 === $exitcode; + } + + /** + * Validate an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * @param OutputInterface $output An Output instance + * @param callable $validator A PHP callback + * @param int|false $attempts Max number of times to ask before giving up; false will ask infinitely + * + * @return string The validated response + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $e = null; + while (false === $attempts || $attempts--) { + if (null !== $e) { + $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($e->getMessage(), 'error')); + } + + try { + return call_user_func($validator, $interviewer()); + } catch (\Exception $e) { + } + } + + throw $e; + } +} diff --git a/core/vendor/symfony/console/Helper/FormatterHelper.php b/core/vendor/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 0000000..ac736f9 --- /dev/null +++ b/core/vendor/symfony/console/Helper/FormatterHelper.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * The Formatter class provides helpers to format messages. + * + * @author Fabien Potencier + */ +class FormatterHelper extends Helper +{ + /** + * Formats a message within a section. + * + * @param string $section The section name + * @param string $message The message + * @param string $style The style to apply to the section + * + * @return string The format section + */ + public function formatSection($section, $message, $style = 'info') + { + return sprintf('<%s>[%s] %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string $style The style to apply to the whole block + * @param bool $large Whether to return a large block + * + * @return string The formatter message + */ + public function formatBlock($messages, $style, $large = false) + { + if (!is_array($messages)) { + $messages = array($messages); + } + + $len = 0; + $lines = array(); + foreach ($messages as $message) { + $message = OutputFormatter::escape($message); + $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); + $len = max($this->strlen($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? array(str_repeat(' ', $len)) : array(); + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i])); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + for ($i = 0; isset($messages[$i]); ++$i) { + $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); + } + + return implode("\n", $messages); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'formatter'; + } +} diff --git a/core/vendor/symfony/console/Helper/Helper.php b/core/vendor/symfony/console/Helper/Helper.php new file mode 100644 index 0000000..8bf82a1 --- /dev/null +++ b/core/vendor/symfony/console/Helper/Helper.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +abstract class Helper implements HelperInterface +{ + protected $helperSet = null; + + /** + * {@inheritdoc} + */ + public function setHelperSet(HelperSet $helperSet = null) + { + $this->helperSet = $helperSet; + } + + /** + * {@inheritdoc} + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Returns the length of a string, using mb_strwidth if it is available. + * + * @param string $string The string to check its length + * + * @return int The length of the string + */ + public static function strlen($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + public static function formatTime($secs) + { + static $timeFormats = array( + array(0, '< 1 sec'), + array(1, '1 sec'), + array(2, 'secs', 1), + array(60, '1 min'), + array(120, 'mins', 60), + array(3600, '1 hr'), + array(7200, 'hrs', 3600), + array(86400, '1 day'), + array(172800, 'days', 86400), + ); + + foreach ($timeFormats as $index => $format) { + if ($secs >= $format[0]) { + if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) + || $index == count($timeFormats) - 1 + ) { + if (2 == count($format)) { + return $format[1]; + } + + return floor($secs / $format[2]).' '.$format[1]; + } + } + } + } + + public static function formatMemory($memory) + { + if ($memory >= 1024 * 1024 * 1024) { + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); + } + + if ($memory >= 1024 * 1024) { + return sprintf('%.1f MiB', $memory / 1024 / 1024); + } + + if ($memory >= 1024) { + return sprintf('%d KiB', $memory / 1024); + } + + return sprintf('%d B', $memory); + } + + public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) + { + return self::strlen(self::removeDecoration($formatter, $string)); + } + + public static function removeDecoration(OutputFormatterInterface $formatter, $string) + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string); + $formatter->setDecorated($isDecorated); + + return $string; + } +} diff --git a/core/vendor/symfony/console/Helper/HelperInterface.php b/core/vendor/symfony/console/Helper/HelperInterface.php new file mode 100644 index 0000000..1ce8235 --- /dev/null +++ b/core/vendor/symfony/console/Helper/HelperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * HelperInterface is the interface all helpers must implement. + * + * @author Fabien Potencier + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + */ + public function setHelperSet(HelperSet $helperSet = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet(); + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName(); +} diff --git a/core/vendor/symfony/console/Helper/HelperSet.php b/core/vendor/symfony/console/Helper/HelperSet.php new file mode 100644 index 0000000..6ae1627 --- /dev/null +++ b/core/vendor/symfony/console/Helper/HelperSet.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Command\Command; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + */ +class HelperSet implements \IteratorAggregate +{ + /** + * @var Helper[] + */ + private $helpers = array(); + private $command; + + /** + * @param Helper[] $helpers An array of helper + */ + public function __construct(array $helpers = array()) + { + foreach ($helpers as $alias => $helper) { + $this->set($helper, is_int($alias) ? null : $alias); + } + } + + /** + * Sets a helper. + * + * @param HelperInterface $helper The helper instance + * @param string $alias An alias + */ + public function set(HelperInterface $helper, $alias = null) + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + * + * @param string $name The helper name + * + * @return bool true if the helper is defined, false otherwise + */ + public function has($name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @param string $name The helper name + * + * @return HelperInterface The helper instance + * + * @throws \InvalidArgumentException if the helper is not defined + */ + public function get($name) + { + if (!$this->has($name)) { + throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + if ('dialog' === $name && $this->helpers[$name] instanceof DialogHelper) { + @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); + } elseif ('progress' === $name && $this->helpers[$name] instanceof ProgressHelper) { + @trigger_error('"Symfony\Component\Console\Helper\ProgressHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\ProgressBar" instead.', E_USER_DEPRECATED); + } elseif ('table' === $name && $this->helpers[$name] instanceof TableHelper) { + @trigger_error('"Symfony\Component\Console\Helper\TableHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\Table" instead.', E_USER_DEPRECATED); + } + + return $this->helpers[$name]; + } + + public function setCommand(Command $command = null) + { + $this->command = $command; + } + + /** + * Gets the command associated with this helper set. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * @return Helper[] + */ + public function getIterator() + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/core/vendor/symfony/console/Helper/InputAwareHelper.php b/core/vendor/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 0000000..4261767 --- /dev/null +++ b/core/vendor/symfony/console/Helper/InputAwareHelper.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputAwareInterface; + +/** + * An implementation of InputAwareInterface for Helpers. + * + * @author Wouter J + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected $input; + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } +} diff --git a/core/vendor/symfony/console/Helper/ProcessHelper.php b/core/vendor/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 0000000..a811eb4 --- /dev/null +++ b/core/vendor/symfony/console/Helper/ProcessHelper.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\ProcessBuilder; + +/** + * The ProcessHelper class provides helpers to run external processes. + * + * @author Fabien Potencier + */ +class ProcessHelper extends Helper +{ + /** + * Runs an external process. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param int $verbosity The threshold for verbosity + * + * @return Process The process that ran + */ + public function run(OutputInterface $output, $cmd, $error = null, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + if (is_array($cmd)) { + $process = ProcessBuilder::create($cmd)->getProcess(); + } elseif ($cmd instanceof Process) { + $process = $cmd; + } else { + $process = new Process($cmd); + } + + if ($verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($output, $process, $callback); + } + + $process->run($callback); + + if ($verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + if (!$process->isSuccessful() && null !== $error) { + $output->writeln(sprintf('%s', $this->escapeString($error))); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|Process $cmd An instance of Process or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The process that ran + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null) + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return $process; + } + + /** + * Wraps a Process callback to add debugging output. + * + * @param OutputInterface $output An OutputInterface interface + * @param Process $process The Process + * @param callable|null $callback A PHP callable + * + * @return callable + */ + public function wrapCallback(OutputInterface $output, Process $process, $callback = null) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + $that = $this; + + return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) { + $output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type)); + + if (null !== $callback) { + call_user_func($callback, $type, $buffer); + } + }; + } + + /** + * This method is public for PHP 5.3 compatibility, it should be private. + * + * @internal + */ + public function escapeString($str) + { + return str_replace('<', '\\<', $str); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'process'; + } +} diff --git a/core/vendor/symfony/console/Helper/ProgressBar.php b/core/vendor/symfony/console/Helper/ProgressBar.php new file mode 100644 index 0000000..4d4c0e3 --- /dev/null +++ b/core/vendor/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,628 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The ProgressBar provides helpers to display progress output. + * + * @author Fabien Potencier + * @author Chris Jones + */ +class ProgressBar +{ + private $barWidth = 28; + private $barChar; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format; + private $internalFormat; + private $redrawFreq = 1; + private $output; + private $step = 0; + private $max; + private $startTime; + private $stepWidth; + private $percent = 0.0; + private $formatLineCount; + private $messages = array(); + private $overwrite = true; + private $firstRun = true; + + private static $formatters; + private static $formats; + + /** + * @param OutputInterface $output An OutputInterface instance + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, $max = 0) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->output = $output; + $this->setMaxSteps($max); + + if (!$this->output->isDecorated()) { + // disable overwrite when output does not support ANSI codes. + $this->overwrite = false; + + // set a reasonable redraw frequency so output isn't flooded + $this->setRedrawFrequency($max / 10); + } + + $this->startTime = time(); + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition($name, $callable) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition($name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; + } + + /** + * Sets a format for a given name. + * + * This method also allow you to override an existing format. + * + * @param string $name The format name + * @param string $format A format string + */ + public static function setFormatDefinition($name, $format) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition($name) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return isset(self::$formats[$name]) ? self::$formats[$name] : null; + } + + /** + * Associates a text with a named placeholder. + * + * The text is displayed when the progress bar is rendered but only + * when the corresponding placeholder is part of the custom format line + * (by wrapping the name with %). + * + * @param string $message The text to associate with the placeholder + * @param string $name The name of the placeholder + */ + public function setMessage($message, $name = 'message') + { + $this->messages[$name] = $message; + } + + public function getMessage($name = 'message') + { + return $this->messages[$name]; + } + + /** + * Gets the progress bar start time. + * + * @return int The progress bar start time + */ + public function getStartTime() + { + return $this->startTime; + } + + /** + * Gets the progress bar maximal steps. + * + * @return int The progress bar max steps + */ + public function getMaxSteps() + { + return $this->max; + } + + /** + * Gets the progress bar step. + * + * @deprecated since version 2.6, to be removed in 3.0. Use {@link getProgress()} instead. + * + * @return int The progress bar step + */ + public function getStep() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED); + + return $this->getProgress(); + } + + /** + * Gets the current step position. + * + * @return int The progress bar step + */ + public function getProgress() + { + return $this->step; + } + + /** + * Gets the progress bar step width. + * + * @internal This method is public for PHP 5.3 compatibility, it should not be used. + * + * @return int The progress bar step width + */ + public function getStepWidth() + { + return $this->stepWidth; + } + + /** + * Gets the current progress bar percent. + * + * @return float The current progress bar percent + */ + public function getProgressPercent() + { + return $this->percent; + } + + /** + * Sets the progress bar width. + * + * @param int $size The progress bar size + */ + public function setBarWidth($size) + { + $this->barWidth = (int) $size; + } + + /** + * Gets the progress bar width. + * + * @return int The progress bar size + */ + public function getBarWidth() + { + return $this->barWidth; + } + + /** + * Sets the bar character. + * + * @param string $char A character + */ + public function setBarCharacter($char) + { + $this->barChar = $char; + } + + /** + * Gets the bar character. + * + * @return string A character + */ + public function getBarCharacter() + { + if (null === $this->barChar) { + return $this->max ? '=' : $this->emptyBarChar; + } + + return $this->barChar; + } + + /** + * Sets the empty bar character. + * + * @param string $char A character + */ + public function setEmptyBarCharacter($char) + { + $this->emptyBarChar = $char; + } + + /** + * Gets the empty bar character. + * + * @return string A character + */ + public function getEmptyBarCharacter() + { + return $this->emptyBarChar; + } + + /** + * Sets the progress bar character. + * + * @param string $char A character + */ + public function setProgressCharacter($char) + { + $this->progressChar = $char; + } + + /** + * Gets the progress bar character. + * + * @return string A character + */ + public function getProgressCharacter() + { + return $this->progressChar; + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + public function setFormat($format) + { + $this->format = null; + $this->internalFormat = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int|float $freq The frequency in steps + */ + public function setRedrawFrequency($freq) + { + $this->redrawFreq = max((int) $freq, 1); + } + + /** + * Starts the progress output. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + */ + public function start($max = null) + { + $this->startTime = time(); + $this->step = 0; + $this->percent = 0.0; + + if (null !== $max) { + $this->setMaxSteps($max); + } + + $this->display(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + * + * @throws \LogicException + */ + public function advance($step = 1) + { + $this->setProgress($this->step + $step); + } + + /** + * Sets the current progress. + * + * @deprecated since version 2.6, to be removed in 3.0. Use {@link setProgress()} instead. + * + * @param int $step The current progress + * + * @throws \LogicException + */ + public function setCurrent($step) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED); + + $this->setProgress($step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + * + * @param bool $overwrite + */ + public function setOverwrite($overwrite) + { + $this->overwrite = (bool) $overwrite; + } + + /** + * Sets the current progress. + * + * @param int $step The current progress + * + * @throws \LogicException + */ + public function setProgress($step) + { + $step = (int) $step; + if ($step < $this->step) { + throw new \LogicException('You can\'t regress the progress bar.'); + } + + if ($this->max && $step > $this->max) { + $this->max = $step; + } + + $prevPeriod = (int) ($this->step / $this->redrawFreq); + $currPeriod = (int) ($step / $this->redrawFreq); + $this->step = $step; + $this->percent = $this->max ? (float) $this->step / $this->max : 0; + if ($prevPeriod !== $currPeriod || $this->max === $step) { + $this->display(); + } + } + + /** + * Finishes the progress output. + */ + public function finish() + { + if (!$this->max) { + $this->max = $this->step; + } + + if ($this->step === $this->max && !$this->overwrite) { + // prevent double 100% output + return; + } + + $this->setProgress($this->max); + } + + /** + * Outputs the current progress string. + */ + public function display() + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped. + $self = $this; + $output = $this->output; + $messages = $this->messages; + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) { + if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { + $text = call_user_func($formatter, $self, $output); + } elseif (isset($messages[$matches[1]])) { + $text = $messages[$matches[1]]; + } else { + return $matches[0]; + } + + if (isset($matches[2])) { + $text = sprintf('%'.$matches[2], $text); + } + + return $text; + }, $this->format)); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear() + { + if (!$this->overwrite) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite(''); + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + private function setRealFormat($format) + { + // try to use the _nomax variant if available + if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { + $this->format = self::getFormatDefinition($format.'_nomax'); + } elseif (null !== self::getFormatDefinition($format)) { + $this->format = self::getFormatDefinition($format); + } else { + $this->format = $format; + } + + $this->formatLineCount = substr_count($this->format, "\n"); + } + + /** + * Sets the progress bar maximal steps. + * + * @param int $max The progress bar max steps + */ + private function setMaxSteps($max) + { + $this->max = max(0, (int) $max); + $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; + } + + /** + * Overwrites a previous message to the output. + * + * @param string $message The message + */ + private function overwrite($message) + { + if ($this->overwrite) { + if (!$this->firstRun) { + // Move the cursor to the beginning of the line + $this->output->write("\x0D"); + + // Erase the line + $this->output->write("\x1B[2K"); + + // Erase previous lines + if ($this->formatLineCount > 0) { + $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount)); + } + } + } elseif ($this->step > 0) { + $this->output->writeln(''); + } + + $this->firstRun = false; + + $this->output->write($message); + } + + private function determineBestFormat() + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->max ? 'verbose' : 'verbose_nomax'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + return $this->max ? 'very_verbose' : 'very_verbose_nomax'; + case OutputInterface::VERBOSITY_DEBUG: + return $this->max ? 'debug' : 'debug_nomax'; + default: + return $this->max ? 'normal' : 'normal_nomax'; + } + } + + private static function initPlaceholderFormatters() + { + return array( + 'bar' => function (ProgressBar $bar, OutputInterface $output) { + $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); + $display = str_repeat($bar->getBarCharacter(), $completeBars); + if ($completeBars < $bar->getBarWidth()) { + $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => function (ProgressBar $bar) { + return Helper::formatTime(time() - $bar->getStartTime()); + }, + 'remaining' => function (ProgressBar $bar) { + if (!$bar->getMaxSteps()) { + throw new \LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $remaining = 0; + } else { + $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); + } + + return Helper::formatTime($remaining); + }, + 'estimated' => function (ProgressBar $bar) { + if (!$bar->getMaxSteps()) { + throw new \LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $estimated = 0; + } else { + $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); + } + + return Helper::formatTime($estimated); + }, + 'memory' => function (ProgressBar $bar) { + return Helper::formatMemory(memory_get_usage(true)); + }, + 'current' => function (ProgressBar $bar) { + return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); + }, + 'max' => function (ProgressBar $bar) { + return $bar->getMaxSteps(); + }, + 'percent' => function (ProgressBar $bar) { + return floor($bar->getProgressPercent() * 100); + }, + ); + } + + private static function initFormats() + { + return array( + 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', + 'normal_nomax' => ' %current% [%bar%]', + + 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ); + } +} diff --git a/core/vendor/symfony/console/Helper/ProgressHelper.php b/core/vendor/symfony/console/Helper/ProgressHelper.php new file mode 100644 index 0000000..20b3468 --- /dev/null +++ b/core/vendor/symfony/console/Helper/ProgressHelper.php @@ -0,0 +1,468 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The Progress class provides helpers to display progress output. + * + * @author Chris Jones + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0 + * Use {@link ProgressBar} instead. + */ +class ProgressHelper extends Helper +{ + const FORMAT_QUIET = ' %percent%%'; + const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%'; + const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%'; + const FORMAT_QUIET_NOMAX = ' %current%'; + const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]'; + const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%'; + + // options + private $barWidth = 28; + private $barChar = '='; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format = null; + private $redrawFreq = 1; + + private $lastMessagesLength; + private $barCharOriginal; + + /** + * @var OutputInterface + */ + private $output; + + /** + * Current step. + * + * @var int + */ + private $current; + + /** + * Maximum number of steps. + * + * @var int + */ + private $max; + + /** + * Start time of the progress bar. + * + * @var int + */ + private $startTime; + + /** + * List of formatting variables. + * + * @var array + */ + private $defaultFormatVars = array( + 'current', + 'max', + 'bar', + 'percent', + 'elapsed', + ); + + /** + * Available formatting variables. + * + * @var array + */ + private $formatVars; + + /** + * Stored format part widths (used for padding). + * + * @var array + */ + private $widths = array( + 'current' => 4, + 'max' => 4, + 'percent' => 3, + 'elapsed' => 6, + ); + + /** + * Various time formats. + * + * @var array + */ + private $timeFormats = array( + array(0, '???'), + array(2, '1 sec'), + array(59, 'secs', 1), + array(60, '1 min'), + array(3600, 'mins', 60), + array(5400, '1 hr'), + array(86400, 'hrs', 3600), + array(129600, '1 day'), + array(604800, 'days', 86400), + ); + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\ProgressBar class instead.', E_USER_DEPRECATED); + } + } + + /** + * Sets the progress bar width. + * + * @param int $size The progress bar size + */ + public function setBarWidth($size) + { + $this->barWidth = (int) $size; + } + + /** + * Sets the bar character. + * + * @param string $char A character + */ + public function setBarCharacter($char) + { + $this->barChar = $char; + } + + /** + * Sets the empty bar character. + * + * @param string $char A character + */ + public function setEmptyBarCharacter($char) + { + $this->emptyBarChar = $char; + } + + /** + * Sets the progress bar character. + * + * @param string $char A character + */ + public function setProgressCharacter($char) + { + $this->progressChar = $char; + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + public function setFormat($format) + { + $this->format = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int $freq The frequency in steps + */ + public function setRedrawFrequency($freq) + { + $this->redrawFreq = (int) $freq; + } + + /** + * Starts the progress output. + * + * @param OutputInterface $output An Output instance + * @param int|null $max Maximum steps + */ + public function start(OutputInterface $output, $max = null) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->startTime = time(); + $this->current = 0; + $this->max = (int) $max; + + // Disabling output when it does not support ANSI codes as it would result in a broken display anyway. + $this->output = $output->isDecorated() ? $output : new NullOutput(); + $this->lastMessagesLength = 0; + $this->barCharOriginal = ''; + + if (null === $this->format) { + switch ($output->getVerbosity()) { + case OutputInterface::VERBOSITY_QUIET: + $this->format = self::FORMAT_QUIET_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_QUIET; + } + break; + case OutputInterface::VERBOSITY_VERBOSE: + case OutputInterface::VERBOSITY_VERY_VERBOSE: + case OutputInterface::VERBOSITY_DEBUG: + $this->format = self::FORMAT_VERBOSE_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_VERBOSE; + } + break; + default: + $this->format = self::FORMAT_NORMAL_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_NORMAL; + } + break; + } + } + + $this->initialize(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + * @param bool $redraw Whether to redraw or not + * + * @throws \LogicException + */ + public function advance($step = 1, $redraw = false) + { + $this->setCurrent($this->current + $step, $redraw); + } + + /** + * Sets the current progress. + * + * @param int $current The current progress + * @param bool $redraw Whether to redraw or not + * + * @throws \LogicException + */ + public function setCurrent($current, $redraw = false) + { + if (null === $this->startTime) { + throw new \LogicException('You must start the progress bar before calling setCurrent().'); + } + + $current = (int) $current; + + if ($current < $this->current) { + throw new \LogicException('You can\'t regress the progress bar'); + } + + if (0 === $this->current) { + $redraw = true; + } + + $prevPeriod = (int) ($this->current / $this->redrawFreq); + + $this->current = $current; + + $currPeriod = (int) ($this->current / $this->redrawFreq); + if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) { + $this->display(); + } + } + + /** + * Outputs the current progress string. + * + * @param bool $finish Forces the end result + * + * @throws \LogicException + */ + public function display($finish = false) + { + if (null === $this->startTime) { + throw new \LogicException('You must start the progress bar before calling display().'); + } + + $message = $this->format; + foreach ($this->generate($finish) as $name => $value) { + $message = str_replace("%{$name}%", $value, $message); + } + $this->overwrite($this->output, $message); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear() + { + $this->overwrite($this->output, ''); + } + + /** + * Finishes the progress output. + */ + public function finish() + { + if (null === $this->startTime) { + throw new \LogicException('You must start the progress bar before calling finish().'); + } + + if (null !== $this->startTime) { + if (!$this->max) { + $this->barChar = $this->barCharOriginal; + $this->display(true); + } + $this->startTime = null; + $this->output->writeln(''); + $this->output = null; + } + } + + /** + * Initializes the progress helper. + */ + private function initialize() + { + $this->formatVars = array(); + foreach ($this->defaultFormatVars as $var) { + if (false !== strpos($this->format, "%{$var}%")) { + $this->formatVars[$var] = true; + } + } + + if ($this->max > 0) { + $this->widths['max'] = $this->strlen($this->max); + $this->widths['current'] = $this->widths['max']; + } else { + $this->barCharOriginal = $this->barChar; + $this->barChar = $this->emptyBarChar; + } + } + + /** + * Generates the array map of format variables to values. + * + * @param bool $finish Forces the end result + * + * @return array Array of format vars and values + */ + private function generate($finish = false) + { + $vars = array(); + $percent = 0; + if ($this->max > 0) { + $percent = (float) $this->current / $this->max; + } + + if (isset($this->formatVars['bar'])) { + if ($this->max > 0) { + $completeBars = floor($percent * $this->barWidth); + } else { + if (!$finish) { + $completeBars = floor($this->current % $this->barWidth); + } else { + $completeBars = $this->barWidth; + } + } + + $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar); + $bar = str_repeat($this->barChar, $completeBars); + if ($completeBars < $this->barWidth) { + $bar .= $this->progressChar; + $bar .= str_repeat($this->emptyBarChar, $emptyBars); + } + + $vars['bar'] = $bar; + } + + if (isset($this->formatVars['elapsed'])) { + $elapsed = time() - $this->startTime; + $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT); + } + + if (isset($this->formatVars['current'])) { + $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT); + } + + if (isset($this->formatVars['max'])) { + $vars['max'] = $this->max; + } + + if (isset($this->formatVars['percent'])) { + $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT); + } + + return $vars; + } + + /** + * Converts seconds into human-readable format. + * + * @param int $secs Number of seconds + * + * @return string Time in readable format + */ + private function humaneTime($secs) + { + $text = ''; + foreach ($this->timeFormats as $format) { + if ($secs < $format[0]) { + if (2 == count($format)) { + $text = $format[1]; + break; + } else { + $text = ceil($secs / $format[2]).' '.$format[1]; + break; + } + } + } + + return $text; + } + + /** + * Overwrites a previous message to the output. + * + * @param OutputInterface $output An Output instance + * @param string $message The message + */ + private function overwrite(OutputInterface $output, $message) + { + $length = $this->strlen($message); + + // append whitespace to match the last line's length + if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) { + $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + } + + // carriage return + $output->write("\x0D"); + $output->write($message); + + $this->lastMessagesLength = $this->strlen($message); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'progress'; + } +} diff --git a/core/vendor/symfony/console/Helper/QuestionHelper.php b/core/vendor/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 0000000..36187f8 --- /dev/null +++ b/core/vendor/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,446 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * The QuestionHelper class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class QuestionHelper extends Helper +{ + private $inputStream; + private static $shell; + private static $stty; + + /** + * Asks a question to the user. + * + * @return mixed The user answer + * + * @throws \RuntimeException If there is no data to read in the input stream + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if (!$input->isInteractive()) { + if ($question instanceof ChoiceQuestion) { + $choices = $question->getChoices(); + + return $choices[$question->getDefault()]; + } + + return $question->getDefault(); + } + + if (!$question->getValidator()) { + return $this->doAsk($output, $question); + } + + $that = $this; + + $interviewer = function () use ($output, $question, $that) { + return $that->doAsk($output, $question); + }; + + return $this->validateAttempts($interviewer, $output, $question); + } + + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + * + * @throws \InvalidArgumentException In case the stream is not a resource + */ + public function setInputStream($stream) + { + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Input stream must be a valid resource.'); + } + + $this->inputStream = $stream; + } + + /** + * Returns the helper's input stream. + * + * @return resource + */ + public function getInputStream() + { + return $this->inputStream; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'question'; + } + + /** + * Asks the question to the user. + * + * This method is public for PHP 5.3 compatibility, it should be private. + * + * @return bool|mixed|null|string + * + * @throws \Exception + * @throws \RuntimeException + */ + public function doAsk(OutputInterface $output, Question $question) + { + $this->writePrompt($output, $question); + + $inputStream = $this->inputStream ?: STDIN; + $autocomplete = $question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($output, $inputStream)); + } catch (\RuntimeException $e) { + if (!$question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($output, $question, $inputStream, is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false))); + } + + $ret = strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + /** + * Outputs the question prompt. + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $message = $question->getQuestion(); + + if ($question instanceof ChoiceQuestion) { + $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices()))); + + $messages = (array) $question->getQuestion(); + foreach ($question->getChoices() as $key => $value) { + $width = $maxWidth - $this->strlen($key); + $messages[] = ' ['.$key.str_repeat(' ', $width).'] '.$value; + } + + $output->writeln($messages); + + $message = $question->getPrompt(); + } + + $output->write($message); + } + + /** + * Outputs an error message. + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { + $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); + } else { + $message = ''.$error->getMessage().''; + } + + $output->writeln($message); + } + + /** + * Autocompletes a question. + * + * @param OutputInterface $output + * @param Question $question + * @param resource $inputStream + * @param array $autocomplete + * + * @return string + */ + private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete) + { + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + // Backspace Character + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + // Move cursor backwards + $output->write("\033[1D"); + } + + if (0 === $i) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + // Echo out remaining chars for current match + $output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (0 === strpos($value, $ret)) { + $matches[$numMatches++] = $value; + } + } + } + + // Erase characters from cursor to end of line + $output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + // Save cursor position + $output->write("\0337"); + // Write highlighted text + $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).''); + // Restore cursor position + $output->write("\0338"); + } + } + + // Reset stty so it behaves normally again + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + /** + * Gets a hidden response from user. + * + * @param OutputInterface $output An Output instance + * @param resource $inputStream The handler resource + * + * @return string The answer + * + * @throws \RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function getHiddenResponse(OutputInterface $output, $inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if ('phar:' === substr(__FILE__, 0, 5)) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $value = rtrim(shell_exec($exe)); + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new RuntimeException('Aborted'); + } + + $value = trim($value); + $output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $output->writeln(''); + + return $value; + } + + throw new RuntimeException('Unable to hide the response.'); + } + + /** + * Validates an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * @param OutputInterface $output An Output instance + * @param Question $question A Question instance + * + * @return mixed The validated response + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts($interviewer, OutputInterface $output, Question $question) + { + $error = null; + $attempts = $question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->writeError($output, $error); + } + + try { + return call_user_func($question->getValidator(), $interviewer()); + } catch (RuntimeException $e) { + throw $e; + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * Returns a valid unix shell. + * + * @return string|bool The valid shell name, false in case no valid shell is found + */ + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + // handle other OSs with bash/zsh/ksh/csh if available to hide the answer + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + /** + * Returns whether Stty is available or not. + * + * @return bool + */ + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = 0 === $exitcode; + } +} diff --git a/core/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/core/vendor/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 0000000..e51e422 --- /dev/null +++ b/core/vendor/symfony/console/Helper/SymfonyQuestionHelper.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Symfony Style Guide compliant question helper. + * + * @author Kevin Bond + */ +class SymfonyQuestionHelper extends QuestionHelper +{ + /** + * {@inheritdoc} + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + $validator = $question->getValidator(); + $question->setValidator(function ($value) use ($validator) { + if (null !== $validator) { + $value = $validator($value); + } else { + // make required + if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) { + throw new \Exception('A value is required.'); + } + } + + return $value; + }); + + return parent::ask($input, $output, $question); + } + + /** + * {@inheritdoc} + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); + $default = $question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $question instanceof ConfirmationQuestion: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $question instanceof ChoiceQuestion && $question->isMultiselect(): + $choices = $question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); + + break; + + case $question instanceof ChoiceQuestion: + $choices = $question->getChoices(); + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default])); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); + } + + $output->writeln($text); + + if ($question instanceof ChoiceQuestion) { + $width = max(array_map('strlen', array_keys($question->getChoices()))); + + foreach ($question->getChoices() as $key => $value) { + $output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $output->write(' > '); + } + + /** + * {@inheritdoc} + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } +} diff --git a/core/vendor/symfony/console/Helper/Table.php b/core/vendor/symfony/console/Helper/Table.php new file mode 100644 index 0000000..15ad1ed --- /dev/null +++ b/core/vendor/symfony/console/Helper/Table.php @@ -0,0 +1,617 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Provides helpers to display a table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Abdellatif Ait boudad + */ +class Table +{ + /** + * Table headers. + */ + private $headers = array(); + + /** + * Table rows. + */ + private $rows = array(); + + /** + * Column widths cache. + */ + private $columnWidths = array(); + + /** + * Number of columns cache. + * + * @var int + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var TableStyle + */ + private $style; + + private static $styles; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + * + * @param string $name The style name + * @param TableStyle $style A TableStyle instance + */ + public static function setStyleDefinition($name, TableStyle $style) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + * + * @param string $name The style name + * + * @return TableStyle + */ + public static function getStyleDefinition($name) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + /** + * Sets table style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setStyle($name) + { + $this->style = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current table style. + * + * @return TableStyle + */ + public function getStyle() + { + return $this->style; + } + + public function setHeaders(array $headers) + { + $headers = array_values($headers); + if (!empty($headers) && !is_array($headers[0])) { + $headers = array($headers); + } + + $this->headers = $headers; + + return $this; + } + + public function setRows(array $rows) + { + $this->rows = array(); + + return $this->addRows($rows); + } + + public function addRows(array $rows) + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + public function addRow($row) + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + if (!is_array($row)) { + throw new \InvalidArgumentException('A row must be an array or a TableSeparator instance.'); + } + + $this->rows[] = array_values($row); + + return $this; + } + + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + * + */ + public function render() + { + $this->calculateNumberOfColumns(); + $this->rows = $this->buildTableRows($this->rows); + $this->headers = $this->buildTableRows($this->headers); + + $this->renderRowSeparator(); + if (!empty($this->headers)) { + foreach ($this->headers as $header) { + $this->renderRow($header, $this->style->getCellHeaderFormat()); + $this->renderRowSeparator(); + } + } + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + } else { + $this->renderRow($row, $this->style->getCellRowFormat()); + } + } + if (!empty($this->rows)) { + $this->renderRowSeparator(); + } + + $this->cleanup(); + } + + /** + * Renders horizontal header separator. + * + * Example: +-----+-----------+-------+ + */ + private function renderRowSeparator() + { + if (0 === $count = $this->numberOfColumns) { + return; + } + + if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { + return; + } + + $markup = $this->style->getCrossingChar(); + for ($column = 0; $column < $count; ++$column) { + $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->getColumnWidth($column)).$this->style->getCrossingChar(); + } + + $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator() + { + return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); + } + + /** + * Renders table row. + * + * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * + * @param array $row + * @param string $cellFormat + */ + private function renderRow(array $row, $cellFormat) + { + if (empty($row)) { + return; + } + + $rowContent = $this->renderColumnSeparator(); + foreach ($this->getRowColumns($row) as $column) { + $rowContent .= $this->renderCell($row, $column, $cellFormat); + $rowContent .= $this->renderColumnSeparator(); + } + $this->output->writeln($rowContent); + } + + /** + * Renders table cell with padding. + * + * @param array $row + * @param int $column + * @param string $cellFormat + */ + private function renderCell(array $row, $column, $cellFormat) + { + $cell = isset($row[$column]) ? $row[$column] : ''; + $width = $this->getColumnWidth($column); + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // add the width of the following columns(numbers of colspan). + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { + $width += $this->getColumnSeparatorWidth() + $this->getColumnWidth($nextColumn); + } + } + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (function_exists('mb_strwidth') && false !== $encoding = mb_detect_encoding($cell)) { + $width += strlen($cell) - mb_strwidth($cell, $encoding); + } + + if ($cell instanceof TableSeparator) { + return sprintf($this->style->getBorderFormat(), str_repeat($this->style->getHorizontalBorderChar(), $width)); + } + + $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + $content = sprintf($this->style->getCellRowContentFormat(), $cell); + + return sprintf($cellFormat, str_pad($content, $width, $this->style->getPaddingChar(), $this->style->getPadType())); + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns() + { + if (null !== $this->numberOfColumns) { + return; + } + + $columns = array(0); + foreach (array_merge($this->headers, $this->rows) as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $columns[] = $this->getNumberOfColumns($row); + } + + $this->numberOfColumns = max($columns); + } + + private function buildTableRows($rows) + { + $unmergedRows = array(); + for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) { + $rows = $this->fillNextRows($rows, $rowKey); + + // Remove any new line breaks and replace it with a new line + foreach ($rows[$rowKey] as $column => $cell) { + if (!strstr($cell, "\n")) { + continue; + } + $lines = explode("\n", str_replace("\n", "\n", $cell)); + foreach ($lines as $lineKey => $line) { + if ($cell instanceof TableCell) { + $line = new TableCell($line, array('colspan' => $cell->getColspan())); + } + if (0 === $lineKey) { + $rows[$rowKey][$column] = $line; + } else { + $unmergedRows[$rowKey][$lineKey][$column] = $line; + } + } + } + } + + $tableRows = array(); + foreach ($rows as $rowKey => $row) { + $tableRows[] = $this->fillCells($row); + if (isset($unmergedRows[$rowKey])) { + $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); + } + } + + return $tableRows; + } + + /** + * fill rows that contains rowspan > 1. + * + * @param array $rows + * @param int $line + * + * @return array + */ + private function fillNextRows(array $rows, $line) + { + $unmergedRows = array(); + foreach ($rows[$line] as $column => $cell) { + if ($cell instanceof TableCell && $cell->getRowspan() > 1) { + $nbLines = $cell->getRowspan() - 1; + $lines = array($cell); + if (strstr($cell, "\n")) { + $lines = explode("\n", str_replace("\n", "\n", $cell)); + $nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan())); + unset($lines[0]); + } + + // create a two dimensional array (rowspan x colspan) + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows); + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan())); + if ($nbLines === $unmergedRowKey - $line) { + break; + } + } + } + } + + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + // we need to know if $unmergedRow will be merged or inserted into $rows + if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + foreach ($unmergedRow as $cellKey => $cell) { + // insert cell into row at cellKey position + array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell)); + } + } else { + $row = $this->copyRow($rows, $unmergedRowKey - 1); + foreach ($unmergedRow as $column => $cell) { + if (!empty($cell)) { + $row[$column] = $unmergedRow[$column]; + } + } + array_splice($rows, $unmergedRowKey, 0, array($row)); + } + } + + return $rows; + } + + /** + * fill cells for a row that contains colspan > 1. + * + * @return array + */ + private function fillCells($row) + { + $newRow = array(); + foreach ($row as $column => $cell) { + $newRow[] = $cell; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value at column position + $newRow[] = ''; + } + } + } + + return $newRow ?: $row; + } + + /** + * @param array $rows + * @param int $line + * + * @return array + */ + private function copyRow(array $rows, $line) + { + $row = $rows[$line]; + foreach ($row as $cellKey => $cellValue) { + $row[$cellKey] = ''; + if ($cellValue instanceof TableCell) { + $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan())); + } + } + + return $row; + } + + /** + * Gets number of columns by row. + * + * @return int + */ + private function getNumberOfColumns(array $row) + { + $columns = count($row); + foreach ($row as $column) { + $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; + } + + return $columns; + } + + /** + * Gets list of columns for the given row. + * + * @return array + */ + private function getRowColumns(array $row) + { + $columns = range(0, $this->numberOfColumns - 1); + foreach ($row as $cellKey => $cell) { + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // exclude grouped columns. + $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); + } + } + + return $columns; + } + + /** + * Gets column width. + * + * @param int $column + * + * @return int + */ + private function getColumnWidth($column) + { + if (isset($this->columnWidths[$column])) { + return $this->columnWidths[$column]; + } + + $lengths = array(); + + foreach (array_merge($this->headers, $this->rows) as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + foreach ($row as $i => $cell) { + if ($cell instanceof TableCell) { + $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); + $textLength = Helper::strlen($textContent); + if ($textLength > 0) { + $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); + foreach ($contentColumns as $position => $content) { + $row[$i + $position] = $content; + } + } + } + } + + $lengths[] = $this->getCellWidth($row, $column); + } + + return $this->columnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2; + } + + /** + * Gets column width. + * + * @return int + */ + private function getColumnSeparatorWidth() + { + return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); + } + + /** + * Gets cell width. + * + * @param array $row + * @param int $column + * + * @return int + */ + private function getCellWidth(array $row, $column) + { + if (isset($row[$column])) { + $cell = $row[$column]; + $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + + return $cellWidth; + } + + return 0; + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup() + { + $this->columnWidths = array(); + $this->numberOfColumns = null; + } + + private static function initStyles() + { + $borderless = new TableStyle(); + $borderless + ->setHorizontalBorderChar('=') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ; + + $compact = new TableStyle(); + $compact + ->setHorizontalBorderChar('') + ->setVerticalBorderChar(' ') + ->setCrossingChar('') + ->setCellRowContentFormat('%s') + ; + + $styleGuide = new TableStyle(); + $styleGuide + ->setHorizontalBorderChar('-') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ->setCellHeaderFormat('%s') + ; + + return array( + 'default' => new TableStyle(), + 'borderless' => $borderless, + 'compact' => $compact, + 'symfony-style-guide' => $styleGuide, + ); + } + + private function resolveStyle($name) + { + if ($name instanceof TableStyle) { + return $name; + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } +} diff --git a/core/vendor/symfony/console/Helper/TableCell.php b/core/vendor/symfony/console/Helper/TableCell.php new file mode 100644 index 0000000..8562f72 --- /dev/null +++ b/core/vendor/symfony/console/Helper/TableCell.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * @author Abdellatif Ait boudad + */ +class TableCell +{ + private $value; + private $options = array( + 'rowspan' => 1, + 'colspan' => 1, + ); + + /** + * @param string $value + * @param array $options + */ + public function __construct($value = '', array $options = array()) + { + if (is_numeric($value) && !is_string($value)) { + $value = (string) $value; + } + + $this->value = $value; + + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new \InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + * + * @return string + */ + public function __toString() + { + return $this->value; + } + + /** + * Gets number of colspan. + * + * @return int + */ + public function getColspan() + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + * + * @return int + */ + public function getRowspan() + { + return (int) $this->options['rowspan']; + } +} diff --git a/core/vendor/symfony/console/Helper/TableHelper.php b/core/vendor/symfony/console/Helper/TableHelper.php new file mode 100644 index 0000000..b03f51f --- /dev/null +++ b/core/vendor/symfony/console/Helper/TableHelper.php @@ -0,0 +1,263 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\NullOutput; + +/** + * Provides helpers to display table output. + * + * @author Саша Стаменковић + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0 + * Use {@link Table} instead. + */ +class TableHelper extends Helper +{ + const LAYOUT_DEFAULT = 0; + const LAYOUT_BORDERLESS = 1; + const LAYOUT_COMPACT = 2; + + private $table; + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\Table class instead.', E_USER_DEPRECATED); + } + + $this->table = new Table(new NullOutput()); + } + + /** + * Sets table layout type. + * + * @param int $layout self::LAYOUT_* + * + * @return $this + * + * @throws \InvalidArgumentException when the table layout is not known + */ + public function setLayout($layout) + { + switch ($layout) { + case self::LAYOUT_BORDERLESS: + $this->table->setStyle('borderless'); + break; + + case self::LAYOUT_COMPACT: + $this->table->setStyle('compact'); + break; + + case self::LAYOUT_DEFAULT: + $this->table->setStyle('default'); + break; + + default: + throw new \InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout)); + } + + return $this; + } + + public function setHeaders(array $headers) + { + $this->table->setHeaders($headers); + + return $this; + } + + public function setRows(array $rows) + { + $this->table->setRows($rows); + + return $this; + } + + public function addRows(array $rows) + { + $this->table->addRows($rows); + + return $this; + } + + public function addRow(array $row) + { + $this->table->addRow($row); + + return $this; + } + + public function setRow($column, array $row) + { + $this->table->setRow($column, $row); + + return $this; + } + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return $this + */ + public function setPaddingChar($paddingChar) + { + $this->table->getStyle()->setPaddingChar($paddingChar); + + return $this; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return $this + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->table->getStyle()->setHorizontalBorderChar($horizontalBorderChar); + + return $this; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return $this + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->table->getStyle()->setVerticalBorderChar($verticalBorderChar); + + return $this; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return $this + */ + public function setCrossingChar($crossingChar) + { + $this->table->getStyle()->setCrossingChar($crossingChar); + + return $this; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return $this + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->table->getStyle()->setCellHeaderFormat($cellHeaderFormat); + + return $this; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return $this + */ + public function setCellRowFormat($cellRowFormat) + { + $this->table->getStyle()->setCellHeaderFormat($cellRowFormat); + + return $this; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return $this + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->table->getStyle()->setCellRowContentFormat($cellRowContentFormat); + + return $this; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return $this + */ + public function setBorderFormat($borderFormat) + { + $this->table->getStyle()->setBorderFormat($borderFormat); + + return $this; + } + + /** + * Sets cell padding type. + * + * @param int $padType STR_PAD_* + * + * @return $this + */ + public function setPadType($padType) + { + $this->table->getStyle()->setPadType($padType); + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + */ + public function render(OutputInterface $output) + { + $p = new \ReflectionProperty($this->table, 'output'); + $p->setAccessible(true); + $p->setValue($this->table, $output); + + $this->table->render(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'table'; + } +} diff --git a/core/vendor/symfony/console/Helper/TableSeparator.php b/core/vendor/symfony/console/Helper/TableSeparator.php new file mode 100644 index 0000000..c7b8dc9 --- /dev/null +++ b/core/vendor/symfony/console/Helper/TableSeparator.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Marks a row as being a separator. + * + * @author Fabien Potencier + */ +class TableSeparator extends TableCell +{ + public function __construct(array $options = array()) + { + parent::__construct('', $options); + } +} diff --git a/core/vendor/symfony/console/Helper/TableStyle.php b/core/vendor/symfony/console/Helper/TableStyle.php new file mode 100644 index 0000000..b00f12e --- /dev/null +++ b/core/vendor/symfony/console/Helper/TableStyle.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Defines the styles for a Table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + */ +class TableStyle +{ + private $paddingChar = ' '; + private $horizontalBorderChar = '-'; + private $verticalBorderChar = '|'; + private $crossingChar = '+'; + private $cellHeaderFormat = '%s'; + private $cellRowFormat = '%s'; + private $cellRowContentFormat = ' %s '; + private $borderFormat = '%s'; + private $padType = STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return $this + */ + public function setPaddingChar($paddingChar) + { + if (!$paddingChar) { + throw new \LogicException('The padding char must not be empty'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + * + * @return string + */ + public function getPaddingChar() + { + return $this->paddingChar; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return $this + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->horizontalBorderChar = $horizontalBorderChar; + + return $this; + } + + /** + * Gets horizontal border character. + * + * @return string + */ + public function getHorizontalBorderChar() + { + return $this->horizontalBorderChar; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return $this + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->verticalBorderChar = $verticalBorderChar; + + return $this; + } + + /** + * Gets vertical border character. + * + * @return string + */ + public function getVerticalBorderChar() + { + return $this->verticalBorderChar; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return $this + */ + public function setCrossingChar($crossingChar) + { + $this->crossingChar = $crossingChar; + + return $this; + } + + /** + * Gets crossing character. + * + * @return string $crossingChar + */ + public function getCrossingChar() + { + return $this->crossingChar; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return $this + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + * + * @return string + */ + public function getCellHeaderFormat() + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return $this + */ + public function setCellRowFormat($cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + * + * @return string + */ + public function getCellRowFormat() + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return $this + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + * + * @return string + */ + public function getCellRowContentFormat() + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return $this + */ + public function setBorderFormat($borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + * + * @return string + */ + public function getBorderFormat() + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @param int $padType STR_PAD_* + * + * @return $this + */ + public function setPadType($padType) + { + if (!in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) { + throw new \InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); + } + + $this->padType = $padType; + + return $this; + } + + /** + * Gets cell padding type. + * + * @return int + */ + public function getPadType() + { + return $this->padType; + } +} diff --git a/core/vendor/symfony/console/Input/ArgvInput.php b/core/vendor/symfony/console/Input/ArgvInput.php new file mode 100644 index 0000000..62c658b --- /dev/null +++ b/core/vendor/symfony/console/Input/ArgvInput.php @@ -0,0 +1,345 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * ArgvInput represents an input coming from the CLI arguments. + * + * Usage: + * + * $input = new ArgvInput(); + * + * By default, the `$_SERVER['argv']` array is used for the input values. + * + * This can be overridden by explicitly passing the input values in the constructor: + * + * $input = new ArgvInput($_SERVER['argv']); + * + * If you pass it yourself, don't forget that the first element of the array + * is the name of the running application. + * + * When passing an argument to the constructor, be sure that it respects + * the same rules as the argv one. It's almost always better to use the + * `StringInput` when you want to provide your own input. + * + * @author Fabien Potencier + * + * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 + */ +class ArgvInput extends Input +{ + private $tokens; + private $parsed; + + /** + * @param array|null $argv An array of parameters from the CLI (in the argv format) + * @param InputDefinition|null $definition A InputDefinition instance + */ + public function __construct(array $argv = null, InputDefinition $definition = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + } + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * Parses a short option. + * + * @param string $token The current token + */ + private function parseShortOption($token) + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @param string $name The current token + * + * @throws \RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet($name) + { + $len = strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + * + * @param string $token The current token + */ + private function parseLongOption($token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + if (0 === strlen($value = substr($name, $pos + 1))) { + array_unshift($this->parsed, null); + } + $this->addLongOption(substr($name, 0, $pos), $value); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Parses an argument. + * + * @param string $token The current token + * + * @throws \RuntimeException When too many arguments are given + */ + private function parseArgument($token) + { + $c = count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token; + + // if last argument isArray(), append token to last argument + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + // unexpected argument + } else { + $all = $this->definition->getArguments(); + if (count($all)) { + throw new \RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); + } + + throw new \RuntimeException(sprintf('No arguments expected, got "%s".', $token)); + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws \RuntimeException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws \RuntimeException When option given doesn't exist + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + // Convert empty values to null + if (!isset($value[0])) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + + if (null === $value && $option->acceptValue() && count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = null; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = 0 === strpos($value, '--') ? $value.'=' : $value; + if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) { + return true; + } + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < count($tokens)) { + $token = array_shift($tokens); + + foreach ($values as $value) { + if ($token === $value) { + return array_shift($tokens); + } + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = 0 === strpos($value, '--') ? $value.'=' : $value; + if ('' !== $leading && 0 === strpos($token, $leading)) { + return substr($token, strlen($leading)); + } + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $self = $this; + $tokens = array_map(function ($token) use ($self) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1].$self->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $self->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/core/vendor/symfony/console/Input/ArrayInput.php b/core/vendor/symfony/console/Input/ArrayInput.php new file mode 100644 index 0000000..eb921fd --- /dev/null +++ b/core/vendor/symfony/console/Input/ArrayInput.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); + * + * @author Fabien Potencier + */ +class ArrayInput extends Input +{ + private $parameters; + + public function __construct(array $parameters, InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->parameters as $key => $value) { + if ($key && '-' === $key[0]) { + continue; + } + + return $value; + } + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (!is_int($k)) { + $v = $k; + } + + if (in_array($v, $values)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (is_int($k)) { + if (in_array($v, $values)) { + return true; + } + } elseif (in_array($k, $values)) { + return $v; + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $params = array(); + foreach ($this->parameters as $param => $val) { + if ($param && '-' === $param[0]) { + if (is_array($val)) { + foreach ($val as $v) { + $params[] = $param.('' != $v ? '='.$this->escapeToken($v) : ''); + } + } else { + $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); + } + } else { + $params[] = is_array($val) ? implode(' ', array_map(array($this, 'escapeToken'), $val)) : $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif ('-' === $key[0]) { + $this->addShortOption(substr($key, 1), $value); + } else { + $this->addArgument($key, $value); + } + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws \InvalidArgumentException When option given doesn't exist + * @throws \InvalidArgumentException When a required value is missing + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \InvalidArgumentException(sprintf('The "--%s" option requires a value.', $name)); + } + + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + + $this->options[$name] = $value; + } + + /** + * Adds an argument value. + * + * @param string $name The argument name + * @param mixed $value The value for the argument + * + * @throws \InvalidArgumentException When argument given doesn't exist + */ + private function addArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/core/vendor/symfony/console/Input/Input.php b/core/vendor/symfony/console/Input/Input.php new file mode 100644 index 0000000..16f8279 --- /dev/null +++ b/core/vendor/symfony/console/Input/Input.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * Input is the base class for all concrete Input classes. + * + * Three concrete classes are provided by default: + * + * * `ArgvInput`: The input comes from the CLI arguments (argv) + * * `StringInput`: The input is provided as a string + * * `ArrayInput`: The input is provided as an array + * + * @author Fabien Potencier + */ +abstract class Input implements InputInterface +{ + protected $definition; + protected $options = array(); + protected $arguments = array(); + protected $interactive = true; + + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + /** + * {@inheritdoc} + */ + public function bind(InputDefinition $definition) + { + $this->arguments = array(); + $this->options = array(); + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(); + + /** + * {@inheritdoc} + */ + public function validate() + { + $definition = $this->definition; + $givenArguments = $this->arguments; + + $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { + return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); + }); + + if (count($missingArguments) > 0) { + throw new \RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); + } + } + + /** + * {@inheritdoc} + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * {@inheritdoc} + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * {@inheritdoc} + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return $this->definition->hasOption($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars. + * + * @param string $token + * + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } +} diff --git a/core/vendor/symfony/console/Input/InputArgument.php b/core/vendor/symfony/console/Input/InputArgument.php new file mode 100644 index 0000000..29b8001 --- /dev/null +++ b/core/vendor/symfony/console/Input/InputArgument.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + */ +class InputArgument +{ + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * @param string $name The argument name + * @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param mixed $default The default value (for self::OPTIONAL mode only) + * + * @throws \InvalidArgumentException When argument mode is not valid + */ + public function __construct($name, $mode = null, $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * Returns the argument name. + * + * @return string The argument name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return bool true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * Returns true if the argument can take multiple values. + * + * @return bool true if mode is self::IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param mixed $default The default value + * + * @throws \LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = array(); + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * Returns the default value. + * + * @return mixed The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/core/vendor/symfony/console/Input/InputAwareInterface.php b/core/vendor/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 0000000..d0f11e9 --- /dev/null +++ b/core/vendor/symfony/console/Input/InputAwareInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputAwareInterface should be implemented by classes that depends on the + * Console Input. + * + * @author Wouter J + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + * + * @param InputInterface + */ + public function setInput(InputInterface $input); +} diff --git a/core/vendor/symfony/console/Input/InputDefinition.php b/core/vendor/symfony/console/Input/InputDefinition.php new file mode 100644 index 0000000..5e5a3ad --- /dev/null +++ b/core/vendor/symfony/console/Input/InputDefinition.php @@ -0,0 +1,446 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * A InputDefinition represents a set of valid command line arguments and options. + * + * Usage: + * + * $definition = new InputDefinition(array( + * new InputArgument('name', InputArgument::REQUIRED), + * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), + * )); + * + * @author Fabien Potencier + */ +class InputDefinition +{ + private $arguments; + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + private $options; + private $shortcuts; + + /** + * @param array $definition An array of InputArgument and InputOption instance + */ + public function __construct(array $definition = array()) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + */ + public function setDefinition(array $definition) + { + $arguments = array(); + $options = array(); + foreach ($definition as $item) { + if ($item instanceof InputOption) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * Sets the InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function setArguments($arguments = array()) + { + $this->arguments = array(); + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function addArguments($arguments = array()) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * @throws \LogicException When incorrect argument is given + */ + public function addArgument(InputArgument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new \LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new \LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @param string|int $name The InputArgument name or position + * + * @return InputArgument An InputArgument object + * + * @throws \InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] An array of InputArgument objects + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + * + * @return int The number of InputArguments + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return int The number of required InputArguments + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * Gets the default values. + * + * @return array An array of default values + */ + public function getArgumentDefaults() + { + $values = array(); + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * Sets the InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function setOptions($options = array()) + { + $this->options = array(); + $this->shortcuts = array(); + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function addOptions($options = array()) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @throws \LogicException When option given already exist + */ + public function addOption(InputOption $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { + throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * Returns an InputOption by name. + * + * @param string $name The InputOption name + * + * @return InputOption A InputOption object + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * Returns true if an InputOption object exists by name. + * + * This method can't be used to check if the user included the option when + * executing the command (use getOption() instead). + * + * @param string $name The InputOption name + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] An array of InputOption objects + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + * + * @param string $name The InputOption shortcut + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * Gets an InputOption by shortcut. + * + * @param string $shortcut The Shortcut name + * + * @return InputOption An InputOption object + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * Gets an array of default values. + * + * @return array An array of all default values + */ + public function getOptionDefaults() + { + $values = array(); + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * Returns the InputOption name given a shortcut. + * + * @param string $shortcut The shortcut + * + * @return string The InputOption name + * + * @throws \InvalidArgumentException When option given does not exist + */ + private function shortcutToName($shortcut) + { + if (!isset($this->shortcuts[$shortcut])) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * Gets the synopsis. + * + * @param bool $short Whether to return the short version (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $elements = array(); + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf( + ' %s%s%s', + $option->isValueOptional() ? '[' : '', + strtoupper($option->getName()), + $option->isValueOptional() ? ']' : '' + ); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<'.$argument->getName().'>'; + if (!$argument->isRequired()) { + $element = '['.$element.']'; + } elseif ($argument->isArray()) { + $element = $element.' ('.$element.')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } + + /** + * Returns a textual representation of the InputDefinition. + * + * @return string A string representing the InputDefinition + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $descriptor->describe($output, $this, array('raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the InputDefinition. + * + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the InputDefinition + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getInputDefinitionDocument($this); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this); + + return $output->fetch(); + } +} diff --git a/core/vendor/symfony/console/Input/InputInterface.php b/core/vendor/symfony/console/Input/InputInterface.php new file mode 100644 index 0000000..ac14de7 --- /dev/null +++ b/core/vendor/symfony/console/Input/InputInterface.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier + */ +interface InputInterface +{ + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string|null The value of the first argument or null otherwise + */ + public function getFirstArgument(); + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * + * @return bool true if the value is contained in the raw parameters + */ + public function hasParameterOption($values); + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false); + + /** + * Binds the current Input instance with the given arguments and options. + */ + public function bind(InputDefinition $definition); + + /** + * Validates the input. + * + * @throws \RuntimeException When not enough arguments are given + */ + public function validate(); + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(); + + /** + * Returns the argument value for a given argument name. + * + * @param string $name The argument name + * + * @return mixed The argument value + * + * @throws \InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name); + + /** + * Sets an argument value by name. + * + * @param string $name The argument name + * @param string $value The argument value + * + * @throws \InvalidArgumentException When argument given doesn't exist + */ + public function setArgument($name, $value); + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name); + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + public function getOptions(); + + /** + * Returns the option value for a given option name. + * + * @param string $name The option name + * + * @return mixed The option value + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + public function getOption($name); + + /** + * Sets an option value by name. + * + * @param string $name The option name + * @param string|bool $value The option value + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + public function setOption($name, $value); + + /** + * Returns true if an InputOption object exists by name. + * + * @param string $name The InputOption name + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name); + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + * + * @param bool $interactive If the input should be interactive + */ + public function setInteractive($interactive); +} diff --git a/core/vendor/symfony/console/Input/InputOption.php b/core/vendor/symfony/console/Input/InputOption.php new file mode 100644 index 0000000..c2b57c7 --- /dev/null +++ b/core/vendor/symfony/console/Input/InputOption.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + */ +class InputOption +{ + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * @param string $name The option name + * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int $mode The option mode: One of the VALUE_* constants + * @param string $description A description text + * @param mixed $default The default value (must be null for self::VALUE_NONE) + * + * @throws \InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new \InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new \InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + * + * @return string The shortcut + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * Returns the option name. + * + * @return string The name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the option accepts a value. + * + * @return bool true if value mode is not self::VALUE_NONE, false otherwise + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * Returns true if the option requires a value. + * + * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * Returns true if the option takes an optional value. + * + * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * Returns true if the option can take multiple values. + * + * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param mixed $default The default value + * + * @throws \LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = array(); + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * Returns the default value. + * + * @return mixed The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } + + /** + * Checks whether the given option equals this one. + * + * @return bool + */ + public function equals(self $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/core/vendor/symfony/console/Input/StringInput.php b/core/vendor/symfony/console/Input/StringInput.php new file mode 100644 index 0000000..754d712 --- /dev/null +++ b/core/vendor/symfony/console/Input/StringInput.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier + */ +class StringInput extends ArgvInput +{ + const REGEX_STRING = '([^\s]+?)(?:\s|(?setTokens($this->tokenize($input)); + + if (null !== $definition) { + $this->bind($definition); + } + } + + /** + * Tokenizes a string. + * + * @param string $input The input to tokenize + * + * @return array An array of tokens + * + * @throws \InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize($input) + { + $tokens = array(); + $length = strlen($input); + $cursor = 0; + while ($cursor < $length) { + if (preg_match('/\s+/A', $input, $match, null, $cursor)) { + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { + $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); + } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes($match[1]); + } else { + // should never happen + throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); + } + + $cursor += strlen($match[0]); + } + + return $tokens; + } +} diff --git a/core/vendor/symfony/console/LICENSE b/core/vendor/symfony/console/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/core/vendor/symfony/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 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/core/vendor/symfony/console/Logger/ConsoleLogger.php b/core/vendor/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 0000000..d1aba10 --- /dev/null +++ b/core/vendor/symfony/console/Logger/ConsoleLogger.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas + * + * @see http://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + const INFO = 'info'; + const ERROR = 'error'; + + private $output; + private $verbosityLevelMap = array( + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ); + private $formatLevelMap = array( + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ); + + public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array()) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = array()) + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + // Write to the error output if necessary and available + if (self::ERROR === $this->formatLevelMap[$level] && $this->output instanceof ConsoleOutputInterface) { + $output = $this->output->getErrorOutput(); + } else { + $output = $this->output; + } + + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context))); + } + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + * + * @param string $message + * @param array $context + * + * @return string + */ + private function interpolate($message, array $context) + { + // build a replacement array with braces around the context keys + $replace = array(); + foreach ($context as $key => $val) { + if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { + $replace[sprintf('{%s}', $key)] = $val; + } + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } +} diff --git a/core/vendor/symfony/console/Output/BufferedOutput.php b/core/vendor/symfony/console/Output/BufferedOutput.php new file mode 100644 index 0000000..8afc893 --- /dev/null +++ b/core/vendor/symfony/console/Output/BufferedOutput.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * @author Jean-François Simon + */ +class BufferedOutput extends Output +{ + private $buffer = ''; + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= PHP_EOL; + } + } +} diff --git a/core/vendor/symfony/console/Output/ConsoleOutput.php b/core/vendor/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 0000000..84efb98 --- /dev/null +++ b/core/vendor/symfony/console/Output/ConsoleOutput.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * ConsoleOutput is the default class for all CLI output. It uses STDOUT. + * + * This class is a convenient wrapper around `StreamOutput`. + * + * $output = new ConsoleOutput(); + * + * This is equivalent to: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * @author Fabien Potencier + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + private $stderr; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + + $actualDecorated = $this->isDecorated(); + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); + + if (null === $decorated) { + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); + } + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getErrorOutput() + { + return $this->stderr; + } + + /** + * {@inheritdoc} + */ + public function setErrorOutput(OutputInterface $error) + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + * + * @return bool + */ + private function isRunningOS400() + { + $checks = array( + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ); + + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * @return resource + */ + private function openOutputStream() + { + $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output'; + + return @fopen($outputStream, 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output'; + + return fopen($errorStream, 'w'); + } +} diff --git a/core/vendor/symfony/console/Output/ConsoleOutputInterface.php b/core/vendor/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 0000000..b44ea7e --- /dev/null +++ b/core/vendor/symfony/console/Output/ConsoleOutputInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. + * This adds information about stderr output stream. + * + * @author Dariusz Górecki + */ +interface ConsoleOutputInterface extends OutputInterface +{ + /** + * Gets the OutputInterface for errors. + * + * @return OutputInterface + */ + public function getErrorOutput(); + + public function setErrorOutput(OutputInterface $error); +} diff --git a/core/vendor/symfony/console/Output/NullOutput.php b/core/vendor/symfony/console/Output/NullOutput.php new file mode 100644 index 0000000..bd3b7e6 --- /dev/null +++ b/core/vendor/symfony/console/Output/NullOutput.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * NullOutput suppresses all output. + * + * $output = new NullOutput(); + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class NullOutput implements OutputInterface +{ + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + // to comply with the interface we must return a OutputFormatterInterface + return new OutputFormatter(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return self::VERBOSITY_QUIET; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + // do nothing + } +} diff --git a/core/vendor/symfony/console/Output/Output.php b/core/vendor/symfony/console/Output/Output.php new file mode 100644 index 0000000..e7e165e --- /dev/null +++ b/core/vendor/symfony/console/Output/Output.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Base class for output classes. + * + * There are five levels of verbosity: + * + * * normal: no option passed (normal output) + * * verbose: -v (more output) + * * very verbose: -vv (highly extended output) + * * debug: -vvv (all debug output) + * * quiet: -q (no output) + * + * @author Fabien Potencier + */ +abstract class Output implements OutputInterface +{ + private $verbosity; + private $formatter; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null) + { + $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; + $this->formatter = $formatter ?: new OutputFormatter(); + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->formatter; + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->formatter->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + if (self::VERBOSITY_QUIET === $this->verbosity) { + return; + } + + $messages = (array) $messages; + + foreach ($messages as $message) { + switch ($type) { + case OutputInterface::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case OutputInterface::OUTPUT_RAW: + break; + case OutputInterface::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline); + } + } + + /** + * Writes a message to the output. + * + * @param string $message A message to write to the output + * @param bool $newline Whether to add a newline or not + */ + abstract protected function doWrite($message, $newline); +} diff --git a/core/vendor/symfony/console/Output/OutputInterface.php b/core/vendor/symfony/console/Output/OutputInterface.php new file mode 100644 index 0000000..ff007e6 --- /dev/null +++ b/core/vendor/symfony/console/Output/OutputInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier + */ +interface OutputInterface +{ + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + /** + * Writes a message to the output. + * + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline + * @param int $type The type of output (one of the OUTPUT constants) + * + * @throws \InvalidArgumentException When unknown output type is given + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL); + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param string|array $messages The message as an array of lines or a single string + * @param int $type The type of output (one of the OUTPUT constants) + * + * @throws \InvalidArgumentException When unknown output type is given + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL); + + /** + * Sets the verbosity of the output. + * + * @param int $level The level of verbosity (one of the VERBOSITY constants) + */ + public function setVerbosity($level); + + /** + * Gets the current verbosity of the output. + * + * @return int The current level of verbosity (one of the VERBOSITY constants) + */ + public function getVerbosity(); + + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + public function setFormatter(OutputFormatterInterface $formatter); + + /** + * Returns current output formatter instance. + * + * @return OutputFormatterInterface + */ + public function getFormatter(); +} diff --git a/core/vendor/symfony/console/Output/StreamOutput.php b/core/vendor/symfony/console/Output/StreamOutput.php new file mode 100644 index 0000000..b0897b2 --- /dev/null +++ b/core/vendor/symfony/console/Output/StreamOutput.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * StreamOutput writes the output to a given stream. + * + * Usage: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * As `StreamOutput` can use any stream, you can also use a file: + * + * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); + * + * @author Fabien Potencier + */ +class StreamOutput extends Output +{ + private $stream; + + /** + * @param resource $stream A stream resource + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @throws \InvalidArgumentException When first argument is not a real stream + */ + public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { + throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); + } + + $this->stream = $stream; + + if (null === $decorated) { + $decorated = $this->hasColorSupport(); + } + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource A stream resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + if (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) { + // should never happen + throw new \RuntimeException('Unable to write output.'); + } + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @return bool true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport() + { + if (DIRECTORY_SEPARATOR === '\\') { + return (function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($this->stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (function_exists('stream_isatty')) { + return @stream_isatty($this->stream); + } + + if (function_exists('posix_isatty')) { + return @posix_isatty($this->stream); + } + + $stat = @fstat($this->stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } +} diff --git a/core/vendor/symfony/console/Question/ChoiceQuestion.php b/core/vendor/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 0000000..7417f67 --- /dev/null +++ b/core/vendor/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a choice question. + * + * @author Fabien Potencier + */ +class ChoiceQuestion extends Question +{ + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * @param string $question The question to ask to the user + * @param array $choices The list of available choices + * @param mixed $default The default answer to return + */ + public function __construct($question, array $choices, $default = null) + { + if (!$choices) { + throw new \LogicException('Choice question must have at least 1 choice available.'); + } + + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * Returns available choices. + * + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @param bool $multiselect + * + * @return $this + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns whether the choices are multiselect. + * + * @return bool + */ + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * Gets the prompt for choices. + * + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @param string $prompt + * + * @return $this + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Sets the error message for invalid values. + * + * The error message has a string placeholder (%s) for the invalid value. + * + * @param string $errorMessage + * + * @return $this + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns the default answer validator. + * + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = array($selected); + } + + $multiselectChoices = array(); + foreach ($selectedChoices as $value) { + $results = array(); + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (false !== $result) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (false === $result && isset($choices[$value])) { + $result = $value; + } + + if (false === $result) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + + $multiselectChoices[] = (string) $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/core/vendor/symfony/console/Question/ConfirmationQuestion.php b/core/vendor/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 0000000..40f54b4 --- /dev/null +++ b/core/vendor/symfony/console/Question/ConfirmationQuestion.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a yes/no question. + * + * @author Fabien Potencier + */ +class ConfirmationQuestion extends Question +{ + private $trueAnswerRegex; + + /** + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param string $trueAnswerRegex A regex to match the "yes" answer + */ + public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * Returns the default answer normalizer. + * + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/core/vendor/symfony/console/Question/Question.php b/core/vendor/symfony/console/Question/Question.php new file mode 100644 index 0000000..d94836b --- /dev/null +++ b/core/vendor/symfony/console/Question/Question.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a Question. + * + * @author Fabien Potencier + */ +class Question +{ + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * @param string $question The question to ask to the user + * @param mixed $default The default answer to return if the user enters nothing + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * Returns the question. + * + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * Returns the default answer. + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns whether the user response must be hidden. + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @param bool $hidden + * + * @return $this + * + * @throws \LogicException In case the autocompleter is also used + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * In case the response can not be hidden, whether to fallback on non-hidden question or not. + * + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response can not be hidden. + * + * @param bool $fallback + * + * @return $this + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + * + * @return null|iterable + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * Sets values for the autocompleter. + * + * @param null|iterable $values + * + * @return $this + * + * @throws \InvalidArgumentException + * @throws \LogicException + */ + public function setAutocompleterValues($values) + { + if (is_array($values)) { + $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); + } + + if (null !== $values && !is_array($values) && !$values instanceof \Traversable) { + throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.'); + } + + if ($this->hidden) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * Sets a validator for the question. + * + * @param null|callable $validator + * + * @return $this + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } + + /** + * Gets the validator for the question. + * + * @return null|callable + */ + public function getValidator() + { + return $this->validator; + } + + /** + * Sets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @param null|int $attempts + * + * @return $this + * + * @throws \InvalidArgumentException in case the number of attempts is invalid + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * Gets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return null|int + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * Sets a normalizer for the response. + * + * The normalizer can be a callable (a string), a closure or a class implementing __invoke. + * + * @param callable $normalizer + * + * @return $this + */ + public function setNormalizer($normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * Gets the normalizer for the response. + * + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * + * @return callable + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/core/vendor/symfony/console/README.md b/core/vendor/symfony/console/README.md new file mode 100644 index 0000000..664a37c --- /dev/null +++ b/core/vendor/symfony/console/README.md @@ -0,0 +1,20 @@ +Console Component +================= + +The Console component eases the creation of beautiful and testable command line +interfaces. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/console/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +Credits +------- + +`Resources/bin/hiddeninput.exe` is a third party binary provided within this +component. Find sources and license at https://github.com/Seldaek/hidden-input. diff --git a/core/vendor/symfony/console/Resources/bin/hiddeninput.exe b/core/vendor/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/core/vendor/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/core/vendor/symfony/console/Shell.php b/core/vendor/symfony/console/Shell.php new file mode 100644 index 0000000..b2e234c --- /dev/null +++ b/core/vendor/symfony/console/Shell.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Process\ProcessBuilder; +use Symfony\Component\Process\PhpExecutableFinder; + +/** + * A Shell wraps an Application to add shell capabilities to it. + * + * Support for history and completion only works with a PHP compiled + * with readline support (either --with-readline or --with-libedit) + * + * @author Fabien Potencier + * @author Martin Hasoň + */ +class Shell +{ + private $application; + private $history; + private $output; + private $hasReadline; + private $processIsolation = false; + + /** + * If there is no readline support for the current PHP executable + * a \RuntimeException exception is thrown. + */ + public function __construct(Application $application) + { + $this->hasReadline = function_exists('readline'); + $this->application = $application; + $this->history = getenv('HOME').'/.history_'.$application->getName(); + $this->output = new ConsoleOutput(); + } + + /** + * Runs the shell. + */ + public function run() + { + $this->application->setAutoExit(false); + $this->application->setCatchExceptions(true); + + if ($this->hasReadline) { + readline_read_history($this->history); + readline_completion_function(array($this, 'autocompleter')); + } + + $this->output->writeln($this->getHeader()); + $php = null; + if ($this->processIsolation) { + $finder = new PhpExecutableFinder(); + $php = $finder->find(); + $this->output->writeln(<<<'EOF' +Running with process isolation, you should consider this: + * each command is executed as separate process, + * commands don't support interactivity, all params must be passed explicitly, + * commands output is not colorized. + +EOF + ); + } + + while (true) { + $command = $this->readline(); + + if (false === $command) { + $this->output->writeln("\n"); + + break; + } + + if ($this->hasReadline) { + readline_add_history($command); + readline_write_history($this->history); + } + + if ($this->processIsolation) { + $pb = new ProcessBuilder(); + + $process = $pb + ->add($php) + ->add($_SERVER['argv'][0]) + ->add($command) + ->inheritEnvironmentVariables(true) + ->getProcess() + ; + + $output = $this->output; + $process->run(function ($type, $data) use ($output) { + $output->writeln($data); + }); + + $ret = $process->getExitCode(); + } else { + $ret = $this->application->run(new StringInput($command), $this->output); + } + + if (0 !== $ret) { + $this->output->writeln(sprintf('The command terminated with an error status (%s)', $ret)); + } + } + } + + /** + * Returns the shell header. + * + * @return string The header string + */ + protected function getHeader() + { + return <<{$this->application->getName()} shell ({$this->application->getVersion()}). + +At the prompt, type help for some help, +or list to get a list of available commands. + +To exit the shell, type ^D. + +EOF; + } + + /** + * Renders a prompt. + * + * @return string The prompt + */ + protected function getPrompt() + { + // using the formatter here is required when using readline + return $this->output->getFormatter()->format($this->application->getName().' > '); + } + + protected function getOutput() + { + return $this->output; + } + + protected function getApplication() + { + return $this->application; + } + + /** + * Tries to return autocompletion for the current entered text. + * + * @param string $text The last segment of the entered text + * + * @return bool|array A list of guessed strings or true + */ + private function autocompleter($text) + { + $info = readline_info(); + $text = substr($info['line_buffer'], 0, $info['end']); + + if ($info['point'] !== $info['end']) { + return true; + } + + // task name? + if (false === strpos($text, ' ') || !$text) { + return array_keys($this->application->all()); + } + + // options and arguments? + try { + $command = $this->application->find(substr($text, 0, strpos($text, ' '))); + } catch (\Exception $e) { + return true; + } + + $list = array('--help'); + foreach ($command->getDefinition()->getOptions() as $option) { + $list[] = '--'.$option->getName(); + } + + return $list; + } + + /** + * Reads a single line from standard input. + * + * @return string The single line from standard input + */ + private function readline() + { + if ($this->hasReadline) { + $line = readline($this->getPrompt()); + } else { + $this->output->write($this->getPrompt()); + $line = fgets(STDIN, 1024); + $line = (false === $line || '' === $line) ? false : rtrim($line); + } + + return $line; + } + + public function getProcessIsolation() + { + return $this->processIsolation; + } + + public function setProcessIsolation($processIsolation) + { + $this->processIsolation = (bool) $processIsolation; + + if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) { + throw new \RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.'); + } + } +} diff --git a/core/vendor/symfony/console/Style/OutputStyle.php b/core/vendor/symfony/console/Style/OutputStyle.php new file mode 100644 index 0000000..f3bf291 --- /dev/null +++ b/core/vendor/symfony/console/Style/OutputStyle.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Decorates output to add console style guide helpers. + * + * @author Kevin Bond + */ +abstract class OutputStyle implements OutputInterface, StyleInterface +{ + private $output; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + $this->output->write(str_repeat(PHP_EOL, $count)); + } + + /** + * @param int $max + * + * @return ProgressBar + */ + public function createProgressBar($max = 0) + { + return new ProgressBar($this->output, $max); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->output->write($messages, $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->output->writeln($messages, $type); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->output->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->output->getVerbosity(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->output->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->output->getFormatter(); + } +} diff --git a/core/vendor/symfony/console/Style/StyleInterface.php b/core/vendor/symfony/console/Style/StyleInterface.php new file mode 100644 index 0000000..475c268 --- /dev/null +++ b/core/vendor/symfony/console/Style/StyleInterface.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond + */ +interface StyleInterface +{ + /** + * Formats a command title. + * + * @param string $message + */ + public function title($message); + + /** + * Formats a section title. + * + * @param string $message + */ + public function section($message); + + /** + * Formats a list. + */ + public function listing(array $elements); + + /** + * Formats informational text. + * + * @param string|array $message + */ + public function text($message); + + /** + * Formats a success result bar. + * + * @param string|array $message + */ + public function success($message); + + /** + * Formats an error result bar. + * + * @param string|array $message + */ + public function error($message); + + /** + * Formats an warning result bar. + * + * @param string|array $message + */ + public function warning($message); + + /** + * Formats a note admonition. + * + * @param string|array $message + */ + public function note($message); + + /** + * Formats a caution admonition. + * + * @param string|array $message + */ + public function caution($message); + + /** + * Formats a table. + */ + public function table(array $headers, array $rows); + + /** + * Asks a question. + * + * @param string $question + * @param string|null $default + * @param callable|null $validator + * + * @return mixed + */ + public function ask($question, $default = null, $validator = null); + + /** + * Asks a question with the user input hidden. + * + * @param string $question + * @param callable|null $validator + * + * @return mixed + */ + public function askHidden($question, $validator = null); + + /** + * Asks for confirmation. + * + * @param string $question + * @param bool $default + * + * @return bool + */ + public function confirm($question, $default = true); + + /** + * Asks a choice question. + * + * @param string $question + * @param array $choices + * @param string|int|null $default + * + * @return mixed + */ + public function choice($question, array $choices, $default = null); + + /** + * Add newline(s). + * + * @param int $count The number of newlines + */ + public function newLine($count = 1); + + /** + * Starts the progress output. + * + * @param int $max Maximum steps (0 if unknown) + */ + public function progressStart($max = 0); + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function progressAdvance($step = 1); + + /** + * Finishes the progress output. + */ + public function progressFinish(); +} diff --git a/core/vendor/symfony/console/Style/SymfonyStyle.php b/core/vendor/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 0000000..bf25c06 --- /dev/null +++ b/core/vendor/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,414 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\SymfonyQuestionHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond + */ +class SymfonyStyle extends OutputStyle +{ + const MAX_LINE_LENGTH = 120; + + private $input; + private $questionHelper; + private $progressBar; + private $lineLength; + private $bufferedOutput; + + public function __construct(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); + // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. + $this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); + + parent::__construct($output); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string|null $type The block type (added in [] on first line) + * @param string|null $style The style to apply to the whole block + * @param string $prefix The prefix for the block + * @param bool $padding Whether to add vertical padding + */ + public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false) + { + $this->autoPrependBlock(); + $messages = is_array($messages) ? array_values($messages) : array($messages); + $indentLength = 0; + $lines = array(); + + if (null !== $type) { + $typePrefix = sprintf('[%s] ', $type); + $indentLength = strlen($typePrefix); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + foreach ($messages as $key => $message) { + $message = OutputFormatter::escape($message); + $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - Helper::strlen($prefix) - $indentLength, PHP_EOL, true))); + + // prefix each line with a number of spaces equivalent to the type length + if (null !== $type) { + foreach ($lines as &$line) { + $line = $lineIndentation === substr($line, 0, $indentLength) ? $line : $lineIndentation.$line; + } + } + + if (count($messages) > 1 && $key < count($messages) - 1) { + $lines[] = ''; + } + } + + if (null !== $type) { + $lines[0] = substr_replace($lines[0], $typePrefix, 0, $indentLength); + } + + if ($padding && $this->isDecorated()) { + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as &$line) { + $line = sprintf('%s%s', $prefix, $line); + $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + $this->writeln($lines); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function title($message) + { + $this->autoPrependBlock(); + $this->writeln(array( + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('=', strlen($message))), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function section($message) + { + $this->autoPrependBlock(); + $this->writeln(array( + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('-', strlen($message))), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function listing(array $elements) + { + $this->autoPrependText(); + $elements = array_map(function ($element) { + return sprintf(' * %s', $element); + }, $elements); + + $this->writeln($elements); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function text($message) + { + $this->autoPrependText(); + + if (!is_array($message)) { + $this->writeln(sprintf(' // %s', $message)); + + return; + } + + foreach ($message as $element) { + $this->text($element); + } + } + + /** + * {@inheritdoc} + */ + public function success($message) + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function error($message) + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function warning($message) + { + $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function note($message) + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * {@inheritdoc} + */ + public function caution($message) + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + /** + * {@inheritdoc} + */ + public function table(array $headers, array $rows) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle($style); + + $table->render(); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function ask($question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function askHidden($question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function confirm($question, $default = true) + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice($question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); + } + + /** + * {@inheritdoc} + */ + public function progressStart($max = 0) + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + /** + * {@inheritdoc} + */ + public function progressAdvance($step = 1) + { + $this->getProgressBar()->advance($step); + } + + /** + * {@inheritdoc} + */ + public function progressFinish() + { + $this->getProgressBar()->finish(); + $this->newLine(2); + $this->progressBar = null; + } + + /** + * {@inheritdoc} + */ + public function createProgressBar($max = 0) + { + $progressBar = parent::createProgressBar($max); + + if ('\\' !== DIRECTORY_SEPARATOR) { + $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $progressBar->setProgressCharacter(''); + $progressBar->setBarCharacter('▓'); // dark shade character \u2593 + } + + return $progressBar; + } + + /** + * @return mixed + */ + public function askQuestion(Question $question) + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + if (!$this->questionHelper) { + $this->questionHelper = new SymfonyQuestionHelper(); + } + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + parent::writeln($messages, $type); + $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + parent::write($messages, $newline, $type); + $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * @return ProgressBar + */ + private function getProgressBar() + { + if (!$this->progressBar) { + throw new \RuntimeException('The ProgressBar is not started.'); + } + + return $this->progressBar; + } + + private function getTerminalWidth() + { + $application = new Application(); + $dimensions = $application->getTerminalDimensions(); + + return $dimensions[0] ?: self::MAX_LINE_LENGTH; + } + + private function autoPrependBlock() + { + $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); + + if (!isset($chars[0])) { + return $this->newLine(); //empty history, so we should start with a new line. + } + //Prepend new line for each non LF chars (This means no blank line was output before) + $this->newLine(2 - substr_count($chars, "\n")); + } + + private function autoPrependText() + { + $fetched = $this->bufferedOutput->fetch(); + //Prepend new line if last char isn't EOL: + if ("\n" !== substr($fetched, -1)) { + $this->newLine(); + } + } + + private function reduceBuffer($messages) + { + // We need to know if the two last chars are PHP_EOL + // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer + return array_map(function ($value) { + return substr($value, -4); + }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages)); + } +} diff --git a/core/vendor/symfony/console/Tester/ApplicationTester.php b/core/vendor/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 0000000..dc12164 --- /dev/null +++ b/core/vendor/symfony/console/Tester/ApplicationTester.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier + */ +class ApplicationTester +{ + private $application; + private $input; + private $output; + private $statusCode; + + public function __construct(Application $application) + { + $this->application = $application; + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of arguments and options + * @param array $options An array of options + * + * @return int The command exit code + */ + public function run(array $input, $options = array()) + { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->statusCode = $this->application->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the application. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the application. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} diff --git a/core/vendor/symfony/console/Tester/CommandTester.php b/core/vendor/symfony/console/Tester/CommandTester.php new file mode 100644 index 0000000..a194949 --- /dev/null +++ b/core/vendor/symfony/console/Tester/CommandTester.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + */ +class CommandTester +{ + private $command; + private $input; + private $output; + private $statusCode; + + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available execution options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = array()) + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(array('command' => $this->command->getName()), $input); + } + + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false); + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->statusCode = $this->command->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the command. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} diff --git a/core/vendor/symfony/console/Tests/ApplicationTest.php b/core/vendor/symfony/console/Tests/ApplicationTest.php new file mode 100644 index 0000000..5ab3b0a --- /dev/null +++ b/core/vendor/symfony/console/Tests/ApplicationTest.php @@ -0,0 +1,1276 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Tester\ApplicationTester; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class ApplicationTest extends TestCase +{ + protected static $fixturesPath; + + public static function setUpBeforeClass() + { + self::$fixturesPath = realpath(__DIR__.'/Fixtures/'); + require_once self::$fixturesPath.'/FooCommand.php'; + require_once self::$fixturesPath.'/FooOptCommand.php'; + require_once self::$fixturesPath.'/Foo1Command.php'; + require_once self::$fixturesPath.'/Foo2Command.php'; + require_once self::$fixturesPath.'/Foo3Command.php'; + require_once self::$fixturesPath.'/Foo4Command.php'; + require_once self::$fixturesPath.'/Foo5Command.php'; + require_once self::$fixturesPath.'/FoobarCommand.php'; + require_once self::$fixturesPath.'/BarBucCommand.php'; + require_once self::$fixturesPath.'/FooSubnamespaced1Command.php'; + require_once self::$fixturesPath.'/FooSubnamespaced2Command.php'; + require_once self::$fixturesPath.'/TestTiti.php'; + require_once self::$fixturesPath.'/TestToto.php'; + } + + protected function normalizeLineBreaks($text) + { + return str_replace(PHP_EOL, "\n", $text); + } + + /** + * Replaces the dynamic placeholders of the command help text with a static version. + * The placeholder %command.full_name% includes the script path that is not predictable + * and can not be tested against. + */ + protected function ensureStaticCommandHelp(Application $application) + { + foreach ($application->all() as $command) { + $command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp())); + } + } + + public function testConstructor() + { + $application = new Application('foo', 'bar'); + $this->assertEquals('foo', $application->getName(), '__construct() takes the application name as its first argument'); + $this->assertEquals('bar', $application->getVersion(), '__construct() takes the application version as its second argument'); + $this->assertEquals(array('help', 'list'), array_keys($application->all()), '__construct() registered the help and list commands by default'); + } + + public function testSetGetName() + { + $application = new Application(); + $application->setName('foo'); + $this->assertEquals('foo', $application->getName(), '->setName() sets the name of the application'); + } + + public function testSetGetVersion() + { + $application = new Application(); + $application->setVersion('bar'); + $this->assertEquals('bar', $application->getVersion(), '->setVersion() sets the version of the application'); + } + + public function testGetLongVersion() + { + $application = new Application('foo', 'bar'); + $this->assertEquals('foo version bar', $application->getLongVersion(), '->getLongVersion() returns the long version of the application'); + } + + public function testHelp() + { + $application = new Application(); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_gethelp.txt', $this->normalizeLineBreaks($application->getHelp()), '->getHelp() returns a help message'); + } + + public function testAll() + { + $application = new Application(); + $commands = $application->all(); + $this->assertInstanceOf('Symfony\\Component\\Console\\Command\\HelpCommand', $commands['help'], '->all() returns the registered commands'); + + $application->add(new \FooCommand()); + $commands = $application->all('foo'); + $this->assertCount(1, $commands, '->all() takes a namespace as its first argument'); + } + + public function testRegister() + { + $application = new Application(); + $command = $application->register('foo'); + $this->assertEquals('foo', $command->getName(), '->register() registers a new command'); + } + + public function testAdd() + { + $application = new Application(); + $application->add($foo = new \FooCommand()); + $commands = $application->all(); + $this->assertEquals($foo, $commands['foo:bar'], '->add() registers a command'); + + $application = new Application(); + $application->addCommands(array($foo = new \FooCommand(), $foo1 = new \Foo1Command())); + $commands = $application->all(); + $this->assertEquals(array($foo, $foo1), array($commands['foo:bar'], $commands['foo:bar1']), '->addCommands() registers an array of commands'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Command class "Foo5Command" is not correctly initialized. You probably forgot to call the parent constructor. + */ + public function testAddCommandWithEmptyConstructor() + { + $application = new Application(); + $application->add(new \Foo5Command()); + } + + public function testHasGet() + { + $application = new Application(); + $this->assertTrue($application->has('list'), '->has() returns true if a named command is registered'); + $this->assertFalse($application->has('afoobar'), '->has() returns false if a named command is not registered'); + + $application->add($foo = new \FooCommand()); + $this->assertTrue($application->has('afoobar'), '->has() returns true if an alias is registered'); + $this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name'); + $this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias'); + + $application = new Application(); + $application->add($foo = new \FooCommand()); + // simulate --help + $r = new \ReflectionObject($application); + $p = $r->getProperty('wantHelps'); + $p->setAccessible(true); + $p->setValue($application, true); + $command = $application->get('foo:bar'); + $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $command, '->get() returns the help command if --help is provided as the input'); + } + + public function testSilentHelp() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $tester = new ApplicationTester($application); + $tester->run(array('-h' => true, '-q' => true), array('decorated' => false)); + + $this->assertEmpty($tester->getDisplay(true)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The command "foofoo" does not exist. + */ + public function testGetInvalidCommand() + { + $application = new Application(); + $application->get('foofoo'); + } + + public function testGetNamespaces() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $this->assertEquals(array('foo'), $application->getNamespaces(), '->getNamespaces() returns an array of unique used namespaces'); + } + + public function testFindNamespace() + { + $application = new Application(); + $application->add(new \FooCommand()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists'); + $this->assertEquals('foo', $application->findNamespace('f'), '->findNamespace() finds a namespace given an abbreviation'); + $application->add(new \Foo2Command()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists'); + } + + public function testFindNamespaceWithSubnamespaces() + { + $application = new Application(); + $application->add(new \FooSubnamespaced1Command()); + $application->add(new \FooSubnamespaced2Command()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns commands even if the commands are only contained in subnamespaces'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The namespace "f" is ambiguous (foo, foo1). + */ + public function testFindAmbiguousNamespace() + { + $application = new Application(); + $application->add(new \BarBucCommand()); + $application->add(new \FooCommand()); + $application->add(new \Foo2Command()); + $application->findNamespace('f'); + } + + public function testFindNonAmbiguous() + { + $application = new Application(); + $application->add(new \TestTiti()); + $application->add(new \TestToto()); + $this->assertEquals('test-toto', $application->find('test')->getName()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage There are no commands defined in the "bar" namespace. + */ + public function testFindInvalidNamespace() + { + $application = new Application(); + $application->findNamespace('bar'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Command "foo1" is not defined + */ + public function testFindUniqueNameButNamespaceName() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + $application->find($commandName = 'foo1'); + } + + public function testFind() + { + $application = new Application(); + $application->add(new \FooCommand()); + + $this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists'); + $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists'); + $this->assertInstanceOf('FooCommand', $application->find('f:bar'), '->find() returns a command if the abbreviation for the namespace exists'); + $this->assertInstanceOf('FooCommand', $application->find('f:b'), '->find() returns a command if the abbreviation for the namespace and the command name exist'); + $this->assertInstanceOf('FooCommand', $application->find('a'), '->find() returns a command if the abbreviation exists for an alias'); + } + + /** + * @dataProvider provideAmbiguousAbbreviations + */ + public function testFindWithAmbiguousAbbreviations($abbreviation, $expectedExceptionMessage) + { + if (method_exists($this, 'expectException')) { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage($expectedExceptionMessage); + } else { + $this->setExpectedException('InvalidArgumentException', $expectedExceptionMessage); + } + + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + $application->find($abbreviation); + } + + public function provideAmbiguousAbbreviations() + { + return array( + array('f', 'Command "f" is not defined.'), + array('a', 'Command "a" is ambiguous (afoobar, afoobar1 and 1 more).'), + array('foo:b', 'Command "foo:b" is ambiguous (foo:bar, foo:bar1 and 1 more).'), + ); + } + + public function testFindCommandEqualNamespace() + { + $application = new Application(); + $application->add(new \Foo3Command()); + $application->add(new \Foo4Command()); + + $this->assertInstanceOf('Foo3Command', $application->find('foo3:bar'), '->find() returns the good command even if a namespace has same name'); + $this->assertInstanceOf('Foo4Command', $application->find('foo3:bar:toh'), '->find() returns a command even if its namespace equals another command name'); + } + + public function testFindCommandWithAmbiguousNamespacesButUniqueName() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \FoobarCommand()); + + $this->assertInstanceOf('FoobarCommand', $application->find('f:f')); + } + + public function testFindCommandWithMissingNamespace() + { + $application = new Application(); + $application->add(new \Foo4Command()); + + $this->assertInstanceOf('Foo4Command', $application->find('f::t')); + } + + /** + * @dataProvider provideInvalidCommandNamesSingle + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Did you mean this + */ + public function testFindAlternativeExceptionMessageSingle($name) + { + $application = new Application(); + $application->add(new \Foo3Command()); + $application->find($name); + } + + public function provideInvalidCommandNamesSingle() + { + return array( + array('foo3:baR'), + array('foO3:bar'), + ); + } + + public function testFindAlternativeExceptionMessageMultiple() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + // Command + plural + try { + $application->find('foo:baR'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertRegExp('/foo1:bar/', $e->getMessage()); + $this->assertRegExp('/foo:bar/', $e->getMessage()); + } + + // Namespace + plural + try { + $application->find('foo2:bar'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertRegExp('/foo1/', $e->getMessage()); + } + + $application->add(new \Foo3Command()); + $application->add(new \Foo4Command()); + + // Subnamespace + plural + try { + $a = $application->find('foo3:'); + $this->fail('->find() should throw an \InvalidArgumentException if a command is ambiguous because of a subnamespace, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e); + $this->assertRegExp('/foo3:bar/', $e->getMessage()); + $this->assertRegExp('/foo3:bar:toh/', $e->getMessage()); + } + } + + public function testFindAlternativeCommands() + { + $application = new Application(); + + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + try { + $application->find($commandName = 'Unknown command'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist'); + $this->assertEquals(sprintf('Command "%s" is not defined.', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, without alternatives'); + } + + // Test if "bar1" command throw an "\InvalidArgumentException" and does not contain + // "foo:bar" as alternative because "bar1" is too far from "foo:bar" + try { + $application->find($commandName = 'bar1'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist'); + $this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertRegExp('/afoobar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "afoobar1"'); + $this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar1"'); + $this->assertNotRegExp('/foo:bar(?>!1)/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, without "foo:bar" alternative'); + } + } + + public function testFindAlternativeCommandsWithAnAlias() + { + $fooCommand = new \FooCommand(); + $fooCommand->setAliases(array('foo2')); + + $application = new Application(); + $application->add($fooCommand); + + $result = $application->find('foo'); + + $this->assertSame($fooCommand, $result); + } + + public function testFindAlternativeNamespace() + { + $application = new Application(); + + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + $application->add(new \Foo3Command()); + + try { + $application->find('Unknown-namespace:Unknown-command'); + $this->fail('->find() throws an \InvalidArgumentException if namespace does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if namespace does not exist'); + $this->assertEquals('There are no commands defined in the "Unknown-namespace" namespace.', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, without alternatives'); + } + + try { + $application->find('foo2:command'); + $this->fail('->find() throws an \InvalidArgumentException if namespace does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if namespace does not exist'); + $this->assertRegExp('/There are no commands defined in the "foo2" namespace./', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative'); + $this->assertRegExp('/foo/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo"'); + $this->assertRegExp('/foo1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo1"'); + $this->assertRegExp('/foo3/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo3"'); + } + } + + public function testFindNamespaceDoesNotFailOnDeepSimilarNamespaces() + { + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getNamespaces'))->getMock(); + $application->expects($this->once()) + ->method('getNamespaces') + ->will($this->returnValue(array('foo:sublong', 'bar:sub'))); + + $this->assertEquals('foo:sublong', $application->findNamespace('f:sub')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Command "foo::bar" is not defined. + */ + public function testFindWithDoubleColonInNameThrowsException() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo4Command()); + $application->find('foo::bar'); + } + + public function testSetCatchExceptions() + { + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getTerminalWidth'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(120)); + $tester = new ApplicationTester($application); + + $application->setCatchExceptions(true); + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->setCatchExceptions() sets the catch exception flag'); + + $application->setCatchExceptions(false); + try { + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->fail('->setCatchExceptions() sets the catch exception flag'); + } catch (\Exception $e) { + $this->assertInstanceOf('\Exception', $e, '->setCatchExceptions() sets the catch exception flag'); + $this->assertEquals('Command "foo" is not defined.', $e->getMessage(), '->setCatchExceptions() sets the catch exception flag'); + } + } + + /** + * @group legacy + */ + public function testLegacyAsText() + { + $application = new Application(); + $application->add(new \FooCommand()); + $this->ensureStaticCommandHelp($application); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_astext1.txt', $this->normalizeLineBreaks($application->asText()), '->asText() returns a text representation of the application'); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_astext2.txt', $this->normalizeLineBreaks($application->asText('foo')), '->asText() returns a text representation of the application'); + } + + /** + * @group legacy + */ + public function testLegacyAsXml() + { + $application = new Application(); + $application->add(new \FooCommand()); + $this->ensureStaticCommandHelp($application); + $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/application_asxml1.txt', $application->asXml(), '->asXml() returns an XML representation of the application'); + $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/application_asxml2.txt', $application->asXml('foo'), '->asXml() returns an XML representation of the application'); + } + + public function testRenderException() + { + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getTerminalWidth'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(120)); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->renderException() renders a pretty exception'); + + $tester->run(array('command' => 'foo'), array('decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE)); + $this->assertContains('Exception trace', $tester->getDisplay(), '->renderException() renders a pretty exception with a stack trace when verbosity is verbose'); + + $tester->run(array('command' => 'list', '--foo' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception2.txt', $tester->getDisplay(true), '->renderException() renders the command synopsis when an exception occurs in the context of a command'); + + $application->add(new \Foo3Command()); + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo3:bar'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $tester->run(array('command' => 'foo3:bar'), array('decorated' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3decorated.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getTerminalWidth'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(32)); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal'); + } + + /** + * @requires extension mbstring + */ + public function testRenderExceptionWithDoubleWidthCharacters() + { + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getTerminalWidth'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(120)); + $application->register('foo')->setCode(function () { + throw new \Exception('エラーメッセージ'); + }); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $tester->run(array('command' => 'foo'), array('decorated' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1decorated.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getTerminalWidth'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(32)); + $application->register('foo')->setCode(function () { + throw new \Exception('コマンドの実行中にエラーが発生しました。'); + }); + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal'); + } + + public function testRenderExceptionEscapesLines() + { + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getTerminalWidth'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(22)); + $application->register('foo')->setCode(function () { + throw new \Exception('dont break here !'); + }); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_escapeslines.txt', $tester->getDisplay(true), '->renderException() escapes lines containing formatting'); + } + + public function testRenderExceptionLineBreaks() + { + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getTerminalWidth'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(120)); + $application->register('foo')->setCode(function () { + throw new \InvalidArgumentException("\n\nline 1 with extra spaces \nline 2\n\nline 4\n"); + }); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_linebreaks.txt', $tester->getDisplay(true), '->renderException() keep multiple line breaks'); + } + + public function testRun() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add($command = new \Foo1Command()); + $_SERVER['argv'] = array('cli.php', 'foo:bar1'); + + ob_start(); + $application->run(); + ob_end_clean(); + + $this->assertInstanceOf('Symfony\Component\Console\Input\ArgvInput', $command->input, '->run() creates an ArgvInput by default if none is given'); + $this->assertInstanceOf('Symfony\Component\Console\Output\ConsoleOutput', $command->output, '->run() creates a ConsoleOutput by default if none is given'); + + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $this->ensureStaticCommandHelp($application); + $tester = new ApplicationTester($application); + + $tester->run(array(), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run1.txt', $tester->getDisplay(true), '->run() runs the list command if no argument is passed'); + + $tester->run(array('--help' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run2.txt', $tester->getDisplay(true), '->run() runs the help command if --help is passed'); + + $tester->run(array('-h' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run2.txt', $tester->getDisplay(true), '->run() runs the help command if -h is passed'); + + $tester->run(array('command' => 'list', '--help' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run3.txt', $tester->getDisplay(true), '->run() displays the help if --help is passed'); + + $tester->run(array('command' => 'list', '-h' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run3.txt', $tester->getDisplay(true), '->run() displays the help if -h is passed'); + + $tester->run(array('--ansi' => true)); + $this->assertTrue($tester->getOutput()->isDecorated(), '->run() forces color output if --ansi is passed'); + + $tester->run(array('--no-ansi' => true)); + $this->assertFalse($tester->getOutput()->isDecorated(), '->run() forces color output to be disabled if --no-ansi is passed'); + + $tester->run(array('--version' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run4.txt', $tester->getDisplay(true), '->run() displays the program version if --version is passed'); + + $tester->run(array('-V' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run4.txt', $tester->getDisplay(true), '->run() displays the program version if -v is passed'); + + $tester->run(array('command' => 'list', '--quiet' => true)); + $this->assertSame('', $tester->getDisplay(), '->run() removes all output if --quiet is passed'); + $this->assertFalse($tester->getInput()->isInteractive(), '->run() sets off the interactive mode if --quiet is passed'); + + $tester->run(array('command' => 'list', '-q' => true)); + $this->assertSame('', $tester->getDisplay(), '->run() removes all output if -q is passed'); + $this->assertFalse($tester->getInput()->isInteractive(), '->run() sets off the interactive mode if -q is passed'); + + $tester->run(array('command' => 'list', '--verbose' => true)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if --verbose is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 1)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if --verbose=1 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 2)); + $this->assertSame(Output::VERBOSITY_VERY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to very verbose if --verbose=2 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 3)); + $this->assertSame(Output::VERBOSITY_DEBUG, $tester->getOutput()->getVerbosity(), '->run() sets the output to debug if --verbose=3 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 4)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if unknown --verbose level is passed'); + + $tester->run(array('command' => 'list', '-v' => true)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $tester->run(array('command' => 'list', '-vv' => true)); + $this->assertSame(Output::VERBOSITY_VERY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $tester->run(array('command' => 'list', '-vvv' => true)); + $this->assertSame(Output::VERBOSITY_DEBUG, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add(new \FooCommand()); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo:bar', '--no-interaction' => true), array('decorated' => false)); + $this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if --no-interaction is passed'); + + $tester->run(array('command' => 'foo:bar', '-n' => true), array('decorated' => false)); + $this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if -n is passed'); + } + + /** + * Issue #9285. + * + * If the "verbose" option is just before an argument in ArgvInput, + * an argument value should not be treated as verbosity value. + * This test will fail with "Not enough arguments." if broken + */ + public function testVerboseValueNotBreakArguments() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add(new \FooCommand()); + + $output = new StreamOutput(fopen('php://memory', 'w', false)); + + $input = new ArgvInput(array('cli.php', '-v', 'foo:bar')); + $application->run($input, $output); + + $this->addToAssertionCount(1); + + $input = new ArgvInput(array('cli.php', '--verbose', 'foo:bar')); + $application->run($input, $output); + + $this->addToAssertionCount(1); + } + + public function testRunReturnsIntegerExitCode() + { + $exception = new \Exception('', 4); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('doRun'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->once()) + ->method('doRun') + ->will($this->throwException($exception)); + + $exitCode = $application->run(new ArrayInput(array()), new NullOutput()); + + $this->assertSame(4, $exitCode, '->run() returns integer exit code extracted from raised exception'); + } + + public function testRunReturnsExitCodeOneForExceptionCodeZero() + { + $exception = new \Exception('', 0); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('doRun'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->once()) + ->method('doRun') + ->will($this->throwException($exception)); + + $exitCode = $application->run(new ArrayInput(array()), new NullOutput()); + + $this->assertSame(1, $exitCode, '->run() returns exit code 1 when exception code is 0'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage An option with shortcut "e" already exists. + */ + public function testAddingOptionWithDuplicateShortcut() + { + $dispatcher = new EventDispatcher(); + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->setDispatcher($dispatcher); + + $application->getDefinition()->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'Environment')); + + $application + ->register('foo') + ->setAliases(array('f')) + ->setDefinition(array(new InputOption('survey', 'e', InputOption::VALUE_REQUIRED, 'My option with a shortcut.'))) + ->setCode(function (InputInterface $input, OutputInterface $output) {}) + ; + + $input = new ArrayInput(array('command' => 'foo')); + $output = new NullOutput(); + + $application->run($input, $output); + } + + /** + * @expectedException \LogicException + * @dataProvider getAddingAlreadySetDefinitionElementData + */ + public function testAddingAlreadySetDefinitionElementData($def) + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application + ->register('foo') + ->setDefinition(array($def)) + ->setCode(function (InputInterface $input, OutputInterface $output) {}) + ; + + $input = new ArrayInput(array('command' => 'foo')); + $output = new NullOutput(); + $application->run($input, $output); + } + + public function getAddingAlreadySetDefinitionElementData() + { + return array( + array(new InputArgument('command', InputArgument::REQUIRED)), + array(new InputOption('quiet', '', InputOption::VALUE_NONE)), + array(new InputOption('query', 'q', InputOption::VALUE_NONE)), + ); + } + + public function testGetDefaultHelperSetReturnsDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + $this->assertTrue($helperSet->has('dialog')); + $this->assertTrue($helperSet->has('progress')); + } + + public function testAddingSingleHelperSetOverwritesDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + + // no other default helper set should be returned + $this->assertFalse($helperSet->has('dialog')); + $this->assertFalse($helperSet->has('progress')); + } + + public function testOverwritingDefaultHelperSetOverwritesDefaultValues() + { + $application = new CustomApplication(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + + // no other default helper set should be returned + $this->assertFalse($helperSet->has('dialog')); + $this->assertFalse($helperSet->has('progress')); + } + + public function testGetDefaultInputDefinitionReturnsDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $inputDefinition = $application->getDefinition(); + + $this->assertTrue($inputDefinition->hasArgument('command')); + + $this->assertTrue($inputDefinition->hasOption('help')); + $this->assertTrue($inputDefinition->hasOption('quiet')); + $this->assertTrue($inputDefinition->hasOption('verbose')); + $this->assertTrue($inputDefinition->hasOption('version')); + $this->assertTrue($inputDefinition->hasOption('ansi')); + $this->assertTrue($inputDefinition->hasOption('no-ansi')); + $this->assertTrue($inputDefinition->hasOption('no-interaction')); + } + + public function testOverwritingDefaultInputDefinitionOverwritesDefaultValues() + { + $application = new CustomApplication(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $inputDefinition = $application->getDefinition(); + + // check whether the default arguments and options are not returned any more + $this->assertFalse($inputDefinition->hasArgument('command')); + + $this->assertFalse($inputDefinition->hasOption('help')); + $this->assertFalse($inputDefinition->hasOption('quiet')); + $this->assertFalse($inputDefinition->hasOption('verbose')); + $this->assertFalse($inputDefinition->hasOption('version')); + $this->assertFalse($inputDefinition->hasOption('ansi')); + $this->assertFalse($inputDefinition->hasOption('no-ansi')); + $this->assertFalse($inputDefinition->hasOption('no-interaction')); + + $this->assertTrue($inputDefinition->hasOption('custom')); + } + + public function testSettingCustomInputDefinitionOverwritesDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setDefinition(new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.')))); + + $inputDefinition = $application->getDefinition(); + + // check whether the default arguments and options are not returned any more + $this->assertFalse($inputDefinition->hasArgument('command')); + + $this->assertFalse($inputDefinition->hasOption('help')); + $this->assertFalse($inputDefinition->hasOption('quiet')); + $this->assertFalse($inputDefinition->hasOption('verbose')); + $this->assertFalse($inputDefinition->hasOption('version')); + $this->assertFalse($inputDefinition->hasOption('ansi')); + $this->assertFalse($inputDefinition->hasOption('no-ansi')); + $this->assertFalse($inputDefinition->hasOption('no-interaction')); + + $this->assertTrue($inputDefinition->hasOption('custom')); + } + + public function testRunWithDispatcher() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setDispatcher($this->getDispatcher()); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertEquals('before.foo.after.'.PHP_EOL, $tester->getDisplay()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage caught + */ + public function testRunWithExceptionAndDispatcher() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + throw new \RuntimeException('foo'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + } + + public function testRunDispatchesAllEventsWithException() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + + throw new \RuntimeException('foo'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertContains('before.foo.caught.after.', $tester->getDisplay()); + } + + public function testRunDispatchesAllEventsWithExceptionInListener() + { + $dispatcher = $this->getDispatcher(); + $dispatcher->addListener('console.command', function () { + throw new \RuntimeException('foo'); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertContains('before.caught.after.', $tester->getDisplay()); + } + + public function testRunWithError() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dym.'); + + throw new \Error('dymerr'); + }); + + $tester = new ApplicationTester($application); + + try { + $tester->run(array('command' => 'dym')); + $this->fail('Error expected.'); + } catch (\Error $e) { + $this->assertSame('dymerr', $e->getMessage()); + } + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage caught + */ + public function testRunWithErrorAndDispatcher() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dym.'); + + throw new \Error('dymerr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dym')); + $this->assertContains('before.dym.caught.after.', $tester->getDisplay(), 'The PHP Error did not dispached events'); + } + + public function testRunDispatchesAllEventsWithError() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dym.'); + + throw new \Error('dymerr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dym')); + $this->assertContains('before.dym.caught.after.', $tester->getDisplay(), 'The PHP Error did not dispached events'); + } + + public function testRunWithErrorFailingStatusCode() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('dus')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dus.'); + + throw new \Error('duserr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dus')); + $this->assertSame(1, $tester->getStatusCode(), 'Status code should be 1'); + } + + public function testRunWithDispatcherSkippingCommand() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher(true)); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $exitCode = $tester->run(array('command' => 'foo')); + $this->assertContains('before.after.', $tester->getDisplay()); + $this->assertEquals(ConsoleCommandEvent::RETURN_CODE_DISABLED, $exitCode); + } + + public function testTerminalDimensions() + { + $application = new Application(); + $originalDimensions = $application->getTerminalDimensions(); + $this->assertCount(2, $originalDimensions); + + $width = 80; + if ($originalDimensions[0] == $width) { + $width = 100; + } + + $application->setTerminalDimensions($width, 80); + $this->assertSame(array($width, 80), $application->getTerminalDimensions()); + } + + public function testSetRunCustomDefaultCommand() + { + $command = new \FooCommand(); + + $application = new Application(); + $application->setAutoExit(false); + $application->add($command); + $application->setDefaultCommand($command->getName()); + + $tester = new ApplicationTester($application); + $tester->run(array(), array('interactive' => false)); + $this->assertEquals('called'.PHP_EOL, $tester->getDisplay(), 'Application runs the default set command if different from \'list\' command'); + + $application = new CustomDefaultCommandApplication(); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array(), array('interactive' => false)); + + $this->assertEquals('called'.PHP_EOL, $tester->getDisplay(), 'Application runs the default set command if different from \'list\' command'); + } + + public function testSetRunCustomDefaultCommandWithOption() + { + $command = new \FooOptCommand(); + + $application = new Application(); + $application->setAutoExit(false); + $application->add($command); + $application->setDefaultCommand($command->getName()); + + $tester = new ApplicationTester($application); + $tester->run(array('--fooopt' => 'opt'), array('interactive' => false)); + + $this->assertEquals('called'.PHP_EOL.'opt'.PHP_EOL, $tester->getDisplay(), 'Application runs the default set command if different from \'list\' command'); + } + + /** + * @requires function posix_isatty + */ + public function testCanCheckIfTerminalIsInteractive() + { + $application = new CustomDefaultCommandApplication(); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'help')); + + $this->assertFalse($tester->getInput()->hasParameterOption(array('--no-interaction', '-n'))); + + $inputStream = $application->getHelperSet()->get('question')->getInputStream(); + $this->assertEquals($tester->getInput()->isInteractive(), @posix_isatty($inputStream)); + } + + protected function getDispatcher($skipCommand = false) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) use ($skipCommand) { + $event->getOutput()->write('before.'); + + if ($skipCommand) { + $event->disableCommand(); + } + }); + $dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) use ($skipCommand) { + $event->getOutput()->writeln('after.'); + + if (!$skipCommand) { + $event->setExitCode(ConsoleCommandEvent::RETURN_CODE_DISABLED); + } + }); + $dispatcher->addListener('console.exception', function (ConsoleExceptionEvent $event) { + $event->getOutput()->write('caught.'); + + $event->setException(new \LogicException('caught.', $event->getExitCode(), $event->getException())); + }); + + return $dispatcher; + } + + /** + * @requires PHP 7 + */ + public function testErrorIsRethrownIfNotHandledByConsoleErrorEventWithCatchingEnabled() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setDispatcher(new EventDispatcher()); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + new \UnknownClass(); + }); + + $tester = new ApplicationTester($application); + + try { + $tester->run(array('command' => 'dym')); + $this->fail('->run() should rethrow PHP errors if not handled via ConsoleErrorEvent.'); + } catch (\Error $e) { + $this->assertSame($e->getMessage(), 'Class \'UnknownClass\' not found'); + } + } +} + +class CustomApplication extends Application +{ + /** + * Overwrites the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.'))); + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + return new HelperSet(array(new FormatterHelper())); + } +} + +class CustomDefaultCommandApplication extends Application +{ + /** + * Overwrites the constructor in order to set a different default command. + */ + public function __construct() + { + parent::__construct(); + + $command = new \FooCommand(); + $this->add($command); + $this->setDefaultCommand($command->getName()); + } +} diff --git a/core/vendor/symfony/console/Tests/Command/CommandTest.php b/core/vendor/symfony/console/Tests/Command/CommandTest.php new file mode 100644 index 0000000..e129029 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Command/CommandTest.php @@ -0,0 +1,404 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Tester\CommandTester; + +class CommandTest extends TestCase +{ + protected static $fixturesPath; + + public static function setUpBeforeClass() + { + self::$fixturesPath = __DIR__.'/../Fixtures/'; + require_once self::$fixturesPath.'/TestCommand.php'; + } + + public function testConstructor() + { + $command = new Command('foo:bar'); + $this->assertEquals('foo:bar', $command->getName(), '__construct() takes the command name as its first argument'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The command defined in "Symfony\Component\Console\Command\Command" cannot have an empty name. + */ + public function testCommandNameCannotBeEmpty() + { + new Command(); + } + + public function testSetApplication() + { + $application = new Application(); + $command = new \TestCommand(); + $command->setApplication($application); + $this->assertEquals($application, $command->getApplication(), '->setApplication() sets the current application'); + $this->assertEquals($application->getHelperSet(), $command->getHelperSet()); + } + + public function testSetApplicationNull() + { + $command = new \TestCommand(); + $command->setApplication(null); + $this->assertNull($command->getHelperSet()); + } + + public function testSetGetDefinition() + { + $command = new \TestCommand(); + $ret = $command->setDefinition($definition = new InputDefinition()); + $this->assertEquals($command, $ret, '->setDefinition() implements a fluent interface'); + $this->assertEquals($definition, $command->getDefinition(), '->setDefinition() sets the current InputDefinition instance'); + $command->setDefinition(array(new InputArgument('foo'), new InputOption('bar'))); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument'); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument'); + $command->setDefinition(new InputDefinition()); + } + + public function testAddArgument() + { + $command = new \TestCommand(); + $ret = $command->addArgument('foo'); + $this->assertEquals($command, $ret, '->addArgument() implements a fluent interface'); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->addArgument() adds an argument to the command'); + } + + public function testAddOption() + { + $command = new \TestCommand(); + $ret = $command->addOption('foo'); + $this->assertEquals($command, $ret, '->addOption() implements a fluent interface'); + $this->assertTrue($command->getDefinition()->hasOption('foo'), '->addOption() adds an option to the command'); + } + + public function testGetNamespaceGetNameSetName() + { + $command = new \TestCommand(); + $this->assertEquals('namespace:name', $command->getName(), '->getName() returns the command name'); + $command->setName('foo'); + $this->assertEquals('foo', $command->getName(), '->setName() sets the command name'); + + $ret = $command->setName('foobar:bar'); + $this->assertEquals($command, $ret, '->setName() implements a fluent interface'); + $this->assertEquals('foobar:bar', $command->getName(), '->setName() sets the command name'); + } + + /** + * @dataProvider provideInvalidCommandNames + */ + public function testInvalidCommandNames($name) + { + if (method_exists($this, 'expectException')) { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage(sprintf('Command name "%s" is invalid.', $name)); + } else { + $this->setExpectedException('InvalidArgumentException', sprintf('Command name "%s" is invalid.', $name)); + } + + $command = new \TestCommand(); + $command->setName($name); + } + + public function provideInvalidCommandNames() + { + return array( + array(''), + array('foo:'), + ); + } + + public function testGetSetDescription() + { + $command = new \TestCommand(); + $this->assertEquals('description', $command->getDescription(), '->getDescription() returns the description'); + $ret = $command->setDescription('description1'); + $this->assertEquals($command, $ret, '->setDescription() implements a fluent interface'); + $this->assertEquals('description1', $command->getDescription(), '->setDescription() sets the description'); + } + + public function testGetSetHelp() + { + $command = new \TestCommand(); + $this->assertEquals('help', $command->getHelp(), '->getHelp() returns the help'); + $ret = $command->setHelp('help1'); + $this->assertEquals($command, $ret, '->setHelp() implements a fluent interface'); + $this->assertEquals('help1', $command->getHelp(), '->setHelp() sets the help'); + $command->setHelp(''); + $this->assertEquals('', $command->getHelp(), '->getHelp() does not fall back to the description'); + } + + public function testGetProcessedHelp() + { + $command = new \TestCommand(); + $command->setHelp('The %command.name% command does... Example: php %command.full_name%.'); + $this->assertContains('The namespace:name command does...', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.name% correctly'); + $this->assertNotContains('%command.full_name%', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.full_name%'); + + $command = new \TestCommand(); + $command->setHelp(''); + $this->assertContains('description', $command->getProcessedHelp(), '->getProcessedHelp() falls back to the description'); + } + + public function testGetSetAliases() + { + $command = new \TestCommand(); + $this->assertEquals(array('name'), $command->getAliases(), '->getAliases() returns the aliases'); + $ret = $command->setAliases(array('name1')); + $this->assertEquals($command, $ret, '->setAliases() implements a fluent interface'); + $this->assertEquals(array('name1'), $command->getAliases(), '->setAliases() sets the aliases'); + } + + public function testSetAliasesNull() + { + $command = new \TestCommand(); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $command->setAliases(null); + } + + public function testGetSynopsis() + { + $command = new \TestCommand(); + $command->addOption('foo'); + $command->addArgument('bar'); + $this->assertEquals('namespace:name [--foo] [--] []', $command->getSynopsis(), '->getSynopsis() returns the synopsis'); + } + + public function testAddGetUsages() + { + $command = new \TestCommand(); + $command->addUsage('foo1'); + $command->addUsage('foo2'); + $this->assertContains('namespace:name foo1', $command->getUsages()); + $this->assertContains('namespace:name foo2', $command->getUsages()); + } + + public function testGetHelper() + { + $application = new Application(); + $command = new \TestCommand(); + $command->setApplication($application); + $formatterHelper = new FormatterHelper(); + $this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->getHelper() returns the correct helper'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot retrieve helper "formatter" because there is no HelperSet defined. + */ + public function testGetHelperWithoutHelperSet() + { + $command = new \TestCommand(); + $command->getHelper('formatter'); + } + + public function testMergeApplicationDefinition() + { + $application1 = new Application(); + $application1->getDefinition()->addArguments(array(new InputArgument('foo'))); + $application1->getDefinition()->addOptions(array(new InputOption('bar'))); + $command = new \TestCommand(); + $command->setApplication($application1); + $command->setDefinition($definition = new InputDefinition(array(new InputArgument('bar'), new InputOption('foo')))); + + $r = new \ReflectionObject($command); + $m = $r->getMethod('mergeApplicationDefinition'); + $m->setAccessible(true); + $m->invoke($command); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition() merges the application arguments and the command arguments'); + $this->assertTrue($command->getDefinition()->hasArgument('bar'), '->mergeApplicationDefinition() merges the application arguments and the command arguments'); + $this->assertTrue($command->getDefinition()->hasOption('foo'), '->mergeApplicationDefinition() merges the application options and the command options'); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition() merges the application options and the command options'); + + $m->invoke($command); + $this->assertEquals(3, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments and options'); + } + + public function testMergeApplicationDefinitionWithoutArgsThenWithArgsAddsArgs() + { + $application1 = new Application(); + $application1->getDefinition()->addArguments(array(new InputArgument('foo'))); + $application1->getDefinition()->addOptions(array(new InputOption('bar'))); + $command = new \TestCommand(); + $command->setApplication($application1); + $command->setDefinition($definition = new InputDefinition(array())); + + $r = new \ReflectionObject($command); + $m = $r->getMethod('mergeApplicationDefinition'); + $m->setAccessible(true); + $m->invoke($command, false); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition(false) merges the application and the command options'); + $this->assertFalse($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(false) does not merge the application arguments'); + + $m->invoke($command, true); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(true) merges the application arguments and the command arguments'); + + $m->invoke($command); + $this->assertEquals(2, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments'); + } + + public function testRunInteractive() + { + $tester = new CommandTester(new \TestCommand()); + + $tester->execute(array(), array('interactive' => true)); + + $this->assertEquals('interact called'.PHP_EOL.'execute called'.PHP_EOL, $tester->getDisplay(), '->run() calls the interact() method if the input is interactive'); + } + + public function testRunNonInteractive() + { + $tester = new CommandTester(new \TestCommand()); + + $tester->execute(array(), array('interactive' => false)); + + $this->assertEquals('execute called'.PHP_EOL, $tester->getDisplay(), '->run() does not call the interact() method if the input is not interactive'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You must override the execute() method in the concrete command class. + */ + public function testExecuteMethodNeedsToBeOverridden() + { + $command = new Command('foo'); + $command->run(new StringInput(''), new NullOutput()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "--bar" option does not exist. + */ + public function testRunWithInvalidOption() + { + $command = new \TestCommand(); + $tester = new CommandTester($command); + $tester->execute(array('--bar' => true)); + } + + public function testRunReturnsIntegerExitCode() + { + $command = new \TestCommand(); + $exitCode = $command->run(new StringInput(''), new NullOutput()); + $this->assertSame(0, $exitCode, '->run() returns integer exit code (treats null as 0)'); + + $command = $this->getMockBuilder('TestCommand')->setMethods(array('execute'))->getMock(); + $command->expects($this->once()) + ->method('execute') + ->will($this->returnValue('2.3')); + $exitCode = $command->run(new StringInput(''), new NullOutput()); + $this->assertSame(2, $exitCode, '->run() returns integer exit code (casts numeric to int)'); + } + + public function testRunWithApplication() + { + $command = new \TestCommand(); + $command->setApplication(new Application()); + $exitCode = $command->run(new StringInput(''), new NullOutput()); + + $this->assertSame(0, $exitCode, '->run() returns an integer exit code'); + } + + public function testRunReturnsAlwaysInteger() + { + $command = new \TestCommand(); + + $this->assertSame(0, $command->run(new StringInput(''), new NullOutput())); + } + + public function testRunWithProcessTitle() + { + $command = new \TestCommand(); + $command->setApplication(new Application()); + $command->setProcessTitle('foo'); + $this->assertSame(0, $command->run(new StringInput(''), new NullOutput())); + if (function_exists('cli_set_process_title')) { + if (null === @cli_get_process_title() && 'Darwin' === PHP_OS) { + $this->markTestSkipped('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); + } + $this->assertEquals('foo', cli_get_process_title()); + } + } + + public function testSetCode() + { + $command = new \TestCommand(); + $ret = $command->setCode(function (InputInterface $input, OutputInterface $output) { + $output->writeln('from the code...'); + }); + $this->assertEquals($command, $ret, '->setCode() implements a fluent interface'); + $tester = new CommandTester($command); + $tester->execute(array()); + $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay()); + } + + public function testSetCodeWithNonClosureCallable() + { + $command = new \TestCommand(); + $ret = $command->setCode(array($this, 'callableMethodCommand')); + $this->assertEquals($command, $ret, '->setCode() implements a fluent interface'); + $tester = new CommandTester($command); + $tester->execute(array()); + $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid callable provided to Command::setCode. + */ + public function testSetCodeWithNonCallable() + { + $command = new \TestCommand(); + $command->setCode(array($this, 'nonExistentMethod')); + } + + public function callableMethodCommand(InputInterface $input, OutputInterface $output) + { + $output->writeln('from the code...'); + } + + /** + * @group legacy + */ + public function testLegacyAsText() + { + $command = new \TestCommand(); + $command->setApplication(new Application()); + $tester = new CommandTester($command); + $tester->execute(array('command' => $command->getName())); + $this->assertStringEqualsFile(self::$fixturesPath.'/command_astext.txt', $command->asText(), '->asText() returns a text representation of the command'); + } + + /** + * @group legacy + */ + public function testLegacyAsXml() + { + $command = new \TestCommand(); + $command->setApplication(new Application()); + $tester = new CommandTester($command); + $tester->execute(array('command' => $command->getName())); + $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/command_asxml.txt', $command->asXml(), '->asXml() returns an XML representation of the command'); + } +} diff --git a/core/vendor/symfony/console/Tests/Command/HelpCommandTest.php b/core/vendor/symfony/console/Tests/Command/HelpCommandTest.php new file mode 100644 index 0000000..a4e032a --- /dev/null +++ b/core/vendor/symfony/console/Tests/Command/HelpCommandTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Application; + +class HelpCommandTest extends TestCase +{ + public function testExecuteForCommandAlias() + { + $command = new HelpCommand(); + $command->setApplication(new Application()); + $commandTester = new CommandTester($command); + $commandTester->execute(array('command_name' => 'li'), array('decorated' => false)); + $this->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + $this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + } + + public function testExecuteForCommand() + { + $command = new HelpCommand(); + $commandTester = new CommandTester($command); + $command->setCommand(new ListCommand()); + $commandTester->execute(array(), array('decorated' => false)); + $this->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + } + + public function testExecuteForCommandWithXmlOption() + { + $command = new HelpCommand(); + $commandTester = new CommandTester($command); + $command->setCommand(new ListCommand()); + $commandTester->execute(array('--format' => 'xml')); + $this->assertContains('getDisplay(), '->execute() returns an XML help text if --xml is passed'); + } + + public function testExecuteForApplicationCommand() + { + $application = new Application(); + $commandTester = new CommandTester($application->get('help')); + $commandTester->execute(array('command_name' => 'list')); + $this->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + } + + public function testExecuteForApplicationCommandWithXmlOption() + { + $application = new Application(); + $commandTester = new CommandTester($application->get('help')); + $commandTester->execute(array('command_name' => 'list', '--format' => 'xml')); + $this->assertContains('list [--xml] [--raw] [--format FORMAT] [--] [<namespace>]', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('getDisplay(), '->execute() returns an XML help text if --format=xml is passed'); + } +} diff --git a/core/vendor/symfony/console/Tests/Command/ListCommandTest.php b/core/vendor/symfony/console/Tests/Command/ListCommandTest.php new file mode 100644 index 0000000..fb6ee3b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Command/ListCommandTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Application; + +class ListCommandTest extends TestCase +{ + public function testExecuteListsCommands() + { + $application = new Application(); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName()), array('decorated' => false)); + + $this->assertRegExp('/help\s{2,}Displays help for a command/', $commandTester->getDisplay(), '->execute() returns a list of available commands'); + } + + public function testExecuteListsCommandsWithXmlOption() + { + $application = new Application(); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName(), '--format' => 'xml')); + $this->assertRegExp('//', $commandTester->getDisplay(), '->execute() returns a list of available commands in XML if --xml is passed'); + } + + public function testExecuteListsCommandsWithRawOption() + { + $application = new Application(); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName(), '--raw' => true)); + $output = <<<'EOF' +help Displays help for a command +list Lists commands + +EOF; + + $this->assertEquals($output, $commandTester->getDisplay(true)); + } + + public function testExecuteListsCommandsWithNamespaceArgument() + { + require_once realpath(__DIR__.'/../Fixtures/FooCommand.php'); + $application = new Application(); + $application->add(new \FooCommand()); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName(), 'namespace' => 'foo', '--raw' => true)); + $output = <<<'EOF' +foo:bar The foo:bar command + +EOF; + + $this->assertEquals($output, $commandTester->getDisplay(true)); + } + + public function testExecuteListsCommandsOrder() + { + require_once realpath(__DIR__.'/../Fixtures/Foo6Command.php'); + $application = new Application(); + $application->add(new \Foo6Command()); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName()), array('decorated' => false)); + $output = <<<'EOF' +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + help Displays help for a command + list Lists commands + 0foo + 0foo:bar 0foo:bar command +EOF; + + $this->assertEquals($output, trim($commandTester->getDisplay(true))); + } + + public function testExecuteListsCommandsOrderRaw() + { + require_once realpath(__DIR__.'/../Fixtures/Foo6Command.php'); + $application = new Application(); + $application->add(new \Foo6Command()); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName(), '--raw' => true)); + $output = <<<'EOF' +help Displays help for a command +list Lists commands +0foo:bar 0foo:bar command +EOF; + + $this->assertEquals($output, trim($commandTester->getDisplay(true))); + } +} diff --git a/core/vendor/symfony/console/Tests/Descriptor/AbstractDescriptorTest.php b/core/vendor/symfony/console/Tests/Descriptor/AbstractDescriptorTest.php new file mode 100644 index 0000000..fcbb719 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Descriptor/AbstractDescriptorTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\BufferedOutput; + +abstract class AbstractDescriptorTest extends TestCase +{ + /** @dataProvider getDescribeInputArgumentTestData */ + public function testDescribeInputArgument(InputArgument $argument, $expectedDescription) + { + $this->assertDescription($expectedDescription, $argument); + } + + /** @dataProvider getDescribeInputOptionTestData */ + public function testDescribeInputOption(InputOption $option, $expectedDescription) + { + $this->assertDescription($expectedDescription, $option); + } + + /** @dataProvider getDescribeInputDefinitionTestData */ + public function testDescribeInputDefinition(InputDefinition $definition, $expectedDescription) + { + $this->assertDescription($expectedDescription, $definition); + } + + /** @dataProvider getDescribeCommandTestData */ + public function testDescribeCommand(Command $command, $expectedDescription) + { + $this->assertDescription($expectedDescription, $command); + } + + /** @dataProvider getDescribeApplicationTestData */ + public function testDescribeApplication(Application $application, $expectedDescription) + { + // Replaces the dynamic placeholders of the command help text with a static version. + // The placeholder %command.full_name% includes the script path that is not predictable + // and can not be tested against. + foreach ($application->all() as $command) { + $command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp())); + } + + $this->assertDescription($expectedDescription, $application); + } + + public function getDescribeInputArgumentTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getInputArguments()); + } + + public function getDescribeInputOptionTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getInputOptions()); + } + + public function getDescribeInputDefinitionTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getInputDefinitions()); + } + + public function getDescribeCommandTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getCommands()); + } + + public function getDescribeApplicationTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getApplications()); + } + + abstract protected function getDescriptor(); + + abstract protected function getFormat(); + + protected function getDescriptionTestData(array $objects) + { + $data = array(); + foreach ($objects as $name => $object) { + $description = file_get_contents(sprintf('%s/../Fixtures/%s.%s', __DIR__, $name, $this->getFormat())); + $data[] = array($object, $description); + } + + return $data; + } + + protected function assertDescription($expectedDescription, $describedObject) + { + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $this->getDescriptor()->describe($output, $describedObject, array('raw_output' => true)); + $this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $output->fetch()))); + } +} diff --git a/core/vendor/symfony/console/Tests/Descriptor/JsonDescriptorTest.php b/core/vendor/symfony/console/Tests/Descriptor/JsonDescriptorTest.php new file mode 100644 index 0000000..f9a1561 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Descriptor/JsonDescriptorTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Output\BufferedOutput; + +class JsonDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new JsonDescriptor(); + } + + protected function getFormat() + { + return 'json'; + } + + protected function assertDescription($expectedDescription, $describedObject) + { + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $this->getDescriptor()->describe($output, $describedObject, array('raw_output' => true)); + $this->assertEquals(json_decode(trim($expectedDescription), true), json_decode(trim(str_replace(PHP_EOL, "\n", $output->fetch())), true)); + } +} diff --git a/core/vendor/symfony/console/Tests/Descriptor/MarkdownDescriptorTest.php b/core/vendor/symfony/console/Tests/Descriptor/MarkdownDescriptorTest.php new file mode 100644 index 0000000..eb80f58 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Descriptor/MarkdownDescriptorTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Tests\Fixtures\DescriptorApplicationMbString; +use Symfony\Component\Console\Tests\Fixtures\DescriptorCommandMbString; + +class MarkdownDescriptorTest extends AbstractDescriptorTest +{ + public function getDescribeCommandTestData() + { + return $this->getDescriptionTestData(array_merge( + ObjectsProvider::getCommands(), + array('command_mbstring' => new DescriptorCommandMbString()) + )); + } + + public function getDescribeApplicationTestData() + { + return $this->getDescriptionTestData(array_merge( + ObjectsProvider::getApplications(), + array('application_mbstring' => new DescriptorApplicationMbString()) + )); + } + + protected function getDescriptor() + { + return new MarkdownDescriptor(); + } + + protected function getFormat() + { + return 'md'; + } +} diff --git a/core/vendor/symfony/console/Tests/Descriptor/ObjectsProvider.php b/core/vendor/symfony/console/Tests/Descriptor/ObjectsProvider.php new file mode 100644 index 0000000..b4f34ad --- /dev/null +++ b/core/vendor/symfony/console/Tests/Descriptor/ObjectsProvider.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication1; +use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication2; +use Symfony\Component\Console\Tests\Fixtures\DescriptorCommand1; +use Symfony\Component\Console\Tests\Fixtures\DescriptorCommand2; + +/** + * @author Jean-François Simon + */ +class ObjectsProvider +{ + public static function getInputArguments() + { + return array( + 'input_argument_1' => new InputArgument('argument_name', InputArgument::REQUIRED), + 'input_argument_2' => new InputArgument('argument_name', InputArgument::IS_ARRAY, 'argument description'), + 'input_argument_3' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', 'default_value'), + 'input_argument_4' => new InputArgument('argument_name', InputArgument::REQUIRED, "multiline\nargument description"), + 'input_argument_with_style' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', 'style'), + 'input_argument_with_default_inf_value' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', INF), + ); + } + + public static function getInputOptions() + { + return array( + 'input_option_1' => new InputOption('option_name', 'o', InputOption::VALUE_NONE), + 'input_option_2' => new InputOption('option_name', 'o', InputOption::VALUE_OPTIONAL, 'option description', 'default_value'), + 'input_option_3' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description'), + 'input_option_4' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'option description', array()), + 'input_option_5' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, "multiline\noption description"), + 'input_option_6' => new InputOption('option_name', array('o', 'O'), InputOption::VALUE_REQUIRED, 'option with multiple shortcuts'), + 'input_option_with_style' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description', 'style'), + 'input_option_with_style_array' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'option description', array('Hello', 'world')), + 'input_option_with_default_inf_value' => new InputOption('option_name', 'o', InputOption::VALUE_OPTIONAL, 'option description', INF), + ); + } + + public static function getInputDefinitions() + { + return array( + 'input_definition_1' => new InputDefinition(), + 'input_definition_2' => new InputDefinition(array(new InputArgument('argument_name', InputArgument::REQUIRED))), + 'input_definition_3' => new InputDefinition(array(new InputOption('option_name', 'o', InputOption::VALUE_NONE))), + 'input_definition_4' => new InputDefinition(array( + new InputArgument('argument_name', InputArgument::REQUIRED), + new InputOption('option_name', 'o', InputOption::VALUE_NONE), + )), + ); + } + + public static function getCommands() + { + return array( + 'command_1' => new DescriptorCommand1(), + 'command_2' => new DescriptorCommand2(), + ); + } + + public static function getApplications() + { + return array( + 'application_1' => new DescriptorApplication1(), + 'application_2' => new DescriptorApplication2(), + ); + } +} diff --git a/core/vendor/symfony/console/Tests/Descriptor/TextDescriptorTest.php b/core/vendor/symfony/console/Tests/Descriptor/TextDescriptorTest.php new file mode 100644 index 0000000..364e29c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Descriptor/TextDescriptorTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Tests\Fixtures\DescriptorApplicationMbString; +use Symfony\Component\Console\Tests\Fixtures\DescriptorCommandMbString; + +class TextDescriptorTest extends AbstractDescriptorTest +{ + public function getDescribeCommandTestData() + { + return $this->getDescriptionTestData(array_merge( + ObjectsProvider::getCommands(), + array('command_mbstring' => new DescriptorCommandMbString()) + )); + } + + public function getDescribeApplicationTestData() + { + return $this->getDescriptionTestData(array_merge( + ObjectsProvider::getApplications(), + array('application_mbstring' => new DescriptorApplicationMbString()) + )); + } + + protected function getDescriptor() + { + return new TextDescriptor(); + } + + protected function getFormat() + { + return 'txt'; + } +} diff --git a/core/vendor/symfony/console/Tests/Descriptor/XmlDescriptorTest.php b/core/vendor/symfony/console/Tests/Descriptor/XmlDescriptorTest.php new file mode 100644 index 0000000..59a5d1e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Descriptor/XmlDescriptorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\XmlDescriptor; + +class XmlDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new XmlDescriptor(); + } + + protected function getFormat() + { + return 'xml'; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/BarBucCommand.php b/core/vendor/symfony/console/Tests/Fixtures/BarBucCommand.php new file mode 100644 index 0000000..52b619e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/BarBucCommand.php @@ -0,0 +1,11 @@ +setName('bar:buc'); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplication1.php b/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplication1.php new file mode 100644 index 0000000..132b6d5 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplication1.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Application; + +class DescriptorApplication1 extends Application +{ +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplication2.php b/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplication2.php new file mode 100644 index 0000000..ff55135 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplication2.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Application; + +class DescriptorApplication2 extends Application +{ + public function __construct() + { + parent::__construct('My Symfony application', 'v1.0'); + $this->add(new DescriptorCommand1()); + $this->add(new DescriptorCommand2()); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplicationMbString.php b/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplicationMbString.php new file mode 100644 index 0000000..bf170c4 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/DescriptorApplicationMbString.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Application; + +class DescriptorApplicationMbString extends Application +{ + public function __construct() + { + parent::__construct('MbString åpplicätion'); + + $this->add(new DescriptorCommandMbString()); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommand1.php b/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommand1.php new file mode 100644 index 0000000..ede05d7 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommand1.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Command\Command; + +class DescriptorCommand1 extends Command +{ + protected function configure() + { + $this + ->setName('descriptor:command1') + ->setAliases(array('alias1', 'alias2')) + ->setDescription('command 1 description') + ->setHelp('command 1 help') + ; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommand2.php b/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommand2.php new file mode 100644 index 0000000..51106b9 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommand2.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class DescriptorCommand2 extends Command +{ + protected function configure() + { + $this + ->setName('descriptor:command2') + ->setDescription('command 2 description') + ->setHelp('command 2 help') + ->addUsage('-o|--option_name ') + ->addUsage('') + ->addArgument('argument_name', InputArgument::REQUIRED) + ->addOption('option_name', 'o', InputOption::VALUE_NONE) + ; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommandMbString.php b/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommandMbString.php new file mode 100644 index 0000000..66de917 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/DescriptorCommandMbString.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class DescriptorCommandMbString extends Command +{ + protected function configure() + { + $this + ->setName('descriptor:åèä') + ->setDescription('command åèä description') + ->setHelp('command åèä help') + ->addUsage('-o|--option_name ') + ->addUsage('') + ->addArgument('argument_åèä', InputArgument::REQUIRED) + ->addOption('option_åèä', 'o', InputOption::VALUE_NONE) + ; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/DummyOutput.php b/core/vendor/symfony/console/Tests/Fixtures/DummyOutput.php new file mode 100644 index 0000000..866e214 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/DummyOutput.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * Dummy output. + * + * @author Kévin Dunglas + */ +class DummyOutput extends BufferedOutput +{ + /** + * @return array + */ + public function getLogs() + { + $logs = array(); + foreach (explode(PHP_EOL, trim($this->fetch())) as $message) { + preg_match('/^\[(.*)\] (.*)/', $message, $matches); + $logs[] = sprintf('%s %s', $matches[1], $matches[2]); + } + + return $logs; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/Foo1Command.php b/core/vendor/symfony/console/Tests/Fixtures/Foo1Command.php new file mode 100644 index 0000000..254162f --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Foo1Command.php @@ -0,0 +1,26 @@ +setName('foo:bar1') + ->setDescription('The foo:bar1 command') + ->setAliases(array('afoobar1')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/Foo2Command.php b/core/vendor/symfony/console/Tests/Fixtures/Foo2Command.php new file mode 100644 index 0000000..8071dc8 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Foo2Command.php @@ -0,0 +1,21 @@ +setName('foo1:bar') + ->setDescription('The foo1:bar command') + ->setAliases(array('afoobar2')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/Foo3Command.php b/core/vendor/symfony/console/Tests/Fixtures/Foo3Command.php new file mode 100644 index 0000000..6c890fa --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Foo3Command.php @@ -0,0 +1,29 @@ +setName('foo3:bar') + ->setDescription('The foo3:bar command') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + try { + throw new \Exception('First exception

    this is html

    '); + } catch (\Exception $e) { + throw new \Exception('Second exception comment', 0, $e); + } + } catch (\Exception $e) { + throw new \Exception('Third exception comment', 0, $e); + } + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/Foo4Command.php b/core/vendor/symfony/console/Tests/Fixtures/Foo4Command.php new file mode 100644 index 0000000..1c54639 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Foo4Command.php @@ -0,0 +1,11 @@ +setName('foo3:bar:toh'); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/Foo5Command.php b/core/vendor/symfony/console/Tests/Fixtures/Foo5Command.php new file mode 100644 index 0000000..a1c6082 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Foo5Command.php @@ -0,0 +1,10 @@ +setName('0foo:bar')->setDescription('0foo:bar command'); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/FooCommand.php b/core/vendor/symfony/console/Tests/Fixtures/FooCommand.php new file mode 100644 index 0000000..355e0ad --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/FooCommand.php @@ -0,0 +1,33 @@ +setName('foo:bar') + ->setDescription('The foo:bar command') + ->setAliases(array('afoobar')) + ; + } + + protected function interact(InputInterface $input, OutputInterface $output) + { + $output->writeln('interact called'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + + $output->writeln('called'); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/FooOptCommand.php b/core/vendor/symfony/console/Tests/Fixtures/FooOptCommand.php new file mode 100644 index 0000000..9043aa4 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/FooOptCommand.php @@ -0,0 +1,36 @@ +setName('foo:bar') + ->setDescription('The foo:bar command') + ->setAliases(array('afoobar')) + ->addOption('fooopt', 'fo', InputOption::VALUE_OPTIONAL, 'fooopt description') + ; + } + + protected function interact(InputInterface $input, OutputInterface $output) + { + $output->writeln('interact called'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + + $output->writeln('called'); + $output->writeln($this->input->getOption('fooopt')); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced1Command.php b/core/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced1Command.php new file mode 100644 index 0000000..fc50c72 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced1Command.php @@ -0,0 +1,26 @@ +setName('foo:bar:baz') + ->setDescription('The foo:bar:baz command') + ->setAliases(array('foobarbaz')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php b/core/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php new file mode 100644 index 0000000..1cf31ff --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php @@ -0,0 +1,26 @@ +setName('foo:go:bret') + ->setDescription('The foo:bar:go command') + ->setAliases(array('foobargo')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php b/core/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php new file mode 100644 index 0000000..9681628 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php @@ -0,0 +1,25 @@ +setName('foobar:foo') + ->setDescription('The foobar:foo command') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php new file mode 100644 index 0000000..996fafb --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php @@ -0,0 +1,11 @@ +caution('Lorem ipsum dolor sit amet'); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php new file mode 100644 index 0000000..6634cd5 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php @@ -0,0 +1,13 @@ +title('Title'); + $output->warning('Lorem ipsum dolor sit amet'); + $output->title('Title'); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_10.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_10.php new file mode 100644 index 0000000..4120df9 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_10.php @@ -0,0 +1,17 @@ +block( + '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', + 'CUSTOM', + 'fg=white;bg=green', + 'X ', + true + ); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_11.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_11.php new file mode 100644 index 0000000..a4ef74e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_11.php @@ -0,0 +1,12 @@ +block($word, 'CUSTOM', 'fg=white;bg=blue', ' § ', false); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_12.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_12.php new file mode 100644 index 0000000..e6a1355 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_12.php @@ -0,0 +1,13 @@ +title('Title ending with \\'); + $output->section('Section ending with \\'); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php new file mode 100644 index 0000000..6004e3d --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php @@ -0,0 +1,16 @@ +warning('Warning'); + $output->caution('Caution'); + $output->error('Error'); + $output->success('Success'); + $output->note('Note'); + $output->block('Custom block', 'CUSTOM', 'fg=white;bg=green', 'X ', true); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php new file mode 100644 index 0000000..c7a08f1 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php @@ -0,0 +1,12 @@ +title('First title'); + $output->title('Second title'); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php new file mode 100644 index 0000000..afea70c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php @@ -0,0 +1,34 @@ +write('Lorem ipsum dolor sit amet'); + $output->title('First title'); + + $output->writeln('Lorem ipsum dolor sit amet'); + $output->title('Second title'); + + $output->write('Lorem ipsum dolor sit amet'); + $output->write(''); + $output->title('Third title'); + + //Ensure edge case by appending empty strings to history: + $output->write('Lorem ipsum dolor sit amet'); + $output->write(array('', '', '')); + $output->title('Fourth title'); + + //Ensure have manual control over number of blank lines: + $output->writeln('Lorem ipsum dolor sit amet'); + $output->writeln(array('', '')); //Should append an extra blank line + $output->title('Fifth title'); + + $output->writeln('Lorem ipsum dolor sit amet'); + $output->newLine(2); //Should append an extra blank line + $output->title('Fifth title'); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php new file mode 100644 index 0000000..2cc2ca0 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php @@ -0,0 +1,29 @@ +writeln('Lorem ipsum dolor sit amet'); + $output->listing(array( + 'Lorem ipsum dolor sit amet', + 'consectetur adipiscing elit', + )); + + //Even using write: + $output->write('Lorem ipsum dolor sit amet'); + $output->listing(array( + 'Lorem ipsum dolor sit amet', + 'consectetur adipiscing elit', + )); + + $output->write('Lorem ipsum dolor sit amet'); + $output->text(array( + 'Lorem ipsum dolor sit amet', + 'consectetur adipiscing elit', + )); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php new file mode 100644 index 0000000..f1d7990 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php @@ -0,0 +1,16 @@ +listing(array( + 'Lorem ipsum dolor sit amet', + 'consectetur adipiscing elit', + )); + $output->success('Lorem ipsum dolor sit amet'); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php new file mode 100644 index 0000000..cbfea73 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php @@ -0,0 +1,15 @@ +title('Title'); + $output->askHidden('Hidden question'); + $output->choice('Choice question with default', array('choice1', 'choice2'), 'choice1'); + $output->confirm('Confirmation with yes default', true); + $output->text('Duis aute irure dolor in reprehenderit in voluptate velit esse'); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_8.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_8.php new file mode 100644 index 0000000..0244fd2 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_8.php @@ -0,0 +1,26 @@ + 3))), + array('ISBN', 'Title', 'Author'), + ); + + $rows = array( + array( + '978-0521567817', + 'De Monarchia', + new TableCell("Dante Alighieri\nspans multiple rows", array('rowspan' => 2)), + ), + array('978-0804169127', 'Divine Comedy'), + ); + + $output = new SymfonyStyleWithForcedLineLength($input, $output); + $output->table($headers, $rows); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_9.php b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_9.php new file mode 100644 index 0000000..6420730 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_9.php @@ -0,0 +1,11 @@ +block(array('Custom block', 'Second custom block line'), 'CUSTOM', 'fg=white;bg=green', 'X ', true); +}; diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_0.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_0.txt new file mode 100644 index 0000000..a42e0f7 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_0.txt @@ -0,0 +1,3 @@ + + ! [CAUTION] Lorem ipsum dolor sit amet + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_1.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_1.txt new file mode 100644 index 0000000..334875f --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_1.txt @@ -0,0 +1,9 @@ + +Title +===== + + [WARNING] Lorem ipsum dolor sit amet + +Title +===== + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_10.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_10.txt new file mode 100644 index 0000000..385c6a2 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_10.txt @@ -0,0 +1,7 @@ + +X [CUSTOM] Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et +X dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +X commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat +X nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit +X anim id est laborum + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_11.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_11.txt new file mode 100644 index 0000000..190d784 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_11.txt @@ -0,0 +1,4 @@ + + § [CUSTOM] Lopadotemachoselachogaleokranioleipsanodrimhypotrimmatosilphioparaomelitokatakechymenokichlepikossyphophatto + § peristeralektryonoptekephalliokigklopeleiolagoiosiraiobaphetraganopterygon + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_12.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_12.txt new file mode 100644 index 0000000..59d00e0 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_12.txt @@ -0,0 +1,7 @@ + +Title ending with \ +=================== + +Section ending with \ +--------------------- + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_2.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_2.txt new file mode 100644 index 0000000..ca60976 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_2.txt @@ -0,0 +1,13 @@ + + [WARNING] Warning + + ! [CAUTION] Caution + + [ERROR] Error + + [OK] Success + + ! [NOTE] Note + +X [CUSTOM] Custom block + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_3.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_3.txt new file mode 100644 index 0000000..f4b6d58 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_3.txt @@ -0,0 +1,7 @@ + +First title +=========== + +Second title +============ + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4.txt new file mode 100644 index 0000000..2646d85 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4.txt @@ -0,0 +1,32 @@ +Lorem ipsum dolor sit amet + +First title +=========== + +Lorem ipsum dolor sit amet + +Second title +============ + +Lorem ipsum dolor sit amet + +Third title +=========== + +Lorem ipsum dolor sit amet + +Fourth title +============ + +Lorem ipsum dolor sit amet + + +Fifth title +=========== + +Lorem ipsum dolor sit amet + + +Fifth title +=========== + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_5.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_5.txt new file mode 100644 index 0000000..910240f --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_5.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet + * Lorem ipsum dolor sit amet + * consectetur adipiscing elit + +Lorem ipsum dolor sit amet + * Lorem ipsum dolor sit amet + * consectetur adipiscing elit + +Lorem ipsum dolor sit amet + // Lorem ipsum dolor sit amet + // consectetur adipiscing elit diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt new file mode 100644 index 0000000..5f2d33c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt @@ -0,0 +1,6 @@ + + * Lorem ipsum dolor sit amet + * consectetur adipiscing elit + + [OK] Lorem ipsum dolor sit amet + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7.txt new file mode 100644 index 0000000..ab18e5d --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7.txt @@ -0,0 +1,5 @@ + +Title +===== + + // Duis aute irure dolor in reprehenderit in voluptate velit esse diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt new file mode 100644 index 0000000..005b846 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt @@ -0,0 +1,9 @@ + ---------------- --------------- --------------------- + Main table title + ---------------- --------------- --------------------- + ISBN Title Author + ---------------- --------------- --------------------- + 978-0521567817 De Monarchia Dante Alighieri + 978-0804169127 Divine Comedy spans multiple rows + ---------------- --------------- --------------------- + diff --git a/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_9.txt b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_9.txt new file mode 100644 index 0000000..069c0d5 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_9.txt @@ -0,0 +1,5 @@ + +X [CUSTOM] Custom block +X +X Second custom block line + diff --git a/core/vendor/symfony/console/Tests/Fixtures/TestCommand.php b/core/vendor/symfony/console/Tests/Fixtures/TestCommand.php new file mode 100644 index 0000000..dcd3273 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/TestCommand.php @@ -0,0 +1,28 @@ +setName('namespace:name') + ->setAliases(array('name')) + ->setDescription('description') + ->setHelp('help') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('execute called'); + } + + protected function interact(InputInterface $input, OutputInterface $output) + { + $output->writeln('interact called'); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/TestTiti.php b/core/vendor/symfony/console/Tests/Fixtures/TestTiti.php new file mode 100644 index 0000000..72e29d2 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/TestTiti.php @@ -0,0 +1,21 @@ +setName('test-titi') + ->setDescription('The test:titi command') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->write('test-titi'); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/TestToto.php b/core/vendor/symfony/console/Tests/Fixtures/TestToto.php new file mode 100644 index 0000000..f14805d --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/TestToto.php @@ -0,0 +1,22 @@ +setName('test-toto') + ->setDescription('The test-toto command') + ->setAliases(array('test')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->write('test-toto'); + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_1.json b/core/vendor/symfony/console/Tests/Fixtures/application_1.json new file mode 100644 index 0000000..ea695a7 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_1.json @@ -0,0 +1,172 @@ +{ + "commands": [ + { + "name": "help", + "usage": [ + "help [--xml] [--format FORMAT] [--raw] [--] []" + ], + "description": "Displays help for a command", + "help": "The help<\/info> command displays help for a given command:\n\n php app\/console help list<\/info>\n\nYou can also output the help in other formats by using the --format<\/comment> option:\n\n php app\/console help --format=xml list<\/info>\n\nTo display the list of available commands, please use the list<\/info> command.", + "definition": { + "arguments": { + "command_name": { + "name": "command_name", + "is_required": false, + "is_array": false, + "description": "The command name", + "default": "help" + } + }, + "options": { + "xml": { + "name": "--xml", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To output help as XML", + "default": false + }, + "format": { + "name": "--format", + "shortcut": "", + "accept_value": true, + "is_value_required": true, + "is_multiple": false, + "description": "The output format (txt, xml, json, or md)", + "default": "txt" + }, + "raw": { + "name": "--raw", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To output raw command help", + "default": false + }, + "help": { + "name": "--help", + "shortcut": "-h", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Display this help message", + "default": false + }, + "quiet": { + "name": "--quiet", + "shortcut": "-q", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Do not output any message", + "default": false + }, + "verbose": { + "name": "--verbose", + "shortcut": "-v|-vv|-vvv", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug", + "default": false + }, + "version": { + "name": "--version", + "shortcut": "-V", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Display this application version", + "default": false + }, + "ansi": { + "name": "--ansi", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Force ANSI output", + "default": false + }, + "no-ansi": { + "name": "--no-ansi", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Disable ANSI output", + "default": false + }, + "no-interaction": { + "name": "--no-interaction", + "shortcut": "-n", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Do not ask any interactive question", + "default": false + } + } + } + }, + { + "name": "list", + "usage": [ + "list [--xml] [--raw] [--format FORMAT] [--] []" + ], + "description": "Lists commands", + "help": "The list<\/info> command lists all commands:\n\n php app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n php app\/console list test<\/info>\n\nYou can also output the information in other formats by using the --format<\/comment> option:\n\n php app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n php app\/console list --raw<\/info>", + "definition": { + "arguments": { + "namespace": { + "name": "namespace", + "is_required": false, + "is_array": false, + "description": "The namespace name", + "default": null + } + }, + "options": { + "xml": { + "name": "--xml", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To output list as XML", + "default": false + }, + "raw": { + "name": "--raw", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To output raw command list", + "default": false + }, + "format": { + "name": "--format", + "shortcut": "", + "accept_value": true, + "is_value_required": true, + "is_multiple": false, + "description": "The output format (txt, xml, json, or md)", + "default": "txt" + } + } + } + } + ], + "namespaces": [ + { + "id": "_global", + "commands": [ + "help", + "list" + ] + } + ] +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_1.md b/core/vendor/symfony/console/Tests/Fixtures/application_1.md new file mode 100644 index 0000000..82a605d --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_1.md @@ -0,0 +1,201 @@ +UNKNOWN +======= + +* help +* list + +help +---- + +* Description: Displays help for a command +* Usage: + + * `help [--xml] [--format FORMAT] [--raw] [--] []` + +The help command displays help for a given command: + + php app/console help list + +You can also output the help in other formats by using the --format option: + + php app/console help --format=xml list + +To display the list of available commands, please use the list command. + +### Arguments: + +**command_name:** + +* Name: command_name +* Is required: no +* Is array: no +* Description: The command name +* Default: `'help'` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output help as XML +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: The output format (txt, xml, json, or md) +* Default: `'txt'` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command help +* Default: `false` + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question +* Default: `false` + +list +---- + +* Description: Lists commands +* Usage: + + * `list [--xml] [--raw] [--format FORMAT] [--] []` + +The list command lists all commands: + + php app/console list + +You can also display the commands for a specific namespace: + + php app/console list test + +You can also output the information in other formats by using the --format option: + + php app/console list --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php app/console list --raw + +### Arguments: + +**namespace:** + +* Name: namespace +* Is required: no +* Is array: no +* Description: The namespace name +* Default: `NULL` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output list as XML +* Default: `false` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command list +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: The output format (txt, xml, json, or md) +* Default: `'txt'` diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_1.txt b/core/vendor/symfony/console/Tests/Fixtures/application_1.txt new file mode 100644 index 0000000..c4cf8f2 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_1.txt @@ -0,0 +1,17 @@ +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + help Displays help for a command + list Lists commands diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_1.xml b/core/vendor/symfony/console/Tests/Fixtures/application_1.xml new file mode 100644 index 0000000..35d1db4 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_1.xml @@ -0,0 +1,110 @@ + + + + + + help [--xml] [--format FORMAT] [--raw] [--] [<command_name>] + + Displays help for a command + The <info>help</info> command displays help for a given command: + + <info>php app/console help list</info> + + You can also output the help in other formats by using the <comment>--format</comment> option: + + <info>php app/console help --format=xml list</info> + + To display the list of available commands, please use the <info>list</info> command. + + + The command name + + help + + + + + + + + + + + + + + + + + + + list [--xml] [--raw] [--format FORMAT] [--] [<namespace>] + + Lists commands + The <info>list</info> command lists all commands: + + <info>php app/console list</info> + + You can also display the commands for a specific namespace: + + <info>php app/console list test</info> + + You can also output the information in other formats by using the <comment>--format</comment> option: + + <info>php app/console list --format=xml</info> + + It's also possible to get raw list of commands (useful for embedding command runner): + + <info>php app/console list --raw</info> + + + The namespace name + + + + + + + + + + + + + help + list + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_2.json b/core/vendor/symfony/console/Tests/Fixtures/application_2.json new file mode 100644 index 0000000..8ffa222 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_2.json @@ -0,0 +1,354 @@ +{ + "commands": [ + { + "name": "help", + "usage": [ + "help [--xml] [--format FORMAT] [--raw] [--] []" + ], + "description": "Displays help for a command", + "help": "The help<\/info> command displays help for a given command:\n\n php app\/console help list<\/info>\n\nYou can also output the help in other formats by using the --format<\/comment> option:\n\n php app\/console help --format=xml list<\/info>\n\nTo display the list of available commands, please use the list<\/info> command.", + "definition": { + "arguments": { + "command_name": { + "name": "command_name", + "is_required": false, + "is_array": false, + "description": "The command name", + "default": "help" + } + }, + "options": { + "xml": { + "name": "--xml", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To output help as XML", + "default": false + }, + "format": { + "name": "--format", + "shortcut": "", + "accept_value": true, + "is_value_required": true, + "is_multiple": false, + "description": "The output format (txt, xml, json, or md)", + "default": "txt" + }, + "raw": { + "name": "--raw", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To output raw command help", + "default": false + }, + "help": { + "name": "--help", + "shortcut": "-h", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Display this help message", + "default": false + }, + "quiet": { + "name": "--quiet", + "shortcut": "-q", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Do not output any message", + "default": false + }, + "verbose": { + "name": "--verbose", + "shortcut": "-v|-vv|-vvv", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug", + "default": false + }, + "version": { + "name": "--version", + "shortcut": "-V", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Display this application version", + "default": false + }, + "ansi": { + "name": "--ansi", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Force ANSI output", + "default": false + }, + "no-ansi": { + "name": "--no-ansi", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Disable ANSI output", + "default": false + }, + "no-interaction": { + "name": "--no-interaction", + "shortcut": "-n", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Do not ask any interactive question", + "default": false + } + } + } + }, + { + "name": "list", + "usage": [ + "list [--xml] [--raw] [--format FORMAT] [--] []" + ], + "description": "Lists commands", + "help": "The list<\/info> command lists all commands:\n\n php app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n php app\/console list test<\/info>\n\nYou can also output the information in other formats by using the --format<\/comment> option:\n\n php app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n php app\/console list --raw<\/info>", + "definition": { + "arguments": { + "namespace": { + "name": "namespace", + "is_required": false, + "is_array": false, + "description": "The namespace name", + "default": null + } + }, + "options": { + "xml": { + "name": "--xml", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To output list as XML", + "default": false + }, + "raw": { + "name": "--raw", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "To output raw command list", + "default": false + }, + "format": { + "name": "--format", + "shortcut": "", + "accept_value": true, + "is_value_required": true, + "is_multiple": false, + "description": "The output format (txt, xml, json, or md)", + "default": "txt" + } + } + } + }, + { + "name": "descriptor:command1", + "usage": [ + "descriptor:command1", + "alias1", + "alias2" + ], + "description": "command 1 description", + "help": "command 1 help", + "definition": { + "arguments": [], + "options": { + "help": { + "name": "--help", + "shortcut": "-h", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Display this help message", + "default": false + }, + "quiet": { + "name": "--quiet", + "shortcut": "-q", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Do not output any message", + "default": false + }, + "verbose": { + "name": "--verbose", + "shortcut": "-v|-vv|-vvv", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug", + "default": false + }, + "version": { + "name": "--version", + "shortcut": "-V", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Display this application version", + "default": false + }, + "ansi": { + "name": "--ansi", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Force ANSI output", + "default": false + }, + "no-ansi": { + "name": "--no-ansi", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Disable ANSI output", + "default": false + }, + "no-interaction": { + "name": "--no-interaction", + "shortcut": "-n", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Do not ask any interactive question", + "default": false + } + } + } + }, + { + "name": "descriptor:command2", + "usage": [ + "descriptor:command2 [-o|--option_name] [--] ", + "descriptor:command2 -o|--option_name ", + "descriptor:command2 " + ], + "description": "command 2 description", + "help": "command 2 help", + "definition": { + "arguments": { + "argument_name": { + "name": "argument_name", + "is_required": true, + "is_array": false, + "description": "", + "default": null + } + }, + "options": { + "option_name": { + "name": "--option_name", + "shortcut": "-o", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "", + "default": false + }, + "help": { + "name": "--help", + "shortcut": "-h", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Display this help message", + "default": false + }, + "quiet": { + "name": "--quiet", + "shortcut": "-q", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Do not output any message", + "default": false + }, + "verbose": { + "name": "--verbose", + "shortcut": "-v|-vv|-vvv", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug", + "default": false + }, + "version": { + "name": "--version", + "shortcut": "-V", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Display this application version", + "default": false + }, + "ansi": { + "name": "--ansi", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Force ANSI output", + "default": false + }, + "no-ansi": { + "name": "--no-ansi", + "shortcut": "", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Disable ANSI output", + "default": false + }, + "no-interaction": { + "name": "--no-interaction", + "shortcut": "-n", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "Do not ask any interactive question", + "default": false + } + } + } + } + ], + "namespaces": [ + { + "id": "_global", + "commands": [ + "alias1", + "alias2", + "help", + "list" + ] + }, + { + "id": "descriptor", + "commands": [ + "descriptor:command1", + "descriptor:command2" + ] + } + ] +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_2.md b/core/vendor/symfony/console/Tests/Fixtures/application_2.md new file mode 100644 index 0000000..f031c9e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_2.md @@ -0,0 +1,396 @@ +My Symfony application +====================== + +* alias1 +* alias2 +* help +* list + +**descriptor:** + +* descriptor:command1 +* descriptor:command2 + +help +---- + +* Description: Displays help for a command +* Usage: + + * `help [--xml] [--format FORMAT] [--raw] [--] []` + +The help command displays help for a given command: + + php app/console help list + +You can also output the help in other formats by using the --format option: + + php app/console help --format=xml list + +To display the list of available commands, please use the list command. + +### Arguments: + +**command_name:** + +* Name: command_name +* Is required: no +* Is array: no +* Description: The command name +* Default: `'help'` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output help as XML +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: The output format (txt, xml, json, or md) +* Default: `'txt'` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command help +* Default: `false` + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question +* Default: `false` + +list +---- + +* Description: Lists commands +* Usage: + + * `list [--xml] [--raw] [--format FORMAT] [--] []` + +The list command lists all commands: + + php app/console list + +You can also display the commands for a specific namespace: + + php app/console list test + +You can also output the information in other formats by using the --format option: + + php app/console list --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php app/console list --raw + +### Arguments: + +**namespace:** + +* Name: namespace +* Is required: no +* Is array: no +* Description: The namespace name +* Default: `NULL` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output list as XML +* Default: `false` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command list +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: The output format (txt, xml, json, or md) +* Default: `'txt'` + +descriptor:command1 +------------------- + +* Description: command 1 description +* Usage: + + * `descriptor:command1` + * `alias1` + * `alias2` + +command 1 help + +### Options: + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question +* Default: `false` + +descriptor:command2 +------------------- + +* Description: command 2 description +* Usage: + + * `descriptor:command2 [-o|--option_name] [--] ` + * `descriptor:command2 -o|--option_name ` + * `descriptor:command2 ` + +command 2 help + +### Arguments: + +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` + +### Options: + +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question +* Default: `false` diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_2.txt b/core/vendor/symfony/console/Tests/Fixtures/application_2.txt new file mode 100644 index 0000000..292aa82 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_2.txt @@ -0,0 +1,22 @@ +My Symfony application version v1.0 + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + alias1 command 1 description + alias2 command 1 description + help Displays help for a command + list Lists commands + descriptor + descriptor:command1 command 1 description + descriptor:command2 command 2 description diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_2.xml b/core/vendor/symfony/console/Tests/Fixtures/application_2.xml new file mode 100644 index 0000000..bc8ab21 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_2.xml @@ -0,0 +1,190 @@ + + + + + + help [--xml] [--format FORMAT] [--raw] [--] [<command_name>] + + Displays help for a command + The <info>help</info> command displays help for a given command: + + <info>php app/console help list</info> + + You can also output the help in other formats by using the <comment>--format</comment> option: + + <info>php app/console help --format=xml list</info> + + To display the list of available commands, please use the <info>list</info> command. + + + The command name + + help + + + + + + + + + + + + + + + + + + + list [--xml] [--raw] [--format FORMAT] [--] [<namespace>] + + Lists commands + The <info>list</info> command lists all commands: + + <info>php app/console list</info> + + You can also display the commands for a specific namespace: + + <info>php app/console list test</info> + + You can also output the information in other formats by using the <comment>--format</comment> option: + + <info>php app/console list --format=xml</info> + + It's also possible to get raw list of commands (useful for embedding command runner): + + <info>php app/console list --raw</info> + + + The namespace name + + + + + + + + + + + + descriptor:command1 + alias1 + alias2 + + command 1 description + command 1 help + + + + + + + + + + + + + + descriptor:command2 [-o|--option_name] [--] <argument_name> + descriptor:command2 -o|--option_name <argument_name> + descriptor:command2 <argument_name> + + command 2 description + command 2 help + + + + + + + + + + + + + + + + + + + + + alias1 + alias2 + help + list + + + descriptor:command1 + descriptor:command2 + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_astext1.txt b/core/vendor/symfony/console/Tests/Fixtures/application_astext1.txt new file mode 100644 index 0000000..19dacb2 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_astext1.txt @@ -0,0 +1,20 @@ +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + afoobar The foo:bar command + help Displays help for a command + list Lists commands + foo + foo:bar The foo:bar command diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_astext2.txt b/core/vendor/symfony/console/Tests/Fixtures/application_astext2.txt new file mode 100644 index 0000000..c99ccdd --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_astext2.txt @@ -0,0 +1,16 @@ +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands for the "foo" namespace: + foo:bar The foo:bar command diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_asxml1.txt b/core/vendor/symfony/console/Tests/Fixtures/application_asxml1.txt new file mode 100644 index 0000000..8277d9e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_asxml1.txt @@ -0,0 +1,146 @@ + + + + + + help [--xml] [--format FORMAT] [--raw] [--] [<command_name>] + + Displays help for a command + The <info>help</info> command displays help for a given command: + + <info>php app/console help list</info> + + You can also output the help in other formats by using the <comment>--format</comment> option: + + <info>php app/console help --format=xml list</info> + + To display the list of available commands, please use the <info>list</info> command. + + + The command name + + help + + + + + + + + + + + + + + + + + + + list [--xml] [--raw] [--format FORMAT] [--] [<namespace>] + + Lists commands + The <info>list</info> command lists all commands: + + <info>php app/console list</info> + + You can also display the commands for a specific namespace: + + <info>php app/console list test</info> + + You can also output the information in other formats by using the <comment>--format</comment> option: + + <info>php app/console list --format=xml</info> + + It's also possible to get raw list of commands (useful for embedding command runner): + + <info>php app/console list --raw</info> + + + The namespace name + + + + + + + + + + + + foo:bar + afoobar + + The foo:bar command + The foo:bar command + + + + + + + + + + + + + + + afoobar + help + list + + + foo:bar + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_asxml2.txt b/core/vendor/symfony/console/Tests/Fixtures/application_asxml2.txt new file mode 100644 index 0000000..93d6d4e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_asxml2.txt @@ -0,0 +1,37 @@ + + + + + + foo:bar + afoobar + + The foo:bar command + The foo:bar command + + + + + + + + + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_gethelp.txt b/core/vendor/symfony/console/Tests/Fixtures/application_gethelp.txt new file mode 100644 index 0000000..0c16e3c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_gethelp.txt @@ -0,0 +1 @@ +Console Tool \ No newline at end of file diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_mbstring.md b/core/vendor/symfony/console/Tests/Fixtures/application_mbstring.md new file mode 100644 index 0000000..ef81f86 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_mbstring.md @@ -0,0 +1,309 @@ +MbString åpplicätion +==================== + +* help +* list + +**descriptor:** + +* descriptor:åèä + +help +---- + +* Description: Displays help for a command +* Usage: + + * `help [--xml] [--format FORMAT] [--raw] [--] []` + +The help command displays help for a given command: + + php app/console help list + +You can also output the help in other formats by using the --format option: + + php app/console help --format=xml list + +To display the list of available commands, please use the list command. + +### Arguments: + +**command_name:** + +* Name: command_name +* Is required: no +* Is array: no +* Description: The command name +* Default: `'help'` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output help as XML +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: The output format (txt, xml, json, or md) +* Default: `'txt'` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command help +* Default: `false` + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question +* Default: `false` + +list +---- + +* Description: Lists commands +* Usage: + + * `list [--xml] [--raw] [--format FORMAT] [--] []` + +The list command lists all commands: + + php app/console list + +You can also display the commands for a specific namespace: + + php app/console list test + +You can also output the information in other formats by using the --format option: + + php app/console list --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php app/console list --raw + +### Arguments: + +**namespace:** + +* Name: namespace +* Is required: no +* Is array: no +* Description: The namespace name +* Default: `NULL` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output list as XML +* Default: `false` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command list +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: The output format (txt, xml, json, or md) +* Default: `'txt'` + +descriptor:åèä +-------------- + +* Description: command åèä description +* Usage: + + * `descriptor:åèä [-o|--option_åèä] [--] ` + * `descriptor:åèä -o|--option_name ` + * `descriptor:åèä ` + +command åèä help + +### Arguments: + +**argument_åèä:** + +* Name: argument_åèä +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` + +### Options: + +**option_åèä:** + +* Name: `--option_åèä` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question +* Default: `false` diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_mbstring.txt b/core/vendor/symfony/console/Tests/Fixtures/application_mbstring.txt new file mode 100644 index 0000000..9d21f82 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_mbstring.txt @@ -0,0 +1,19 @@ +MbString åpplicätion + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + help Displays help for a command + list Lists commands + descriptor + descriptor:åèä command åèä description diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception1.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception1.txt new file mode 100644 index 0000000..c56f4b6 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception1.txt @@ -0,0 +1,6 @@ + + + [InvalidArgumentException] + Command "foo" is not defined. + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception2.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception2.txt new file mode 100644 index 0000000..58cd420 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception2.txt @@ -0,0 +1,8 @@ + + + [InvalidArgumentException] + The "--foo" option does not exist. + + +list [--xml] [--raw] [--format FORMAT] [--] [] + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception3.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception3.txt new file mode 100644 index 0000000..f41925f --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception3.txt @@ -0,0 +1,18 @@ + + + [Exception] + Third exception comment + + + + [Exception] + Second exception comment + + + + [Exception] + First exception

    this is html

    + + +foo3:bar + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception3decorated.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception3decorated.txt new file mode 100644 index 0000000..5adccdd --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception3decorated.txt @@ -0,0 +1,18 @@ + +  + [Exception]  + Third exception comment  +  + +  + [Exception]  + Second exception comment  +  + +  + [Exception]  + First exception

    this is html

     +  + +foo3:bar + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception4.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception4.txt new file mode 100644 index 0000000..9d881e7 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception4.txt @@ -0,0 +1,7 @@ + + + [InvalidArgumentException] + Command "foo" is not define + d. + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1.txt new file mode 100644 index 0000000..1ba5f8f --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1.txt @@ -0,0 +1,8 @@ + + + [Exception] + エラーメッセージ + + +foo + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt new file mode 100644 index 0000000..2064425 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt @@ -0,0 +1,8 @@ + +  + [Exception]  + エラーメッセージ  +  + +foo + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth2.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth2.txt new file mode 100644 index 0000000..e41fcfc --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth2.txt @@ -0,0 +1,9 @@ + + + [Exception] + コマンドの実行中にエラーが + 発生しました。 + + +foo + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_escapeslines.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_escapeslines.txt new file mode 100644 index 0000000..cf79b37 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_escapeslines.txt @@ -0,0 +1,9 @@ + + + [Exception] + dont break here < + info>!
    + + +foo + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_linebreaks.txt b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_linebreaks.txt new file mode 100644 index 0000000..e9a9518 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_renderexception_linebreaks.txt @@ -0,0 +1,11 @@ + + + [InvalidArgumentException] + line 1 with extra spaces + line 2 + + line 4 + + +foo + diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_run1.txt b/core/vendor/symfony/console/Tests/Fixtures/application_run1.txt new file mode 100644 index 0000000..0dc2730 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_run1.txt @@ -0,0 +1,17 @@ +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + help Displays help for a command + list Lists commands diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_run2.txt b/core/vendor/symfony/console/Tests/Fixtures/application_run2.txt new file mode 100644 index 0000000..06ad09a --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_run2.txt @@ -0,0 +1,29 @@ +Usage: + help [options] [--] [] + +Arguments: + command The command to execute [default: "list"] + command_name The command name [default: "help"] + +Options: + --xml To output help as XML + --format=FORMAT The output format (txt, xml, json, or md) [default: "txt"] + --raw To output raw command help + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Help: + The help command displays help for a given command: + + php app/console help list + + You can also output the help in other formats by using the --format option: + + php app/console help --format=xml list + + To display the list of available commands, please use the list command. diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_run3.txt b/core/vendor/symfony/console/Tests/Fixtures/application_run3.txt new file mode 100644 index 0000000..2e8d92e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_run3.txt @@ -0,0 +1,27 @@ +Usage: + list [options] [--] [] + +Arguments: + namespace The namespace name + +Options: + --xml To output list as XML + --raw To output raw command list + --format=FORMAT The output format (txt, xml, json, or md) [default: "txt"] + +Help: + The list command lists all commands: + + php app/console list + + You can also display the commands for a specific namespace: + + php app/console list test + + You can also output the information in other formats by using the --format option: + + php app/console list --format=xml + + It's also possible to get raw list of commands (useful for embedding command runner): + + php app/console list --raw diff --git a/core/vendor/symfony/console/Tests/Fixtures/application_run4.txt b/core/vendor/symfony/console/Tests/Fixtures/application_run4.txt new file mode 100644 index 0000000..47187fc --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/application_run4.txt @@ -0,0 +1 @@ +Console Tool diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_1.json b/core/vendor/symfony/console/Tests/Fixtures/command_1.json new file mode 100644 index 0000000..4cd37ee --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_1.json @@ -0,0 +1,14 @@ +{ + "name": "descriptor:command1", + "usage": [ + "descriptor:command1", + "alias1", + "alias2" + ], + "description": "command 1 description", + "help": "command 1 help", + "definition": { + "arguments": [], + "options": [] + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_1.md b/core/vendor/symfony/console/Tests/Fixtures/command_1.md new file mode 100644 index 0000000..34ed3ea --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_1.md @@ -0,0 +1,11 @@ +descriptor:command1 +------------------- + +* Description: command 1 description +* Usage: + + * `descriptor:command1` + * `alias1` + * `alias2` + +command 1 help diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_1.txt b/core/vendor/symfony/console/Tests/Fixtures/command_1.txt new file mode 100644 index 0000000..e5e93ee --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_1.txt @@ -0,0 +1,7 @@ +Usage: + descriptor:command1 + alias1 + alias2 + +Help: + command 1 help diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_1.xml b/core/vendor/symfony/console/Tests/Fixtures/command_1.xml new file mode 100644 index 0000000..838b9bd --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_1.xml @@ -0,0 +1,12 @@ + + + + descriptor:command1 + alias1 + alias2 + + command 1 description + command 1 help + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_2.json b/core/vendor/symfony/console/Tests/Fixtures/command_2.json new file mode 100644 index 0000000..74c1f12 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_2.json @@ -0,0 +1,32 @@ +{ + "name": "descriptor:command2", + "usage": [ + "descriptor:command2 [-o|--option_name] [--] ", + "descriptor:command2 -o|--option_name ", + "descriptor:command2 " + ], + "description": "command 2 description", + "help": "command 2 help", + "definition": { + "arguments": { + "argument_name": { + "name": "argument_name", + "is_required": true, + "is_array": false, + "description": "", + "default": null + } + }, + "options": { + "option_name": { + "name": "--option_name", + "shortcut": "-o", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "", + "default": false + } + } + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_2.md b/core/vendor/symfony/console/Tests/Fixtures/command_2.md new file mode 100644 index 0000000..6f538b6 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_2.md @@ -0,0 +1,33 @@ +descriptor:command2 +------------------- + +* Description: command 2 description +* Usage: + + * `descriptor:command2 [-o|--option_name] [--] ` + * `descriptor:command2 -o|--option_name ` + * `descriptor:command2 ` + +command 2 help + +### Arguments: + +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` + +### Options: + +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_2.txt b/core/vendor/symfony/console/Tests/Fixtures/command_2.txt new file mode 100644 index 0000000..2864c7b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_2.txt @@ -0,0 +1,13 @@ +Usage: + descriptor:command2 [options] [--] \ + descriptor:command2 -o|--option_name \ + descriptor:command2 \ + +Arguments: + argument_name + +Options: + -o, --option_name + +Help: + command 2 help diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_2.xml b/core/vendor/symfony/console/Tests/Fixtures/command_2.xml new file mode 100644 index 0000000..67364ca --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_2.xml @@ -0,0 +1,21 @@ + + + + descriptor:command2 [-o|--option_name] [--] <argument_name> + descriptor:command2 -o|--option_name <argument_name> + descriptor:command2 <argument_name> + + command 2 description + command 2 help + + + + + + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_astext.txt b/core/vendor/symfony/console/Tests/Fixtures/command_astext.txt new file mode 100644 index 0000000..4d9055d --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_astext.txt @@ -0,0 +1,18 @@ +Usage: + namespace:name + name + +Arguments: + command The command to execute + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Help: + help diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_asxml.txt b/core/vendor/symfony/console/Tests/Fixtures/command_asxml.txt new file mode 100644 index 0000000..5e77623 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_asxml.txt @@ -0,0 +1,38 @@ + + + + namespace:name + name + + description + help + + + The command to execute + + + + + + + + + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_mbstring.md b/core/vendor/symfony/console/Tests/Fixtures/command_mbstring.md new file mode 100644 index 0000000..2adac53 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_mbstring.md @@ -0,0 +1,33 @@ +descriptor:åèä +-------------- + +* Description: command åèä description +* Usage: + + * `descriptor:åèä [-o|--option_åèä] [--] ` + * `descriptor:åèä -o|--option_name ` + * `descriptor:åèä ` + +command åèä help + +### Arguments: + +**argument_åèä:** + +* Name: argument_åèä +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` + +### Options: + +**option_åèä:** + +* Name: `--option_åèä` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/core/vendor/symfony/console/Tests/Fixtures/command_mbstring.txt b/core/vendor/symfony/console/Tests/Fixtures/command_mbstring.txt new file mode 100644 index 0000000..cde457d --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/command_mbstring.txt @@ -0,0 +1,13 @@ +Usage: + descriptor:åèä [options] [--] \ + descriptor:åèä -o|--option_name \ + descriptor:åèä \ + +Arguments: + argument_åèä + +Options: + -o, --option_åèä + +Help: + command åèä help diff --git a/core/vendor/symfony/console/Tests/Fixtures/definition_astext.txt b/core/vendor/symfony/console/Tests/Fixtures/definition_astext.txt new file mode 100644 index 0000000..0431c07 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/definition_astext.txt @@ -0,0 +1,11 @@ +Arguments: + foo The foo argument + baz The baz argument [default: true] + bar The bar argument [default: ["http://foo.com/"]] + +Options: + -f, --foo=FOO The foo option + --baz[=BAZ] The baz option [default: false] + -b, --bar[=BAR] The bar option [default: "bar"] + --qux[=QUX] The qux option [default: ["http://foo.com/","bar"]] (multiple values allowed) + --qux2[=QUX2] The qux2 option [default: {"foo":"bar"}] (multiple values allowed) \ No newline at end of file diff --git a/core/vendor/symfony/console/Tests/Fixtures/definition_asxml.txt b/core/vendor/symfony/console/Tests/Fixtures/definition_asxml.txt new file mode 100644 index 0000000..eec8c07 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/definition_asxml.txt @@ -0,0 +1,39 @@ + + + + + The foo argument + + + + The baz argument + + true + + + + The bar argument + + bar + + + + + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.json b/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.json new file mode 100644 index 0000000..0ab9329 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.json @@ -0,0 +1,7 @@ +{ + "name": "argument_name", + "is_required": true, + "is_array": false, + "description": "", + "default": null +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.md b/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.md new file mode 100644 index 0000000..88f311a --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.txt b/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.txt new file mode 100644 index 0000000..5503518 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.txt @@ -0,0 +1 @@ + argument_name diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.xml b/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.xml new file mode 100644 index 0000000..cb37f81 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_1.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.json b/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.json new file mode 100644 index 0000000..7450016 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.json @@ -0,0 +1,7 @@ +{ + "name": "argument_name", + "is_required": false, + "is_array": true, + "description": "argument description", + "default": [] +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.md b/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.md new file mode 100644 index 0000000..3cdb00c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: no +* Is array: yes +* Description: argument description +* Default: `array ()` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.txt b/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.txt new file mode 100644 index 0000000..e713660 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.txt @@ -0,0 +1 @@ + argument_name argument description diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.xml b/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.xml new file mode 100644 index 0000000..629da5a --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_2.xml @@ -0,0 +1,5 @@ + + + argument description + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.json b/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.json new file mode 100644 index 0000000..9a83c5a --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.json @@ -0,0 +1,7 @@ +{ + "name": "argument_name", + "is_required": false, + "is_array": false, + "description": "argument description", + "default": "default_value" +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.md b/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.md new file mode 100644 index 0000000..be1c443 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: no +* Is array: no +* Description: argument description +* Default: `'default_value'` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.txt b/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.txt new file mode 100644 index 0000000..6b76639 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.txt @@ -0,0 +1 @@ + argument_name argument description [default: "default_value"] diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.xml b/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.xml new file mode 100644 index 0000000..399a5c8 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_3.xml @@ -0,0 +1,7 @@ + + + argument description + + default_value + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.json b/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.json new file mode 100644 index 0000000..cbcb19b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.json @@ -0,0 +1,7 @@ +{ + "name": "argument_name", + "is_required": true, + "is_array": false, + "description": "multiline argument description", + "default": null +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.md b/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.md new file mode 100644 index 0000000..f026ab3 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.md @@ -0,0 +1,8 @@ +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: multiline + argument description +* Default: `NULL` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.txt b/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.txt new file mode 100644 index 0000000..fc7d669 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.txt @@ -0,0 +1,2 @@ + argument_name multiline + argument description diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.xml b/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.xml new file mode 100644 index 0000000..5ca135e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_4.xml @@ -0,0 +1,6 @@ + + + multiline +argument description + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.json b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.json new file mode 100644 index 0000000..b61ecf7 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.json @@ -0,0 +1,7 @@ +{ + "name": "argument_name", + "is_required": false, + "is_array": false, + "description": "argument description", + "default": "INF" +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.md b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.md new file mode 100644 index 0000000..293d816 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: no +* Is array: no +* Description: argument description +* Default: `INF` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.txt b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.txt new file mode 100644 index 0000000..c32d768 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.txt @@ -0,0 +1 @@ + argument_name argument description [default: INF] diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.xml b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.xml new file mode 100644 index 0000000..d457260 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.xml @@ -0,0 +1,7 @@ + + + argument description + + INF + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.json b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.json new file mode 100644 index 0000000..9334235 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.json @@ -0,0 +1,7 @@ +{ + "name": "argument_name", + "is_required": false, + "is_array": false, + "description": "argument description", + "default": "style" +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.md b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.md new file mode 100644 index 0000000..45adf2f --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: no +* Is array: no +* Description: argument description +* Default: `'style'` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.txt b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.txt new file mode 100644 index 0000000..35384a6 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.txt @@ -0,0 +1 @@ + argument_name argument description [default: "\style\"] diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.xml b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.xml new file mode 100644 index 0000000..73332c7 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.xml @@ -0,0 +1,7 @@ + + + argument description + + <comment>style</> + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.json b/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.json new file mode 100644 index 0000000..44aa2c2 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.json @@ -0,0 +1,4 @@ +{ + "arguments": [], + "options": [] +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.md b/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.md new file mode 100644 index 0000000..e69de29 diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.txt b/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.txt new file mode 100644 index 0000000..e69de29 diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.xml b/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.xml new file mode 100644 index 0000000..b5481ce --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_1.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.json b/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.json new file mode 100644 index 0000000..7cfd57e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.json @@ -0,0 +1,12 @@ +{ + "arguments": { + "argument_name": { + "name": "argument_name", + "is_required": true, + "is_array": false, + "description": "", + "default": null + } + }, + "options": [] +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.md b/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.md new file mode 100644 index 0000000..923191c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.md @@ -0,0 +1,9 @@ +### Arguments: + +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.txt b/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.txt new file mode 100644 index 0000000..73b0f30 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.txt @@ -0,0 +1,2 @@ +Arguments: + argument_name diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.xml b/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.xml new file mode 100644 index 0000000..102efc1 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_2.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.json b/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.json new file mode 100644 index 0000000..3b3cf73 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.json @@ -0,0 +1,14 @@ +{ + "arguments": [], + "options": { + "option_name": { + "name": "--option_name", + "shortcut": "-o", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "", + "default": false + } + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.md b/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.md new file mode 100644 index 0000000..40fd7b0 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.md @@ -0,0 +1,11 @@ +### Options: + +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.txt b/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.txt new file mode 100644 index 0000000..c02766f --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.txt @@ -0,0 +1,2 @@ +Options: + -o, --option_name diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.xml b/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.xml new file mode 100644 index 0000000..bc95151 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_3.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.json b/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.json new file mode 100644 index 0000000..d4a51e8 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.json @@ -0,0 +1,22 @@ +{ + "arguments": { + "argument_name": { + "name": "argument_name", + "is_required": true, + "is_array": false, + "description": "", + "default": null + } + }, + "options": { + "option_name": { + "name": "--option_name", + "shortcut": "-o", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "", + "default": false + } + } +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.md b/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.md new file mode 100644 index 0000000..a31feea --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.md @@ -0,0 +1,21 @@ +### Arguments: + +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` + +### Options: + +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.txt b/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.txt new file mode 100644 index 0000000..63aa81d --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.txt @@ -0,0 +1,5 @@ +Arguments: + argument_name + +Options: + -o, --option_name diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.xml b/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.xml new file mode 100644 index 0000000..cffceec --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_definition_4.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_1.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_1.json new file mode 100644 index 0000000..f86bf96 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_1.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": false, + "is_value_required": false, + "is_multiple": false, + "description": "", + "default": false +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_1.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_1.md new file mode 100644 index 0000000..6f9e9a7 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_1.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_1.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_1.txt new file mode 100644 index 0000000..3a5e4ee --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_1.txt @@ -0,0 +1 @@ + -o, --option_name diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_1.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_1.xml new file mode 100644 index 0000000..8a64ea6 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_1.xml @@ -0,0 +1,4 @@ + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_2.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_2.json new file mode 100644 index 0000000..32dbab2 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_2.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": true, + "is_value_required": false, + "is_multiple": false, + "description": "option description", + "default": "default_value" +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_2.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_2.md new file mode 100644 index 0000000..634ac0b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_2.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: no +* Is multiple: no +* Description: option description +* Default: `'default_value'` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_2.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_2.txt new file mode 100644 index 0000000..1009eff --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_2.txt @@ -0,0 +1 @@ + -o, --option_name[=OPTION_NAME] option description [default: "default_value"] diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_2.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_2.xml new file mode 100644 index 0000000..4afac5b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_2.xml @@ -0,0 +1,7 @@ + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_3.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_3.json new file mode 100644 index 0000000..6d55a6e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_3.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": true, + "is_value_required": true, + "is_multiple": false, + "description": "option description", + "default": null +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_3.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_3.md new file mode 100644 index 0000000..3428289 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_3.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: option description +* Default: `NULL` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_3.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_3.txt new file mode 100644 index 0000000..947bb65 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_3.txt @@ -0,0 +1 @@ + -o, --option_name=OPTION_NAME option description diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_3.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_3.xml new file mode 100644 index 0000000..dcc0631 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_3.xml @@ -0,0 +1,5 @@ + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_4.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_4.json new file mode 100644 index 0000000..788a8ed --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_4.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": true, + "is_value_required": false, + "is_multiple": true, + "description": "option description", + "default": [] +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_4.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_4.md new file mode 100644 index 0000000..8ffba56 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_4.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: no +* Is multiple: yes +* Description: option description +* Default: `array ()` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_4.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_4.txt new file mode 100644 index 0000000..27edf77 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_4.txt @@ -0,0 +1 @@ + -o, --option_name[=OPTION_NAME] option description (multiple values allowed) diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_4.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_4.xml new file mode 100644 index 0000000..5e2418b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_4.xml @@ -0,0 +1,5 @@ + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_5.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_5.json new file mode 100644 index 0000000..9f34d83 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_5.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": true, + "is_value_required": true, + "is_multiple": false, + "description": "multiline option description", + "default": null +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_5.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_5.md new file mode 100644 index 0000000..82f51ca --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_5.md @@ -0,0 +1,10 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: multiline + option description +* Default: `NULL` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_5.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_5.txt new file mode 100644 index 0000000..9563b4c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_5.txt @@ -0,0 +1,2 @@ + -o, --option_name=OPTION_NAME multiline + option description diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_5.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_5.xml new file mode 100644 index 0000000..90040cc --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_5.xml @@ -0,0 +1,6 @@ + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_6.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_6.json new file mode 100644 index 0000000..0638de0 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_6.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o|-O", + "accept_value": true, + "is_value_required": true, + "is_multiple": false, + "description": "option with multiple shortcuts", + "default": null +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_6.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_6.md new file mode 100644 index 0000000..ed1ea1c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_6.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o|-O` +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: option with multiple shortcuts +* Default: `NULL` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_6.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_6.txt new file mode 100644 index 0000000..0e6c975 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_6.txt @@ -0,0 +1 @@ + -o|O, --option_name=OPTION_NAME option with multiple shortcuts diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_6.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_6.xml new file mode 100644 index 0000000..06126a2 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_6.xml @@ -0,0 +1,5 @@ + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.json new file mode 100644 index 0000000..7c96ad3 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": true, + "is_value_required": false, + "is_multiple": false, + "description": "option description", + "default": "INF" +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.md new file mode 100644 index 0000000..b57bd04 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: no +* Is multiple: no +* Description: option description +* Default: `INF` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.txt new file mode 100644 index 0000000..d467dcf --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.txt @@ -0,0 +1 @@ + -o, --option_name[=OPTION_NAME] option description [default: INF] diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.xml new file mode 100644 index 0000000..5d1d217 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.xml @@ -0,0 +1,7 @@ + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.json new file mode 100644 index 0000000..df328bf --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": true, + "is_value_required": true, + "is_multiple": false, + "description": "option description", + "default": "style" +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.md new file mode 100644 index 0000000..3f6dd23 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: option description +* Default: `'style'` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.txt new file mode 100644 index 0000000..880a535 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.txt @@ -0,0 +1 @@ + -o, --option_name=OPTION_NAME option description [default: "\style\"] diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.xml new file mode 100644 index 0000000..764b9e6 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style.xml @@ -0,0 +1,7 @@ + + diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.json b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.json new file mode 100644 index 0000000..b175455 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.json @@ -0,0 +1,12 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": true, + "is_value_required": true, + "is_multiple": true, + "description": "option description", + "default": [ + "Hello", + "world" + ] +} diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.md b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.md new file mode 100644 index 0000000..24e58b5 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: yes +* Is multiple: yes +* Description: option description +* Default: `array ( 0 => 'Hello', 1 => 'world',)` diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.txt b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.txt new file mode 100644 index 0000000..265c18c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.txt @@ -0,0 +1 @@ + -o, --option_name=OPTION_NAME option description [default: ["\Hello\","\world\"]] (multiple values allowed) diff --git a/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.xml b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.xml new file mode 100644 index 0000000..09dc865 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.xml @@ -0,0 +1,8 @@ + + diff --git a/core/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleStackTest.php b/core/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleStackTest.php new file mode 100644 index 0000000..6cd7c82 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleStackTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Formatter; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Formatter\OutputFormatterStyleStack; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +class OutputFormatterStyleStackTest extends TestCase +{ + public function testPush() + { + $stack = new OutputFormatterStyleStack(); + $stack->push($s1 = new OutputFormatterStyle('white', 'black')); + $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue')); + + $this->assertEquals($s2, $stack->getCurrent()); + + $stack->push($s3 = new OutputFormatterStyle('green', 'red')); + + $this->assertEquals($s3, $stack->getCurrent()); + } + + public function testPop() + { + $stack = new OutputFormatterStyleStack(); + $stack->push($s1 = new OutputFormatterStyle('white', 'black')); + $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue')); + + $this->assertEquals($s2, $stack->pop()); + $this->assertEquals($s1, $stack->pop()); + } + + public function testPopEmpty() + { + $stack = new OutputFormatterStyleStack(); + $style = new OutputFormatterStyle(); + + $this->assertEquals($style, $stack->pop()); + } + + public function testPopNotLast() + { + $stack = new OutputFormatterStyleStack(); + $stack->push($s1 = new OutputFormatterStyle('white', 'black')); + $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue')); + $stack->push($s3 = new OutputFormatterStyle('green', 'red')); + + $this->assertEquals($s2, $stack->pop($s2)); + $this->assertEquals($s1, $stack->pop()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidPop() + { + $stack = new OutputFormatterStyleStack(); + $stack->push(new OutputFormatterStyle('white', 'black')); + $stack->pop(new OutputFormatterStyle('yellow', 'blue')); + } +} diff --git a/core/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php b/core/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php new file mode 100644 index 0000000..ddf7790 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Formatter; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +class OutputFormatterStyleTest extends TestCase +{ + public function testConstructor() + { + $style = new OutputFormatterStyle('green', 'black', array('bold', 'underscore')); + $this->assertEquals("\033[32;40;1;4mfoo\033[39;49;22;24m", $style->apply('foo')); + + $style = new OutputFormatterStyle('red', null, array('blink')); + $this->assertEquals("\033[31;5mfoo\033[39;25m", $style->apply('foo')); + + $style = new OutputFormatterStyle(null, 'white'); + $this->assertEquals("\033[47mfoo\033[49m", $style->apply('foo')); + } + + public function testForeground() + { + $style = new OutputFormatterStyle(); + + $style->setForeground('black'); + $this->assertEquals("\033[30mfoo\033[39m", $style->apply('foo')); + + $style->setForeground('blue'); + $this->assertEquals("\033[34mfoo\033[39m", $style->apply('foo')); + + $style->setForeground('default'); + $this->assertEquals("\033[39mfoo\033[39m", $style->apply('foo')); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $style->setForeground('undefined-color'); + } + + public function testBackground() + { + $style = new OutputFormatterStyle(); + + $style->setBackground('black'); + $this->assertEquals("\033[40mfoo\033[49m", $style->apply('foo')); + + $style->setBackground('yellow'); + $this->assertEquals("\033[43mfoo\033[49m", $style->apply('foo')); + + $style->setBackground('default'); + $this->assertEquals("\033[49mfoo\033[49m", $style->apply('foo')); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $style->setBackground('undefined-color'); + } + + public function testOptions() + { + $style = new OutputFormatterStyle(); + + $style->setOptions(array('reverse', 'conceal')); + $this->assertEquals("\033[7;8mfoo\033[27;28m", $style->apply('foo')); + + $style->setOption('bold'); + $this->assertEquals("\033[7;8;1mfoo\033[27;28;22m", $style->apply('foo')); + + $style->unsetOption('reverse'); + $this->assertEquals("\033[8;1mfoo\033[28;22m", $style->apply('foo')); + + $style->setOption('bold'); + $this->assertEquals("\033[8;1mfoo\033[28;22m", $style->apply('foo')); + + $style->setOptions(array('bold')); + $this->assertEquals("\033[1mfoo\033[22m", $style->apply('foo')); + + try { + $style->setOption('foo'); + $this->fail('->setOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->setOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + $this->assertContains('Invalid option specified: "foo"', $e->getMessage(), '->setOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + } + + try { + $style->unsetOption('foo'); + $this->fail('->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + $this->assertContains('Invalid option specified: "foo"', $e->getMessage(), '->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + } + } +} diff --git a/core/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php b/core/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php new file mode 100644 index 0000000..00e5231 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php @@ -0,0 +1,283 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Formatter; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +class OutputFormatterTest extends TestCase +{ + public function testEmptyTag() + { + $formatter = new OutputFormatter(true); + $this->assertEquals('foo<>bar', $formatter->format('foo<>bar')); + } + + public function testLGCharEscaping() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals('fooformat('foo\\assertEquals('foo << bar', $formatter->format('foo << bar')); + $this->assertEquals('foo << bar \\', $formatter->format('foo << bar \\')); + $this->assertEquals("foo << \033[32mbar \\ baz\033[39m \\", $formatter->format('foo << bar \\ baz \\')); + $this->assertEquals('some info', $formatter->format('\\some info\\')); + $this->assertEquals('\\some info\\', OutputFormatter::escape('some info')); + + $this->assertEquals( + "\033[33mSymfony\\Component\\Console does work very well!\033[39m", + $formatter->format('Symfony\Component\Console does work very well!') + ); + } + + public function testBundledStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertTrue($formatter->hasStyle('error')); + $this->assertTrue($formatter->hasStyle('info')); + $this->assertTrue($formatter->hasStyle('comment')); + $this->assertTrue($formatter->hasStyle('question')); + + $this->assertEquals( + "\033[37;41msome error\033[39;49m", + $formatter->format('some error') + ); + $this->assertEquals( + "\033[32msome info\033[39m", + $formatter->format('some info') + ); + $this->assertEquals( + "\033[33msome comment\033[39m", + $formatter->format('some comment') + ); + $this->assertEquals( + "\033[30;46msome question\033[39;49m", + $formatter->format('some question') + ); + } + + public function testNestedStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "\033[37;41msome \033[39;49m\033[32msome info\033[39m\033[37;41m error\033[39;49m", + $formatter->format('some some info error') + ); + } + + public function testAdjacentStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "\033[37;41msome error\033[39;49m\033[32msome info\033[39m", + $formatter->format('some errorsome info') + ); + } + + public function testStyleMatchingNotGreedy() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "(\033[32m>=2.0,<2.3\033[39m)", + $formatter->format('(>=2.0,<2.3)') + ); + } + + public function testStyleEscaping() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "(\033[32mz>=2.0,<<format('('.$formatter->escape('z>=2.0,<\\<)') + ); + + $this->assertEquals( + "\033[32msome error\033[39m", + $formatter->format(''.$formatter->escape('some error').'') + ); + } + + public function testDeepNestedStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "\033[37;41merror\033[39;49m\033[32minfo\033[39m\033[33mcomment\033[39m\033[37;41merror\033[39;49m", + $formatter->format('errorinfocommenterror') + ); + } + + public function testNewStyle() + { + $formatter = new OutputFormatter(true); + + $style = new OutputFormatterStyle('blue', 'white'); + $formatter->setStyle('test', $style); + + $this->assertEquals($style, $formatter->getStyle('test')); + $this->assertNotEquals($style, $formatter->getStyle('info')); + + $style = new OutputFormatterStyle('blue', 'white'); + $formatter->setStyle('b', $style); + + $this->assertEquals("\033[34;47msome \033[39;49m\033[34;47mcustom\033[39;49m\033[34;47m msg\033[39;49m", $formatter->format('some custom msg')); + } + + public function testRedefineStyle() + { + $formatter = new OutputFormatter(true); + + $style = new OutputFormatterStyle('blue', 'white'); + $formatter->setStyle('info', $style); + + $this->assertEquals("\033[34;47msome custom msg\033[39;49m", $formatter->format('some custom msg')); + } + + public function testInlineStyle() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals("\033[34;41msome text\033[39;49m", $formatter->format('some text')); + $this->assertEquals("\033[34;41msome text\033[39;49m", $formatter->format('some text')); + } + + public function testNonStyleTag() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals("\033[32msome \033[39m\033[32m\033[39m\033[32m \033[39m\033[32m\033[39m\033[32m styled \033[39m\033[32m

    \033[39m\033[32msingle-char tag\033[39m\033[32m

    \033[39m", $formatter->format('some styled

    single-char tag

    ')); + } + + public function testFormatLongString() + { + $formatter = new OutputFormatter(true); + $long = str_repeat('\\', 14000); + $this->assertEquals("\033[37;41msome error\033[39;49m".$long, $formatter->format('some error'.$long)); + } + + public function testFormatToStringObject() + { + $formatter = new OutputFormatter(false); + $this->assertEquals( + 'some info', $formatter->format(new TableCell()) + ); + } + + public function testNotDecoratedFormatter() + { + $formatter = new OutputFormatter(false); + + $this->assertTrue($formatter->hasStyle('error')); + $this->assertTrue($formatter->hasStyle('info')); + $this->assertTrue($formatter->hasStyle('comment')); + $this->assertTrue($formatter->hasStyle('question')); + + $this->assertEquals( + 'some error', $formatter->format('some error') + ); + $this->assertEquals( + 'some info', $formatter->format('some info') + ); + $this->assertEquals( + 'some comment', $formatter->format('some comment') + ); + $this->assertEquals( + 'some question', $formatter->format('some question') + ); + $this->assertEquals( + 'some text with inline style', $formatter->format('some text with inline style') + ); + + $formatter->setDecorated(true); + + $this->assertEquals( + "\033[37;41msome error\033[39;49m", $formatter->format('some error') + ); + $this->assertEquals( + "\033[32msome info\033[39m", $formatter->format('some info') + ); + $this->assertEquals( + "\033[33msome comment\033[39m", $formatter->format('some comment') + ); + $this->assertEquals( + "\033[30;46msome question\033[39;49m", $formatter->format('some question') + ); + $this->assertEquals( + "\033[31msome text with inline style\033[39m", $formatter->format('some text with inline style') + ); + } + + public function testContentWithLineBreaks() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals(<<format(<<<'EOF' + +some text +EOF + )); + + $this->assertEquals(<<format(<<<'EOF' +some text + +EOF + )); + + $this->assertEquals(<<format(<<<'EOF' + +some text + +EOF + )); + + $this->assertEquals(<<format(<<<'EOF' + +some text +more text + +EOF + )); + } +} + +class TableCell +{ + public function __toString() + { + return 'some info'; + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/FormatterHelperTest.php b/core/vendor/symfony/console/Tests/Helper/FormatterHelperTest.php new file mode 100644 index 0000000..746c2b4 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/FormatterHelperTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\FormatterHelper; + +class FormatterHelperTest extends TestCase +{ + public function testFormatSection() + { + $formatter = new FormatterHelper(); + + $this->assertEquals( + '[cli] Some text to display', + $formatter->formatSection('cli', 'Some text to display'), + '::formatSection() formats a message in a section' + ); + } + + public function testFormatBlock() + { + $formatter = new FormatterHelper(); + + $this->assertEquals( + ' Some text to display ', + $formatter->formatBlock('Some text to display', 'error'), + '::formatBlock() formats a message in a block' + ); + + $this->assertEquals( + ' Some text to display '."\n". + ' foo bar ', + $formatter->formatBlock(array('Some text to display', 'foo bar'), 'error'), + '::formatBlock() formats a message in a block' + ); + + $this->assertEquals( + ' '."\n". + ' Some text to display '."\n". + ' ', + $formatter->formatBlock('Some text to display', 'error', true), + '::formatBlock() formats a message in a block' + ); + } + + /** + * @requires extension mbstring + */ + public function testFormatBlockWithDiacriticLetters() + { + $formatter = new FormatterHelper(); + + $this->assertEquals( + ' '."\n". + ' Du texte à afficher '."\n". + ' ', + $formatter->formatBlock('Du texte à afficher', 'error', true), + '::formatBlock() formats a message in a block' + ); + } + + /** + * @requires extension mbstring + */ + public function testFormatBlockWithDoubleWidthDiacriticLetters() + { + $formatter = new FormatterHelper(); + $this->assertEquals( + ' '."\n". + ' 表示するテキスト '."\n". + ' ', + $formatter->formatBlock('表示するテキスト', 'error', true), + '::formatBlock() formats a message in a block' + ); + } + + public function testFormatBlockLGEscaping() + { + $formatter = new FormatterHelper(); + + $this->assertEquals( + ' '."\n". + ' \some info\ '."\n". + ' ', + $formatter->formatBlock('some info', 'error', true), + '::formatBlock() escapes \'<\' chars' + ); + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/HelperSetTest.php b/core/vendor/symfony/console/Tests/Helper/HelperSetTest.php new file mode 100644 index 0000000..8f42cf9 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/HelperSetTest.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Command\Command; + +class HelperSetTest extends TestCase +{ + public function testConstructor() + { + $mock_helper = $this->getGenericMockHelper('fake_helper'); + $helperset = new HelperSet(array('fake_helper_alias' => $mock_helper)); + + $this->assertEquals($mock_helper, $helperset->get('fake_helper_alias'), '__construct sets given helper to helpers'); + $this->assertTrue($helperset->has('fake_helper_alias'), '__construct sets helper alias for given helper'); + } + + public function testSet() + { + $helperset = new HelperSet(); + $helperset->set($this->getGenericMockHelper('fake_helper', $helperset)); + $this->assertTrue($helperset->has('fake_helper'), '->set() adds helper to helpers'); + + $helperset = new HelperSet(); + $helperset->set($this->getGenericMockHelper('fake_helper_01', $helperset)); + $helperset->set($this->getGenericMockHelper('fake_helper_02', $helperset)); + $this->assertTrue($helperset->has('fake_helper_01'), '->set() will set multiple helpers on consecutive calls'); + $this->assertTrue($helperset->has('fake_helper_02'), '->set() will set multiple helpers on consecutive calls'); + + $helperset = new HelperSet(); + $helperset->set($this->getGenericMockHelper('fake_helper', $helperset), 'fake_helper_alias'); + $this->assertTrue($helperset->has('fake_helper'), '->set() adds helper alias when set'); + $this->assertTrue($helperset->has('fake_helper_alias'), '->set() adds helper alias when set'); + } + + public function testHas() + { + $helperset = new HelperSet(array('fake_helper_alias' => $this->getGenericMockHelper('fake_helper'))); + $this->assertTrue($helperset->has('fake_helper'), '->has() finds set helper'); + $this->assertTrue($helperset->has('fake_helper_alias'), '->has() finds set helper by alias'); + } + + public function testGet() + { + $helper_01 = $this->getGenericMockHelper('fake_helper_01'); + $helper_02 = $this->getGenericMockHelper('fake_helper_02'); + $helperset = new HelperSet(array('fake_helper_01_alias' => $helper_01, 'fake_helper_02_alias' => $helper_02)); + $this->assertEquals($helper_01, $helperset->get('fake_helper_01'), '->get() returns correct helper by name'); + $this->assertEquals($helper_01, $helperset->get('fake_helper_01_alias'), '->get() returns correct helper by alias'); + $this->assertEquals($helper_02, $helperset->get('fake_helper_02'), '->get() returns correct helper by name'); + $this->assertEquals($helper_02, $helperset->get('fake_helper_02_alias'), '->get() returns correct helper by alias'); + + $helperset = new HelperSet(); + try { + $helperset->get('foo'); + $this->fail('->get() throws \InvalidArgumentException when helper not found'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->get() throws \InvalidArgumentException when helper not found'); + $this->assertContains('The helper "foo" is not defined.', $e->getMessage(), '->get() throws \InvalidArgumentException when helper not found'); + } + } + + public function testSetCommand() + { + $cmd_01 = new Command('foo'); + $cmd_02 = new Command('bar'); + + $helperset = new HelperSet(); + $helperset->setCommand($cmd_01); + $this->assertEquals($cmd_01, $helperset->getCommand(), '->setCommand() stores given command'); + + $helperset = new HelperSet(); + $helperset->setCommand($cmd_01); + $helperset->setCommand($cmd_02); + $this->assertEquals($cmd_02, $helperset->getCommand(), '->setCommand() overwrites stored command with consecutive calls'); + } + + public function testGetCommand() + { + $cmd = new Command('foo'); + $helperset = new HelperSet(); + $helperset->setCommand($cmd); + $this->assertEquals($cmd, $helperset->getCommand(), '->getCommand() retrieves stored command'); + } + + public function testIteration() + { + $helperset = new HelperSet(); + $helperset->set($this->getGenericMockHelper('fake_helper_01', $helperset)); + $helperset->set($this->getGenericMockHelper('fake_helper_02', $helperset)); + + $helpers = array('fake_helper_01', 'fake_helper_02'); + $i = 0; + + foreach ($helperset as $helper) { + $this->assertEquals($helpers[$i++], $helper->getName()); + } + } + + private function getGenericMockHelper($name, HelperSet $helperset = null) + { + $mock_helper = $this->getMockBuilder('\Symfony\Component\Console\Helper\HelperInterface')->getMock(); + $mock_helper->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + + if ($helperset) { + $mock_helper->expects($this->any()) + ->method('setHelperSet') + ->with($this->equalTo($helperset)); + } + + return $mock_helper; + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/HelperTest.php b/core/vendor/symfony/console/Tests/Helper/HelperTest.php new file mode 100644 index 0000000..1847582 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/HelperTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\Helper; + +class HelperTest extends TestCase +{ + public function formatTimeProvider() + { + return array( + array(0, '< 1 sec'), + array(1, '1 sec'), + array(2, '2 secs'), + array(59, '59 secs'), + array(60, '1 min'), + array(61, '1 min'), + array(119, '1 min'), + array(120, '2 mins'), + array(121, '2 mins'), + array(3599, '59 mins'), + array(3600, '1 hr'), + array(7199, '1 hr'), + array(7200, '2 hrs'), + array(7201, '2 hrs'), + array(86399, '23 hrs'), + array(86400, '1 day'), + array(86401, '1 day'), + array(172799, '1 day'), + array(172800, '2 days'), + array(172801, '2 days'), + ); + } + + /** + * @dataProvider formatTimeProvider + * + * @param int $secs + * @param string $expectedFormat + */ + public function testFormatTime($secs, $expectedFormat) + { + $this->assertEquals($expectedFormat, Helper::formatTime($secs)); + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/LegacyDialogHelperTest.php b/core/vendor/symfony/console/Tests/Helper/LegacyDialogHelperTest.php new file mode 100644 index 0000000..b7d1b5c --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/LegacyDialogHelperTest.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Helper\DialogHelper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * @group legacy + */ +class LegacyDialogHelperTest extends TestCase +{ + public function testSelect() + { + $dialog = new DialogHelper(); + + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $heroes = array('Superman', 'Batman', 'Spiderman'); + + $dialog->setInputStream($this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n")); + $this->assertEquals('2', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, '2')); + $this->assertEquals('1', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes)); + $this->assertEquals('1', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes)); + $this->assertEquals('1', $dialog->select($output = $this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', false)); + + rewind($output->getStream()); + $this->assertContains('Input "Fabien" is not a superhero!', stream_get_contents($output->getStream())); + + try { + $this->assertEquals('1', $dialog->select($output = $this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, 1)); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals('Value "Fabien" is invalid', $e->getMessage()); + } + + $this->assertEquals(array('1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true)); + $this->assertEquals(array('0', '2'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true)); + $this->assertEquals(array('0', '2'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true)); + $this->assertEquals(array('0', '1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, '0,1', false, 'Input "%s" is not a superhero!', true)); + $this->assertEquals(array('0', '1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, ' 0 , 1 ', false, 'Input "%s" is not a superhero!', true)); + } + + public function testSelectOnErrorOutput() + { + $dialog = new DialogHelper(); + + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $heroes = array('Superman', 'Batman', 'Spiderman'); + + $dialog->setInputStream($this->getInputStream("Stdout\n1\n")); + $this->assertEquals('1', $dialog->select($output = $this->getConsoleOutput($this->getOutputStream()), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', false)); + + rewind($output->getErrorOutput()->getStream()); + $this->assertContains('Input "Stdout" is not a superhero!', stream_get_contents($output->getErrorOutput()->getStream())); + } + + public function testAsk() + { + $dialog = new DialogHelper(); + + $dialog->setInputStream($this->getInputStream("\n8AM\n")); + + $this->assertEquals('2PM', $dialog->ask($this->getOutputStream(), 'What time is it?', '2PM')); + $this->assertEquals('8AM', $dialog->ask($output = $this->getOutputStream(), 'What time is it?', '2PM')); + + rewind($output->getStream()); + $this->assertEquals('What time is it?', stream_get_contents($output->getStream())); + } + + public function testAskOnErrorOutput() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stderr` is required to test stderr output functionality'); + } + + $dialog = new DialogHelper(); + + $dialog->setInputStream($this->getInputStream("not stdout\n")); + + $this->assertEquals('not stdout', $dialog->ask($output = $this->getConsoleOutput($this->getOutputStream()), 'Where should output go?', 'stderr')); + + rewind($output->getErrorOutput()->getStream()); + $this->assertEquals('Where should output go?', stream_get_contents($output->getErrorOutput()->getStream())); + } + + public function testAskWithAutocomplete() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // Acm + // AcsTest + // + // + // Test + // + // S + // F00oo + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + + $dialog = new DialogHelper(); + $dialog->setInputStream($inputStream); + + $bundles = array('AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('FooBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + } + + /** + * @group tty + */ + public function testAskHiddenResponse() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is not supported on Windows'); + } + + $dialog = new DialogHelper(); + + $dialog->setInputStream($this->getInputStream("8AM\n")); + + $this->assertEquals('8AM', $dialog->askHiddenResponse($this->getOutputStream(), 'What time is it?')); + } + + /** + * @group tty + */ + public function testAskHiddenResponseOnErrorOutput() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is not supported on Windows'); + } + + $dialog = new DialogHelper(); + + $dialog->setInputStream($this->getInputStream("8AM\n")); + + $this->assertEquals('8AM', $dialog->askHiddenResponse($output = $this->getConsoleOutput($this->getOutputStream()), 'What time is it?')); + + rewind($output->getErrorOutput()->getStream()); + $this->assertContains('What time is it?', stream_get_contents($output->getErrorOutput()->getStream())); + } + + public function testAskConfirmation() + { + $dialog = new DialogHelper(); + + $dialog->setInputStream($this->getInputStream("\n\n")); + $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?')); + $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false)); + + $dialog->setInputStream($this->getInputStream("y\nyes\n")); + $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false)); + $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false)); + + $dialog->setInputStream($this->getInputStream("n\nno\n")); + $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', true)); + $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', true)); + } + + public function testAskAndValidate() + { + $dialog = new DialogHelper(); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = 'What color was the white horse of Henry IV?'; + $error = 'This is not a color!'; + $validator = function ($color) use ($error) { + if (!in_array($color, array('white', 'black'))) { + throw new \InvalidArgumentException($error); + } + + return $color; + }; + + $dialog->setInputStream($this->getInputStream("\nblack\n")); + $this->assertEquals('white', $dialog->askAndValidate($this->getOutputStream(), $question, $validator, 2, 'white')); + $this->assertEquals('black', $dialog->askAndValidate($this->getOutputStream(), $question, $validator, 2, 'white')); + + $dialog->setInputStream($this->getInputStream("green\nyellow\norange\n")); + try { + $this->assertEquals('white', $dialog->askAndValidate($output = $this->getConsoleOutput($this->getOutputStream()), $question, $validator, 2, 'white')); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals($error, $e->getMessage()); + rewind($output->getErrorOutput()->getStream()); + $this->assertContains('What color was the white horse of Henry IV?', stream_get_contents($output->getErrorOutput()->getStream())); + } + } + + public function testNoInteraction() + { + $dialog = new DialogHelper(); + + $input = new ArrayInput(array()); + $input->setInteractive(false); + + $dialog->setInput($input); + + $this->assertEquals('not yet', $dialog->ask($this->getOutputStream(), 'Do you have a job?', 'not yet')); + } + + protected function getInputStream($input) + { + $stream = fopen('php://memory', 'r+', false); + fwrite($stream, $input); + rewind($stream); + + return $stream; + } + + protected function getOutputStream() + { + return new StreamOutput(fopen('php://memory', 'r+', false)); + } + + protected function getConsoleOutput($stderr) + { + $output = new ConsoleOutput(); + $output->setErrorOutput($stderr); + + return $output; + } + + private function hasSttyAvailable() + { + exec('stty 2>&1', $output, $exitcode); + + return 0 === $exitcode; + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/LegacyProgressHelperTest.php b/core/vendor/symfony/console/Tests/Helper/LegacyProgressHelperTest.php new file mode 100644 index 0000000..76d003e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/LegacyProgressHelperTest.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\ProgressHelper; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * @group legacy + * @group time-sensitive + */ +class LegacyProgressHelperTest extends TestCase +{ + public function testAdvance() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 1 [->--------------------------]'), stream_get_contents($output->getStream())); + } + + public function testAdvanceWithStep() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->advance(5); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 5 [----->----------------------]'), stream_get_contents($output->getStream())); + } + + public function testAdvanceMultipleTimes() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->advance(3); + $progress->advance(2); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 3 [--->------------------------]').$this->generateOutput(' 5 [----->----------------------]'), stream_get_contents($output->getStream())); + } + + public function testCustomizations() + { + $progress = new ProgressHelper(); + $progress->setBarWidth(10); + $progress->setBarCharacter('_'); + $progress->setEmptyBarCharacter(' '); + $progress->setProgressCharacter('/'); + $progress->setFormat(' %current%/%max% [%bar%] %percent%%'); + $progress->start($output = $this->getOutputStream(), 10); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 1/10 [_/ ] 10%'), stream_get_contents($output->getStream())); + } + + public function testPercent() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->display(); + $progress->advance(); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 0/50 [>---------------------------] 0%').$this->generateOutput(' 1/50 [>---------------------------] 2%').$this->generateOutput(' 2/50 [=>--------------------------] 4%'), stream_get_contents($output->getStream())); + } + + public function testOverwriteWithShorterLine() + { + $progress = new ProgressHelper(); + $progress->setFormat(' %current%/%max% [%bar%] %percent%%'); + $progress->start($output = $this->getOutputStream(), 50); + $progress->display(); + $progress->advance(); + + // set shorter format + $progress->setFormat(' %current%/%max% [%bar%]'); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 1/50 [>---------------------------] 2%'). + $this->generateOutput(' 2/50 [=>--------------------------] '), + stream_get_contents($output->getStream()) + ); + } + + public function testSetCurrentProgress() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->display(); + $progress->advance(); + $progress->setCurrent(15); + $progress->setCurrent(25); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 1/50 [>---------------------------] 2%'). + $this->generateOutput(' 15/50 [========>-------------------] 30%'). + $this->generateOutput(' 25/50 [==============>-------------] 50%'), + stream_get_contents($output->getStream()) + ); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You must start the progress bar + */ + public function testSetCurrentBeforeStarting() + { + $progress = new ProgressHelper(); + $progress->setCurrent(15); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You can't regress the progress bar + */ + public function testRegressProgress() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->setCurrent(15); + $progress->setCurrent(10); + } + + public function testRedrawFrequency() + { + $progress = $this->getMockBuilder('Symfony\Component\Console\Helper\ProgressHelper')->setMethods(array('display'))->getMock(); + $progress->expects($this->exactly(4)) + ->method('display'); + + $progress->setRedrawFrequency(2); + + $progress->start($output = $this->getOutputStream(), 6); + $progress->setCurrent(1); + $progress->advance(2); + $progress->advance(2); + $progress->advance(1); + } + + /** + * @requires extension mbstring + */ + public function testMultiByteSupport() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->setBarCharacter('■'); + $progress->advance(3); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 3 [■■■>------------------------]'), stream_get_contents($output->getStream())); + } + + public function testClear() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->setCurrent(25); + $progress->clear(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 25/50 [==============>-------------] 50%').$this->generateOutput(''), + stream_get_contents($output->getStream()) + ); + } + + public function testPercentNotHundredBeforeComplete() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 200); + $progress->display(); + $progress->advance(199); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 0/200 [>---------------------------] 0%').$this->generateOutput(' 199/200 [===========================>] 99%').$this->generateOutput(' 200/200 [============================] 100%'), stream_get_contents($output->getStream())); + } + + public function testNonDecoratedOutput() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(false)); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals('', stream_get_contents($output->getStream())); + } + + protected function getOutputStream($decorated = true) + { + return new StreamOutput(fopen('php://memory', 'r+', false), StreamOutput::VERBOSITY_NORMAL, $decorated); + } + + protected $lastMessagesLength; + + protected function generateOutput($expected) + { + $expectedout = $expected; + + if (null !== $this->lastMessagesLength) { + $expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + } + + $this->lastMessagesLength = strlen($expectedout); + + return "\x0D".$expectedout; + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/LegacyTableHelperTest.php b/core/vendor/symfony/console/Tests/Helper/LegacyTableHelperTest.php new file mode 100644 index 0000000..9fdc1fb --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/LegacyTableHelperTest.php @@ -0,0 +1,323 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * @group legacy + */ +class LegacyTableHelperTest extends TestCase +{ + protected $stream; + + protected function setUp() + { + $this->stream = fopen('php://memory', 'r+'); + } + + protected function tearDown() + { + fclose($this->stream); + $this->stream = null; + } + + /** + * @dataProvider renderProvider + */ + public function testRender($headers, $rows, $layout, $expected) + { + $table = new TableHelper(); + $table + ->setHeaders($headers) + ->setRows($rows) + ->setLayout($layout) + ; + $table->render($output = $this->getOutputStream()); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @dataProvider renderProvider + */ + public function testRenderAddRows($headers, $rows, $layout, $expected) + { + $table = new TableHelper(); + $table + ->setHeaders($headers) + ->addRows($rows) + ->setLayout($layout) + ; + $table->render($output = $this->getOutputStream()); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @dataProvider renderProvider + */ + public function testRenderAddRowsOneByOne($headers, $rows, $layout, $expected) + { + $table = new TableHelper(); + $table + ->setHeaders($headers) + ->setLayout($layout) + ; + foreach ($rows as $row) { + $table->addRow($row); + } + $table->render($output = $this->getOutputStream()); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + public function renderProvider() + { + $books = array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + ); + + return array( + array( + array('ISBN', 'Title', 'Author'), + $books, + TableHelper::LAYOUT_DEFAULT, +<<<'TABLE' ++---------------+--------------------------+------------------+ +| ISBN | Title | Author | ++---------------+--------------------------+------------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | +| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | +| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | +| 80-902734-1-6 | And Then There Were None | Agatha Christie | ++---------------+--------------------------+------------------+ + +TABLE + ), + array( + array('ISBN', 'Title', 'Author'), + $books, + TableHelper::LAYOUT_COMPACT, +<<<'TABLE' + ISBN Title Author + 99921-58-10-7 Divine Comedy Dante Alighieri + 9971-5-0210-0 A Tale of Two Cities Charles Dickens + 960-425-059-0 The Lord of the Rings J. R. R. Tolkien + 80-902734-1-6 And Then There Were None Agatha Christie + +TABLE + ), + array( + array('ISBN', 'Title', 'Author'), + $books, + TableHelper::LAYOUT_BORDERLESS, +<<<'TABLE' + =============== ========================== ================== + ISBN Title Author + =============== ========================== ================== + 99921-58-10-7 Divine Comedy Dante Alighieri + 9971-5-0210-0 A Tale of Two Cities Charles Dickens + 960-425-059-0 The Lord of the Rings J. R. R. Tolkien + 80-902734-1-6 And Then There Were None Agatha Christie + =============== ========================== ================== + +TABLE + ), + array( + array('ISBN', 'Title'), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0'), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + ), + TableHelper::LAYOUT_DEFAULT, +<<<'TABLE' ++---------------+--------------------------+------------------+ +| ISBN | Title | | ++---------------+--------------------------+------------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | +| 9971-5-0210-0 | | | +| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | +| 80-902734-1-6 | And Then There Were None | Agatha Christie | ++---------------+--------------------------+------------------+ + +TABLE + ), + array( + array(), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0'), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + ), + TableHelper::LAYOUT_DEFAULT, +<<<'TABLE' ++---------------+--------------------------+------------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | +| 9971-5-0210-0 | | | +| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | +| 80-902734-1-6 | And Then There Were None | Agatha Christie | ++---------------+--------------------------+------------------+ + +TABLE + ), + array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-7', "Divine\nComedy", 'Dante Alighieri'), + array('9971-5-0210-2', "Harry Potter\nand the Chamber of Secrets", "Rowling\nJoanne K."), + array('9971-5-0210-2', "Harry Potter\nand the Chamber of Secrets", "Rowling\nJoanne K."), + array('960-425-059-0', 'The Lord of the Rings', "J. R. R.\nTolkien"), + ), + TableHelper::LAYOUT_DEFAULT, +<<<'TABLE' ++---------------+----------------------------+-----------------+ +| ISBN | Title | Author | ++---------------+----------------------------+-----------------+ +| 99921-58-10-7 | Divine | Dante Alighieri | +| | Comedy | | +| 9971-5-0210-2 | Harry Potter | Rowling | +| | and the Chamber of Secrets | Joanne K. | +| 9971-5-0210-2 | Harry Potter | Rowling | +| | and the Chamber of Secrets | Joanne K. | +| 960-425-059-0 | The Lord of the Rings | J. R. R. | +| | | Tolkien | ++---------------+----------------------------+-----------------+ + +TABLE + ), + array( + array('ISBN', 'Title'), + array(), + TableHelper::LAYOUT_DEFAULT, +<<<'TABLE' ++------+-------+ +| ISBN | Title | ++------+-------+ + +TABLE + ), + array( + array(), + array(), + TableHelper::LAYOUT_DEFAULT, + '', + ), + 'Cell text with tags used for Output styling' => array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + ), + TableHelper::LAYOUT_DEFAULT, +<<<'TABLE' ++---------------+----------------------+-----------------+ +| ISBN | Title | Author | ++---------------+----------------------+-----------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | +| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | ++---------------+----------------------+-----------------+ + +TABLE + ), + 'Cell text with tags not used for Output styling' => array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-700', 'Divine Com', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + ), + TableHelper::LAYOUT_DEFAULT, +<<<'TABLE' ++----------------------------------+----------------------+-----------------+ +| ISBN | Title | Author | ++----------------------------------+----------------------+-----------------+ +| 99921-58-10-700 | Divine Com | Dante Alighieri | +| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | ++----------------------------------+----------------------+-----------------+ + +TABLE + ), + ); + } + + /** + * @requires extension mbstring + */ + public function testRenderMultiByte() + { + $table = new TableHelper(); + $table + ->setHeaders(array('■■')) + ->setRows(array(array(1234))) + ->setLayout(TableHelper::LAYOUT_DEFAULT) + ; + $table->render($output = $this->getOutputStream()); + + $expected = +<<<'TABLE' ++------+ +| ■■ | ++------+ +| 1234 | ++------+ + +TABLE; + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @requires extension mbstring + */ + public function testRenderFullWidthCharacters() + { + $table = new TableHelper(); + $table + ->setHeaders(array('あいうえお')) + ->setRows(array(array(1234567890))) + ->setLayout(TableHelper::LAYOUT_DEFAULT) + ; + $table->render($output = $this->getOutputStream()); + + $expected = + <<<'TABLE' ++------------+ +| あいうえお | ++------------+ +| 1234567890 | ++------------+ + +TABLE; + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + protected function getOutputStream() + { + return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, false); + } + + protected function getOutputContent(StreamOutput $output) + { + rewind($output->getStream()); + + return str_replace(PHP_EOL, "\n", stream_get_contents($output->getStream())); + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/ProcessHelperTest.php b/core/vendor/symfony/console/Tests/Helper/ProcessHelperTest.php new file mode 100644 index 0000000..8069bcc --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/ProcessHelperTest.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\ProcessBuilder; + +class ProcessHelperTest extends TestCase +{ + /** + * @dataProvider provideCommandsAndOutput + */ + public function testVariousProcessRuns($expected, $cmd, $verbosity, $error) + { + $helper = new ProcessHelper(); + $helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); + $output = $this->getOutputStream($verbosity); + $helper->run($output, $cmd, $error); + $this->assertEquals($expected, $this->getOutput($output)); + } + + public function testPassedCallbackIsExecuted() + { + $helper = new ProcessHelper(); + $helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); + $output = $this->getOutputStream(StreamOutput::VERBOSITY_NORMAL); + + $executed = false; + $callback = function () use (&$executed) { $executed = true; }; + + $helper->run($output, 'php -r "echo 42;"', null, $callback); + $this->assertTrue($executed); + } + + public function provideCommandsAndOutput() + { + $successOutputVerbose = <<<'EOT' + RUN php -r "echo 42;" + RES Command ran successfully + +EOT; + $successOutputDebug = <<<'EOT' + RUN php -r "echo 42;" + OUT 42 + RES Command ran successfully + +EOT; + $successOutputDebugWithTags = <<<'EOT' + RUN php -r "echo '42';" + OUT 42 + RES Command ran successfully + +EOT; + $successOutputProcessDebug = <<<'EOT' + RUN 'php' '-r' 'echo 42;' + OUT 42 + RES Command ran successfully + +EOT; + $syntaxErrorOutputVerbose = <<<'EOT' + RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);" + RES 252 Command did not run successfully + +EOT; + $syntaxErrorOutputDebug = <<<'EOT' + RUN php -r "fwrite(STDERR, 'error message');usleep(500000);fwrite(STDOUT, 'out message');exit(252);" + ERR error message + OUT out message + RES 252 Command did not run successfully + +EOT; + + $errorMessage = 'An error occurred'; + $args = new ProcessBuilder(array('php', '-r', 'echo 42;')); + $args = $args->getProcess()->getCommandLine(); + $successOutputProcessDebug = str_replace("'php' '-r' 'echo 42;'", $args, $successOutputProcessDebug); + + return array( + array('', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null), + array($successOutputVerbose, 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERY_VERBOSE, null), + array($successOutputDebug, 'php -r "echo 42;"', StreamOutput::VERBOSITY_DEBUG, null), + array($successOutputDebugWithTags, 'php -r "echo \'42\';"', StreamOutput::VERBOSITY_DEBUG, null), + array('', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null), + array($syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null), + array($syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(500000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null), + array($errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage), + array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage), + array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(500000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage), + array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null), + array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null), + ); + } + + private function getOutputStream($verbosity) + { + return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, false); + } + + private function getOutput(StreamOutput $output) + { + rewind($output->getStream()); + + return stream_get_contents($output->getStream()); + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/ProgressBarTest.php b/core/vendor/symfony/console/Tests/Helper/ProgressBarTest.php new file mode 100644 index 0000000..d1616d1 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/ProgressBarTest.php @@ -0,0 +1,668 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * @group time-sensitive + */ +class ProgressBarTest extends TestCase +{ + public function testMultipleStart() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->advance(); + $bar->start(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]'. + $this->generateOutput(' 1 [->--------------------------]'). + $this->generateOutput(' 0 [>---------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testAdvance() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]'. + $this->generateOutput(' 1 [->--------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testAdvanceWithStep() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->advance(5); + + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]'. + $this->generateOutput(' 5 [----->----------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testAdvanceMultipleTimes() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->advance(3); + $bar->advance(2); + + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]'. + $this->generateOutput(' 3 [--->------------------------]'). + $this->generateOutput(' 5 [----->----------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testAdvanceOverMax() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 10); + $bar->setProgress(9); + $bar->advance(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 9/10 [=========================>--] 90%'. + $this->generateOutput(' 10/10 [============================] 100%'). + $this->generateOutput(' 11/11 [============================] 100%'), + stream_get_contents($output->getStream()) + ); + } + + public function testFormat() + { + $expected = + ' 0/10 [>---------------------------] 0%'. + $this->generateOutput(' 10/10 [============================] 100%'). + $this->generateOutput(' 10/10 [============================] 100%') + ; + + // max in construct, no format + $bar = new ProgressBar($output = $this->getOutputStream(), 10); + $bar->start(); + $bar->advance(10); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals($expected, stream_get_contents($output->getStream())); + + // max in start, no format + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(10); + $bar->advance(10); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals($expected, stream_get_contents($output->getStream())); + + // max in construct, explicit format before + $bar = new ProgressBar($output = $this->getOutputStream(), 10); + $bar->setFormat('normal'); + $bar->start(); + $bar->advance(10); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals($expected, stream_get_contents($output->getStream())); + + // max in start, explicit format before + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat('normal'); + $bar->start(10); + $bar->advance(10); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals($expected, stream_get_contents($output->getStream())); + } + + public function testCustomizations() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 10); + $bar->setBarWidth(10); + $bar->setBarCharacter('_'); + $bar->setEmptyBarCharacter(' '); + $bar->setProgressCharacter('/'); + $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%'); + $bar->start(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/10 [/ ] 0%'. + $this->generateOutput(' 1/10 [_/ ] 10%'), + stream_get_contents($output->getStream()) + ); + } + + public function testDisplayWithoutStart() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->display(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/50 [>---------------------------] 0%', + stream_get_contents($output->getStream()) + ); + } + + public function testDisplayWithQuietVerbosity() + { + $bar = new ProgressBar($output = $this->getOutputStream(true, StreamOutput::VERBOSITY_QUIET), 50); + $bar->display(); + + rewind($output->getStream()); + $this->assertEquals( + '', + stream_get_contents($output->getStream()) + ); + } + + public function testFinishWithoutStart() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ' 50/50 [============================] 100%', + stream_get_contents($output->getStream()) + ); + } + + public function testPercent() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->start(); + $bar->display(); + $bar->advance(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/50 [>---------------------------] 0%'. + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 1/50 [>---------------------------] 2%'). + $this->generateOutput(' 2/50 [=>--------------------------] 4%'), + stream_get_contents($output->getStream()) + ); + } + + public function testOverwriteWithShorterLine() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%'); + $bar->start(); + $bar->display(); + $bar->advance(); + + // set shorter format + $bar->setFormat(' %current%/%max% [%bar%]'); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/50 [>---------------------------] 0%'. + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 1/50 [>---------------------------] 2%'). + $this->generateOutput(' 2/50 [=>--------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testStartWithMax() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat('%current%/%max% [%bar%]'); + $bar->start(50); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/50 [>---------------------------]'. + $this->generateOutput(' 1/50 [>---------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testSetCurrentProgress() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->start(); + $bar->display(); + $bar->advance(); + $bar->setProgress(15); + $bar->setProgress(25); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/50 [>---------------------------] 0%'. + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 1/50 [>---------------------------] 2%'). + $this->generateOutput(' 15/50 [========>-------------------] 30%'). + $this->generateOutput(' 25/50 [==============>-------------] 50%'), + stream_get_contents($output->getStream()) + ); + } + + public function testSetCurrentBeforeStarting() + { + $bar = new ProgressBar($this->getOutputStream()); + $bar->setProgress(15); + $this->assertNotNull($bar->getStartTime()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You can't regress the progress bar + */ + public function testRegressProgress() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->start(); + $bar->setProgress(15); + $bar->setProgress(10); + } + + public function testRedrawFrequency() + { + $bar = $this->getMockBuilder('Symfony\Component\Console\Helper\ProgressBar')->setMethods(array('display'))->setConstructorArgs(array($this->getOutputStream(), 6))->getMock(); + $bar->expects($this->exactly(4))->method('display'); + + $bar->setRedrawFrequency(2); + $bar->start(); + $bar->setProgress(1); + $bar->advance(2); + $bar->advance(2); + $bar->advance(1); + } + + public function testRedrawFrequencyIsAtLeastOneIfZeroGiven() + { + $bar = $this->getMockBuilder('Symfony\Component\Console\Helper\ProgressBar')->setMethods(array('display'))->setConstructorArgs(array($this->getOutputStream()))->getMock(); + + $bar->expects($this->exactly(2))->method('display'); + $bar->setRedrawFrequency(0); + $bar->start(); + $bar->advance(); + } + + public function testRedrawFrequencyIsAtLeastOneIfSmallerOneGiven() + { + $bar = $this->getMockBuilder('Symfony\Component\Console\Helper\ProgressBar')->setMethods(array('display'))->setConstructorArgs(array($this->getOutputStream()))->getMock(); + + $bar->expects($this->exactly(2))->method('display'); + $bar->setRedrawFrequency(0.9); + $bar->start(); + $bar->advance(); + } + + /** + * @requires extension mbstring + */ + public function testMultiByteSupport() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->setBarCharacter('■'); + $bar->advance(3); + + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]'. + $this->generateOutput(' 3 [■■■>------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testClear() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->start(); + $bar->setProgress(25); + $bar->clear(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/50 [>---------------------------] 0%'. + $this->generateOutput(' 25/50 [==============>-------------] 50%'). + $this->generateOutput(''), + stream_get_contents($output->getStream()) + ); + } + + public function testPercentNotHundredBeforeComplete() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 200); + $bar->start(); + $bar->display(); + $bar->advance(199); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/200 [>---------------------------] 0%'. + $this->generateOutput(' 0/200 [>---------------------------] 0%'). + $this->generateOutput(' 199/200 [===========================>] 99%'). + $this->generateOutput(' 200/200 [============================] 100%'), + stream_get_contents($output->getStream()) + ); + } + + public function testNonDecoratedOutput() + { + $bar = new ProgressBar($output = $this->getOutputStream(false), 200); + $bar->start(); + + for ($i = 0; $i < 200; ++$i) { + $bar->advance(); + } + + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/200 [>---------------------------] 0%'.PHP_EOL. + ' 20/200 [==>-------------------------] 10%'.PHP_EOL. + ' 40/200 [=====>----------------------] 20%'.PHP_EOL. + ' 60/200 [========>-------------------] 30%'.PHP_EOL. + ' 80/200 [===========>----------------] 40%'.PHP_EOL. + ' 100/200 [==============>-------------] 50%'.PHP_EOL. + ' 120/200 [================>-----------] 60%'.PHP_EOL. + ' 140/200 [===================>--------] 70%'.PHP_EOL. + ' 160/200 [======================>-----] 80%'.PHP_EOL. + ' 180/200 [=========================>--] 90%'.PHP_EOL. + ' 200/200 [============================] 100%', + stream_get_contents($output->getStream()) + ); + } + + public function testNonDecoratedOutputWithClear() + { + $bar = new ProgressBar($output = $this->getOutputStream(false), 50); + $bar->start(); + $bar->setProgress(25); + $bar->clear(); + $bar->setProgress(50); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/50 [>---------------------------] 0%'.PHP_EOL. + ' 25/50 [==============>-------------] 50%'.PHP_EOL. + ' 50/50 [============================] 100%', + stream_get_contents($output->getStream()) + ); + } + + public function testNonDecoratedOutputWithoutMax() + { + $bar = new ProgressBar($output = $this->getOutputStream(false)); + $bar->start(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]'.PHP_EOL. + ' 1 [->--------------------------]', + stream_get_contents($output->getStream()) + ); + } + + public function testParallelBars() + { + $output = $this->getOutputStream(); + $bar1 = new ProgressBar($output, 2); + $bar2 = new ProgressBar($output, 3); + $bar2->setProgressCharacter('#'); + $bar3 = new ProgressBar($output); + + $bar1->start(); + $output->write("\n"); + $bar2->start(); + $output->write("\n"); + $bar3->start(); + + for ($i = 1; $i <= 3; ++$i) { + // up two lines + $output->write("\033[2A"); + if ($i <= 2) { + $bar1->advance(); + } + $output->write("\n"); + $bar2->advance(); + $output->write("\n"); + $bar3->advance(); + } + $output->write("\033[2A"); + $output->write("\n"); + $output->write("\n"); + $bar3->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/2 [>---------------------------] 0%'."\n". + ' 0/3 [#---------------------------] 0%'."\n". + rtrim(' 0 [>---------------------------]'). + + "\033[2A". + $this->generateOutput(' 1/2 [==============>-------------] 50%')."\n". + $this->generateOutput(' 1/3 [=========#------------------] 33%')."\n". + rtrim($this->generateOutput(' 1 [->--------------------------]')). + + "\033[2A". + $this->generateOutput(' 2/2 [============================] 100%')."\n". + $this->generateOutput(' 2/3 [==================#---------] 66%')."\n". + rtrim($this->generateOutput(' 2 [-->-------------------------]')). + + "\033[2A". + "\n". + $this->generateOutput(' 3/3 [============================] 100%')."\n". + rtrim($this->generateOutput(' 3 [--->------------------------]')). + + "\033[2A". + "\n". + "\n". + rtrim($this->generateOutput(' 3 [============================]')), + stream_get_contents($output->getStream()) + ); + } + + public function testWithoutMax() + { + $output = $this->getOutputStream(); + + $bar = new ProgressBar($output); + $bar->start(); + $bar->advance(); + $bar->advance(); + $bar->advance(); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + rtrim(' 0 [>---------------------------]'). + rtrim($this->generateOutput(' 1 [->--------------------------]')). + rtrim($this->generateOutput(' 2 [-->-------------------------]')). + rtrim($this->generateOutput(' 3 [--->------------------------]')). + rtrim($this->generateOutput(' 3 [============================]')), + stream_get_contents($output->getStream()) + ); + } + + public function testAddingPlaceholderFormatter() + { + ProgressBar::setPlaceholderFormatterDefinition('remaining_steps', function (ProgressBar $bar) { + return $bar->getMaxSteps() - $bar->getProgress(); + }); + $bar = new ProgressBar($output = $this->getOutputStream(), 3); + $bar->setFormat(' %remaining_steps% [%bar%]'); + + $bar->start(); + $bar->advance(); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ' 3 [>---------------------------]'. + $this->generateOutput(' 2 [=========>------------------]'). + $this->generateOutput(' 0 [============================]'), + stream_get_contents($output->getStream()) + ); + } + + public function testMultilineFormat() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 3); + $bar->setFormat("%bar%\nfoobar"); + + $bar->start(); + $bar->advance(); + $bar->clear(); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ">---------------------------\nfoobar". + $this->generateOutput("=========>------------------\nfoobar"). + "\x0D\x1B[2K\x1B[1A\x1B[2K". + $this->generateOutput("============================\nfoobar"), + stream_get_contents($output->getStream()) + ); + } + + /** + * @requires extension mbstring + */ + public function testAnsiColorsAndEmojis() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 15); + ProgressBar::setPlaceholderFormatterDefinition('memory', function (ProgressBar $bar) { + static $i = 0; + $mem = 100000 * $i; + $colors = $i++ ? '41;37' : '44;37'; + + return "\033[".$colors.'m '.Helper::formatMemory($mem)." \033[0m"; + }); + $bar->setFormat(" \033[44;37m %title:-37s% \033[0m\n %current%/%max% %bar% %percent:3s%%\n 🏁 %remaining:-10s% %memory:37s%"); + $bar->setBarCharacter($done = "\033[32m●\033[0m"); + $bar->setEmptyBarCharacter($empty = "\033[31m●\033[0m"); + $bar->setProgressCharacter($progress = "\033[32m➤ \033[0m"); + + $bar->setMessage('Starting the demo... fingers crossed', 'title'); + $bar->start(); + $bar->setMessage('Looks good to me...', 'title'); + $bar->advance(4); + $bar->setMessage('Thanks, bye', 'title'); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + " \033[44;37m Starting the demo... fingers crossed \033[0m\n". + ' 0/15 '.$progress.str_repeat($empty, 26)." 0%\n". + " \xf0\x9f\x8f\x81 < 1 sec \033[44;37m 0 B \033[0m" + . + $this->generateOutput( + " \033[44;37m Looks good to me... \033[0m\n". + ' 4/15 '.str_repeat($done, 7).$progress.str_repeat($empty, 19)." 26%\n". + " \xf0\x9f\x8f\x81 < 1 sec \033[41;37m 97 KiB \033[0m" + ). + $this->generateOutput( + " \033[44;37m Thanks, bye \033[0m\n". + ' 15/15 '.str_repeat($done, 28)." 100%\n". + " \xf0\x9f\x8f\x81 < 1 sec \033[41;37m 195 KiB \033[0m" + ), + stream_get_contents($output->getStream()) + ); + } + + public function testSetFormat() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat('normal'); + $bar->start(); + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]', + stream_get_contents($output->getStream()) + ); + + $bar = new ProgressBar($output = $this->getOutputStream(), 10); + $bar->setFormat('normal'); + $bar->start(); + rewind($output->getStream()); + $this->assertEquals( + ' 0/10 [>---------------------------] 0%', + stream_get_contents($output->getStream()) + ); + } + + /** + * @dataProvider provideFormat + */ + public function testFormatsWithoutMax($format) + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat($format); + $bar->start(); + + rewind($output->getStream()); + $this->assertNotEmpty(stream_get_contents($output->getStream())); + } + + /** + * Provides each defined format. + * + * @return array + */ + public function provideFormat() + { + return array( + array('normal'), + array('verbose'), + array('very_verbose'), + array('debug'), + ); + } + + protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL) + { + return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated); + } + + protected function generateOutput($expected) + { + $count = substr_count($expected, "\n"); + + return "\x0D\x1B[2K".($count ? str_repeat("\x1B[1A\x1B[2K", $count) : '').$expected; + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php b/core/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php new file mode 100644 index 0000000..9f837d0 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php @@ -0,0 +1,624 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * @group tty + */ +class QuestionHelperTest extends TestCase +{ + public function testAskChoice() + { + $questionHelper = new QuestionHelper(); + + $helperSet = new HelperSet(array(new FormatterHelper())); + $questionHelper->setHelperSet($helperSet); + + $heroes = array('Superman', 'Batman', 'Spiderman'); + + $questionHelper->setInputStream($this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n")); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '2'); + $question->setMaxAttempts(1); + // first answer is an empty answer, we're supposed to receive the default value + $this->assertEquals('Spiderman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); + $question->setMaxAttempts(1); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); + $question->setErrorMessage('Input "%s" is not a superhero!'); + $question->setMaxAttempts(2); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + + rewind($output->getStream()); + $stream = stream_get_contents($output->getStream()); + $this->assertContains('Input "Fabien" is not a superhero!', $stream); + + try { + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1'); + $question->setMaxAttempts(1); + $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals('Value "Fabien" is invalid', $e->getMessage()); + } + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0,1'); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, ' 0 , 1 '); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, 0); + // We are supposed to get the default value since we are not in interactive mode + $this->assertEquals('Superman', $questionHelper->ask($this->createInputInterfaceMock(true), $this->createOutputInterface(), $question)); + } + + public function testAsk() + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream("\n8AM\n")); + + $question = new Question('What time is it?', '2PM'); + $this->assertEquals('2PM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new Question('What time is it?', '2PM'); + $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + + rewind($output->getStream()); + $this->assertEquals('What time is it?', stream_get_contents($output->getStream())); + } + + public function testAskWithAutocomplete() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // Acm + // AcsTest + // + // + // Test + // + // S + // F00oo + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($inputStream); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new Question('Please select a bundle', 'FrameworkBundle'); + $question->setAutocompleterValues(array('AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle')); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + public function testAskWithAutocompleteWithNonSequentialKeys() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // + $inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n"); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($inputStream); + $dialog->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $question = new ChoiceQuestion('Please select a bundle', array(1 => 'AcmeDemoBundle', 4 => 'AsseticBundle')); + $question->setMaxAttempts(1); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + public function testAskWithAutocompleteWithExactMatch() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + $inputStream = $this->getInputStream("b\n"); + + $possibleChoices = array( + 'a' => 'berlin', + 'b' => 'copenhagen', + 'c' => 'amsterdam', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($inputStream); + $dialog->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $question = new ChoiceQuestion('Please select a city', $possibleChoices); + $question->setMaxAttempts(1); + + $this->assertSame('b', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + public function testAutocompleteWithTrailingBackslash() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + $inputStream = $this->getInputStream('E'); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($inputStream); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new Question(''); + $expectedCompletion = 'ExampleNamespace\\'; + $question->setAutocompleterValues(array($expectedCompletion)); + + $output = $this->createOutputInterface(); + $dialog->ask($this->createInputInterfaceMock(), $output, $question); + + $outputStream = $output->getStream(); + rewind($outputStream); + $actualOutput = stream_get_contents($outputStream); + + // Shell control (esc) sequences are not so important: we only care that + // tag is interpreted correctly and replaced + $irrelevantEscSequences = array( + "\0337" => '', // Save cursor position + "\0338" => '', // Restore cursor position + "\033[K" => '', // Clear line from cursor till the end + ); + + $importantActualOutput = strtr($actualOutput, $irrelevantEscSequences); + + // Remove colors (e.g. "\033[30m", "\033[31;41m") + $importantActualOutput = preg_replace('/\033\[\d+(;\d+)?m/', '', $importantActualOutput); + + $this->assertEquals($expectedCompletion, $importantActualOutput); + } + + public function testAskHiddenResponse() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is not supported on Windows'); + } + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream("8AM\n")); + + $question = new Question('What time is it?'); + $question->setHidden(true); + + $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + /** + * @dataProvider getAskConfirmationData + */ + public function testAskConfirmation($question, $expected, $default = true) + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream($question."\n")); + $question = new ConfirmationQuestion('Do you like French fries?', $default); + $this->assertEquals($expected, $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel')); + } + + public function getAskConfirmationData() + { + return array( + array('', true), + array('', false, false), + array('y', true), + array('yes', true), + array('n', false), + array('no', false), + ); + } + + public function testAskConfirmationWithCustomTrueAnswer() + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream("j\ny\n")); + $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i'); + $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i'); + $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + public function testAskAndValidate() + { + $dialog = new QuestionHelper(); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $error = 'This is not a color!'; + $validator = function ($color) use ($error) { + if (!in_array($color, array('white', 'black'))) { + throw new \InvalidArgumentException($error); + } + + return $color; + }; + + $question = new Question('What color was the white horse of Henry IV?', 'white'); + $question->setValidator($validator); + $question->setMaxAttempts(2); + + $dialog->setInputStream($this->getInputStream("\nblack\n")); + $this->assertEquals('white', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('black', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $dialog->setInputStream($this->getInputStream("green\nyellow\norange\n")); + try { + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals($error, $e->getMessage()); + } + } + + /** + * @dataProvider simpleAnswerProvider + */ + public function testSelectChoiceFromSimpleChoices($providedAnswer, $expectedValue) + { + $possibleChoices = array( + 'My environment 1', + 'My environment 2', + 'My environment 3', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + public function simpleAnswerProvider() + { + return array( + array(0, 'My environment 1'), + array(1, 'My environment 2'), + array(2, 'My environment 3'), + array('My environment 1', 'My environment 1'), + array('My environment 2', 'My environment 2'), + array('My environment 3', 'My environment 3'), + ); + } + + /** + * @dataProvider specialCharacterInMultipleChoice + */ + public function testSpecialCharacterChoiceFromMultipleChoiceList($providedAnswer, $expectedValue) + { + $possibleChoices = array( + '.', + 'src', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the directory', $possibleChoices); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + public function specialCharacterInMultipleChoice() + { + return array( + array('.', array('.')), + array('., src', array('.', 'src')), + ); + } + + /** + * @dataProvider mixedKeysChoiceListAnswerProvider + */ + public function testChoiceFromChoicelistWithMixedKeys($providedAnswer, $expectedValue) + { + $possibleChoices = array( + '0' => 'No environment', + '1' => 'My environment 1', + 'env_2' => 'My environment 2', + 3 => 'My environment 3', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + public function mixedKeysChoiceListAnswerProvider() + { + return array( + array('0', '0'), + array('No environment', '0'), + array('1', '1'), + array('env_2', 'env_2'), + array(3, '3'), + array('My environment 1', '1'), + ); + } + + /** + * @dataProvider answerProvider + */ + public function testSelectChoiceFromChoiceList($providedAnswer, $expectedValue) + { + $possibleChoices = array( + 'env_1' => 'My environment 1', + 'env_2' => 'My environment', + 'env_3' => 'My environment', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The provided answer is ambiguous. Value should be one of env_2 or env_3. + */ + public function testAmbiguousChoiceFromChoicelist() + { + $possibleChoices = array( + 'env_1' => 'My first environment', + 'env_2' => 'My environment', + 'env_3' => 'My environment', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream("My environment\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + } + + public function answerProvider() + { + return array( + array('env_1', 'env_1'), + array('env_2', 'env_2'), + array('env_3', 'env_3'), + array('My environment 1', 'env_1'), + ); + } + + public function testNoInteraction() + { + $dialog = new QuestionHelper(); + $question = new Question('Do you have a job?', 'not yet'); + $this->assertEquals('not yet', $dialog->ask($this->createInputInterfaceMock(false), $this->createOutputInterface(), $question)); + } + + /** + * @requires function mb_strwidth + */ + public function testChoiceOutputFormattingQuestionForUtf8Keys() + { + $question = 'Lorem ipsum?'; + $possibleChoices = array( + 'foo' => 'foo', + 'żółw' => 'bar', + 'łabądź' => 'baz', + ); + $outputShown = array( + $question, + ' [foo ] foo', + ' [żółw ] bar', + ' [łabądź] baz', + ); + $output = $this->getMockBuilder('\Symfony\Component\Console\Output\OutputInterface')->getMock(); + $output->method('getFormatter')->willReturn(new OutputFormatter()); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream("\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $output->expects($this->once())->method('writeln')->with($this->equalTo($outputShown)); + + $question = new ChoiceQuestion($question, $possibleChoices, 'foo'); + $dialog->ask($this->createInputInterfaceMock(), $output, $question); + } + + /** + * @expectedException \Symfony\Component\Console\Exception\RuntimeException + * @expectedExceptionMessage Aborted + */ + public function testAskThrowsExceptionOnMissingInput() + { + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream('')); + + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), new Question('What\'s your name?')); + } + + /** + * @expectedException \Symfony\Component\Console\Exception\RuntimeException + * @expectedExceptionMessage Aborted + */ + public function testAskThrowsExceptionOnMissingInputWithValidator() + { + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream('')); + + $question = new Question('What\'s your name?'); + $question->setValidator(function () { + if (!$value) { + throw new \Exception('A value is required.'); + } + }); + + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Choice question must have at least 1 choice available. + */ + public function testEmptyChoices() + { + new ChoiceQuestion('Question', array(), 'irrelevant'); + } + + public function testTraversableAutocomplete() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // Acm + // AcsTest + // + // + // Test + // + // S + // F00oo + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($inputStream); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new Question('Please select a bundle', 'FrameworkBundle'); + $question->setAutocompleterValues(new AutocompleteValues(array('irrelevant' => 'AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'))); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + protected function getInputStream($input) + { + $stream = fopen('php://memory', 'r+', false); + fwrite($stream, $input); + rewind($stream); + + return $stream; + } + + protected function createOutputInterface() + { + return new StreamOutput(fopen('php://memory', 'r+', false)); + } + + protected function createInputInterfaceMock($interactive = true) + { + $mock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $mock->expects($this->any()) + ->method('isInteractive') + ->will($this->returnValue($interactive)); + + return $mock; + } + + private function hasSttyAvailable() + { + exec('stty 2>&1', $output, $exitcode); + + return 0 === $exitcode; + } +} + +class AutocompleteValues implements \IteratorAggregate +{ + private $values; + + public function __construct(array $values) + { + $this->values = $values; + } + + public function getIterator() + { + return new \ArrayIterator($this->values); + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/SymfonyQuestionHelperTest.php b/core/vendor/symfony/console/Tests/Helper/SymfonyQuestionHelperTest.php new file mode 100644 index 0000000..1a2d1b8 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/SymfonyQuestionHelperTest.php @@ -0,0 +1,159 @@ +setHelperSet($helperSet); + + $heroes = array('Superman', 'Batman', 'Spiderman'); + + $questionHelper->setInputStream($this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n")); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '2'); + $question->setMaxAttempts(1); + // first answer is an empty answer, we're supposed to receive the default value + $this->assertEquals('Spiderman', $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + $this->assertOutputContains('What is your favorite superhero? [Spiderman]', $output); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); + $question->setMaxAttempts(1); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); + $question->setErrorMessage('Input "%s" is not a superhero!'); + $question->setMaxAttempts(2); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + $this->assertOutputContains('Input "Fabien" is not a superhero!', $output); + + try { + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1'); + $question->setMaxAttempts(1); + $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals('Value "Fabien" is invalid', $e->getMessage()); + } + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0,1'); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + $this->assertOutputContains('What is your favorite superhero? [Superman, Batman]', $output); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, ' 0 , 1 '); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + $this->assertOutputContains('What is your favorite superhero? [Superman, Batman]', $output); + } + + public function testAskReturnsNullIfValidatorAllowsIt() + { + $questionHelper = new SymfonyQuestionHelper(); + $questionHelper->setInputStream($this->getInputStream("\n")); + $question = new Question('What is your favorite superhero?'); + $question->setValidator(function ($value) { return $value; }); + $this->assertNull($questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + public function testAskEscapeDefaultValue() + { + $helper = new SymfonyQuestionHelper(); + $helper->setInputStream($this->getInputStream('\\')); + $helper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), new Question('Can I have a backslash?', '\\')); + + $this->assertOutputContains('Can I have a backslash? [\]', $output); + } + + public function testAskEscapeAndFormatLabel() + { + $helper = new SymfonyQuestionHelper(); + $helper->setInputStream($this->getInputStream('Foo\\Bar')); + $helper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), new Question('Do you want to use Foo\\Bar or Foo\\Baz\\?', 'Foo\\Baz')); + + $this->assertOutputContains('Do you want to use Foo\\Bar or Foo\\Baz\\? [Foo\\Baz]:', $output); + } + + public function testLabelTrailingBackslash() + { + $helper = new SymfonyQuestionHelper(); + $helper->setInputStream($this->getInputStream('sure')); + $helper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), new Question('Question with a trailing \\')); + + $this->assertOutputContains('Question with a trailing \\', $output); + } + + /** + * @expectedException \Symfony\Component\Console\Exception\RuntimeException + * @expectedExceptionMessage Aborted + */ + public function testAskThrowsExceptionOnMissingInput() + { + $dialog = new SymfonyQuestionHelper(); + + $dialog->setInputStream($this->getInputStream('')); + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), new Question('What\'s your name?')); + } + + protected function getInputStream($input) + { + $stream = fopen('php://memory', 'r+', false); + fwrite($stream, $input); + rewind($stream); + + return $stream; + } + + protected function createOutputInterface() + { + $output = new StreamOutput(fopen('php://memory', 'r+', false)); + $output->setDecorated(false); + + return $output; + } + + protected function createInputInterfaceMock($interactive = true) + { + $mock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $mock->expects($this->any()) + ->method('isInteractive') + ->will($this->returnValue($interactive)); + + return $mock; + } + + private function assertOutputContains($expected, StreamOutput $output) + { + rewind($output->getStream()); + $stream = stream_get_contents($output->getStream()); + $this->assertContains($expected, $stream); + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/TableStyleTest.php b/core/vendor/symfony/console/Tests/Helper/TableStyleTest.php new file mode 100644 index 0000000..13e918b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/TableStyleTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\TableStyle; + +class TableStyleTest extends TestCase +{ + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH). + */ + public function testSetPadTypeWithInvalidType() + { + $style = new TableStyle(); + $style->setPadType('TEST'); + } +} diff --git a/core/vendor/symfony/console/Tests/Helper/TableTest.php b/core/vendor/symfony/console/Tests/Helper/TableTest.php new file mode 100644 index 0000000..0e98432 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Helper/TableTest.php @@ -0,0 +1,705 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableStyle; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Output\StreamOutput; + +class TableTest extends TestCase +{ + protected $stream; + + protected function setUp() + { + $this->stream = fopen('php://memory', 'r+'); + } + + protected function tearDown() + { + fclose($this->stream); + $this->stream = null; + } + + /** + * @dataProvider renderProvider + */ + public function testRender($headers, $rows, $style, $expected, $decorated = false) + { + $table = new Table($output = $this->getOutputStream($decorated)); + $table + ->setHeaders($headers) + ->setRows($rows) + ->setStyle($style) + ; + $table->render(); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @dataProvider renderProvider + */ + public function testRenderAddRows($headers, $rows, $style, $expected, $decorated = false) + { + $table = new Table($output = $this->getOutputStream($decorated)); + $table + ->setHeaders($headers) + ->addRows($rows) + ->setStyle($style) + ; + $table->render(); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @dataProvider renderProvider + */ + public function testRenderAddRowsOneByOne($headers, $rows, $style, $expected, $decorated = false) + { + $table = new Table($output = $this->getOutputStream($decorated)); + $table + ->setHeaders($headers) + ->setStyle($style) + ; + foreach ($rows as $row) { + $table->addRow($row); + } + $table->render(); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + public function renderProvider() + { + $books = array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + ); + + return array( + array( + array('ISBN', 'Title', 'Author'), + $books, + 'default', +<<<'TABLE' ++---------------+--------------------------+------------------+ +| ISBN | Title | Author | ++---------------+--------------------------+------------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | +| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | +| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | +| 80-902734-1-6 | And Then There Were None | Agatha Christie | ++---------------+--------------------------+------------------+ + +TABLE + ), + array( + array('ISBN', 'Title', 'Author'), + $books, + 'compact', +<<<'TABLE' + ISBN Title Author + 99921-58-10-7 Divine Comedy Dante Alighieri + 9971-5-0210-0 A Tale of Two Cities Charles Dickens + 960-425-059-0 The Lord of the Rings J. R. R. Tolkien + 80-902734-1-6 And Then There Were None Agatha Christie + +TABLE + ), + array( + array('ISBN', 'Title', 'Author'), + $books, + 'borderless', +<<<'TABLE' + =============== ========================== ================== + ISBN Title Author + =============== ========================== ================== + 99921-58-10-7 Divine Comedy Dante Alighieri + 9971-5-0210-0 A Tale of Two Cities Charles Dickens + 960-425-059-0 The Lord of the Rings J. R. R. Tolkien + 80-902734-1-6 And Then There Were None Agatha Christie + =============== ========================== ================== + +TABLE + ), + array( + array('ISBN', 'Title'), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0'), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + ), + 'default', +<<<'TABLE' ++---------------+--------------------------+------------------+ +| ISBN | Title | | ++---------------+--------------------------+------------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | +| 9971-5-0210-0 | | | +| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | +| 80-902734-1-6 | And Then There Were None | Agatha Christie | ++---------------+--------------------------+------------------+ + +TABLE + ), + array( + array(), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0'), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + ), + 'default', +<<<'TABLE' ++---------------+--------------------------+------------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | +| 9971-5-0210-0 | | | +| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | +| 80-902734-1-6 | And Then There Were None | Agatha Christie | ++---------------+--------------------------+------------------+ + +TABLE + ), + array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-7', "Divine\nComedy", 'Dante Alighieri'), + array('9971-5-0210-2', "Harry Potter\nand the Chamber of Secrets", "Rowling\nJoanne K."), + array('9971-5-0210-2', "Harry Potter\nand the Chamber of Secrets", "Rowling\nJoanne K."), + array('960-425-059-0', 'The Lord of the Rings', "J. R. R.\nTolkien"), + ), + 'default', +<<<'TABLE' ++---------------+----------------------------+-----------------+ +| ISBN | Title | Author | ++---------------+----------------------------+-----------------+ +| 99921-58-10-7 | Divine | Dante Alighieri | +| | Comedy | | +| 9971-5-0210-2 | Harry Potter | Rowling | +| | and the Chamber of Secrets | Joanne K. | +| 9971-5-0210-2 | Harry Potter | Rowling | +| | and the Chamber of Secrets | Joanne K. | +| 960-425-059-0 | The Lord of the Rings | J. R. R. | +| | | Tolkien | ++---------------+----------------------------+-----------------+ + +TABLE + ), + array( + array('ISBN', 'Title'), + array(), + 'default', +<<<'TABLE' ++------+-------+ +| ISBN | Title | ++------+-------+ + +TABLE + ), + array( + array(), + array(), + 'default', + '', + ), + 'Cell text with tags used for Output styling' => array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + ), + 'default', +<<<'TABLE' ++---------------+----------------------+-----------------+ +| ISBN | Title | Author | ++---------------+----------------------+-----------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | +| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | ++---------------+----------------------+-----------------+ + +TABLE + ), + 'Cell text with tags not used for Output styling' => array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-700', 'Divine Com', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + ), + 'default', +<<<'TABLE' ++----------------------------------+----------------------+-----------------+ +| ISBN | Title | Author | ++----------------------------------+----------------------+-----------------+ +| 99921-58-10-700 | Divine Com | Dante Alighieri | +| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | ++----------------------------------+----------------------+-----------------+ + +TABLE + ), + 'Cell with colspan' => array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + new TableSeparator(), + array(new TableCell('Divine Comedy(Dante Alighieri)', array('colspan' => 3))), + new TableSeparator(), + array( + new TableCell('Arduino: A Quick-Start Guide', array('colspan' => 2)), + 'Mark Schmidt', + ), + new TableSeparator(), + array( + '9971-5-0210-0', + new TableCell("A Tale of \nTwo Cities", array('colspan' => 2)), + ), + new TableSeparator(), + array( + new TableCell('Cupiditate dicta atque porro, tempora exercitationem modi animi nulla nemo vel nihil!', array('colspan' => 3)), + ), + ), + 'default', +<<<'TABLE' ++-------------------------------+-------------------------------+-----------------------------+ +| ISBN | Title | Author | ++-------------------------------+-------------------------------+-----------------------------+ +| 99921-58-10-7 | Divine Comedy | Dante Alighieri | ++-------------------------------+-------------------------------+-----------------------------+ +| Divine Comedy(Dante Alighieri) | ++-------------------------------+-------------------------------+-----------------------------+ +| Arduino: A Quick-Start Guide | Mark Schmidt | ++-------------------------------+-------------------------------+-----------------------------+ +| 9971-5-0210-0 | A Tale of | +| | Two Cities | ++-------------------------------+-------------------------------+-----------------------------+ +| Cupiditate dicta atque porro, tempora exercitationem modi animi nulla nemo vel nihil! | ++-------------------------------+-------------------------------+-----------------------------+ + +TABLE + ), + 'Cell with rowspan' => array( + array('ISBN', 'Title', 'Author'), + array( + array( + new TableCell('9971-5-0210-0', array('rowspan' => 3)), + new TableCell('Divine Comedy', array('rowspan' => 2)), + 'Dante Alighieri', + ), + array(), + array("The Lord of \nthe Rings", "J. R. \nR. Tolkien"), + new TableSeparator(), + array('80-902734-1-6', new TableCell("And Then \nThere \nWere None", array('rowspan' => 3)), 'Agatha Christie'), + array('80-902734-1-7', 'Test'), + ), + 'default', +<<<'TABLE' ++---------------+---------------+-----------------+ +| ISBN | Title | Author | ++---------------+---------------+-----------------+ +| 9971-5-0210-0 | Divine Comedy | Dante Alighieri | +| | | | +| | The Lord of | J. R. | +| | the Rings | R. Tolkien | ++---------------+---------------+-----------------+ +| 80-902734-1-6 | And Then | Agatha Christie | +| 80-902734-1-7 | There | Test | +| | Were None | | ++---------------+---------------+-----------------+ + +TABLE + ), + 'Cell with rowspan and colspan' => array( + array('ISBN', 'Title', 'Author'), + array( + array( + new TableCell('9971-5-0210-0', array('rowspan' => 2, 'colspan' => 2)), + 'Dante Alighieri', + ), + array('Charles Dickens'), + new TableSeparator(), + array( + 'Dante Alighieri', + new TableCell('9971-5-0210-0', array('rowspan' => 3, 'colspan' => 2)), + ), + array('J. R. R. Tolkien'), + array('J. R. R'), + ), + 'default', +<<<'TABLE' ++------------------+---------+-----------------+ +| ISBN | Title | Author | ++------------------+---------+-----------------+ +| 9971-5-0210-0 | Dante Alighieri | +| | Charles Dickens | ++------------------+---------+-----------------+ +| Dante Alighieri | 9971-5-0210-0 | +| J. R. R. Tolkien | | +| J. R. R | | ++------------------+---------+-----------------+ + +TABLE + ), + 'Cell with rowspan and colspan contains new line break' => array( + array('ISBN', 'Title', 'Author'), + array( + array( + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + 'Dante Alighieri', + ), + array('Charles Dickens'), + new TableSeparator(), + array( + 'Dante Alighieri', + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + ), + array('Charles Dickens'), + new TableSeparator(), + array( + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + new TableCell("Dante \nAlighieri", array('rowspan' => 2, 'colspan' => 1)), + ), + ), + 'default', +<<<'TABLE' ++-----------------+-------+-----------------+ +| ISBN | Title | Author | ++-----------------+-------+-----------------+ +| 9971 | Dante Alighieri | +| -5- | Charles Dickens | +| 021 | | +| 0-0 | | ++-----------------+-------+-----------------+ +| Dante Alighieri | 9971 | +| Charles Dickens | -5- | +| | 021 | +| | 0-0 | ++-----------------+-------+-----------------+ +| 9971 | Dante | +| -5- | Alighieri | +| 021 | | +| 0-0 | | ++-----------------+-------+-----------------+ + +TABLE + ), + 'Cell with rowspan and colspan without using TableSeparator' => array( + array('ISBN', 'Title', 'Author'), + array( + array( + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + 'Dante Alighieri', + ), + array('Charles Dickens'), + array( + 'Dante Alighieri', + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + ), + array('Charles Dickens'), + ), + 'default', +<<<'TABLE' ++-----------------+-------+-----------------+ +| ISBN | Title | Author | ++-----------------+-------+-----------------+ +| 9971 | Dante Alighieri | +| -5- | Charles Dickens | +| 021 | | +| 0-0 | | +| Dante Alighieri | 9971 | +| Charles Dickens | -5- | +| | 021 | +| | 0-0 | ++-----------------+-------+-----------------+ + +TABLE + ), + 'Cell with rowspan and colspan with separator inside a rowspan' => array( + array('ISBN', 'Author'), + array( + array( + new TableCell('9971-5-0210-0', array('rowspan' => 3, 'colspan' => 1)), + 'Dante Alighieri', + ), + array(new TableSeparator()), + array('Charles Dickens'), + ), + 'default', +<<<'TABLE' ++---------------+-----------------+ +| ISBN | Author | ++---------------+-----------------+ +| 9971-5-0210-0 | Dante Alighieri | +| |-----------------| +| | Charles Dickens | ++---------------+-----------------+ + +TABLE + ), + 'Multiple header lines' => array( + array( + array(new TableCell('Main title', array('colspan' => 3))), + array('ISBN', 'Title', 'Author'), + ), + array(), + 'default', +<<<'TABLE' ++------+-------+--------+ +| Main title | ++------+-------+--------+ +| ISBN | Title | Author | ++------+-------+--------+ + +TABLE + ), + 'Row with multiple cells' => array( + array(), + array( + array( + new TableCell('1', array('colspan' => 3)), + new TableCell('2', array('colspan' => 2)), + new TableCell('3', array('colspan' => 2)), + new TableCell('4', array('colspan' => 2)), + ), + ), + 'default', +<<<'TABLE' ++---+--+--+---+--+---+--+---+--+ +| 1 | 2 | 3 | 4 | ++---+--+--+---+--+---+--+---+--+ + +TABLE + ), + 'Coslpan and table cells with comment style' => array( + array( + new TableCell('Long Title', array('colspan' => 3)), + ), + array( + array( + new TableCell('9971-5-0210-0', array('colspan' => 3)), + ), + new TableSeparator(), + array( + 'Dante Alighieri', + 'J. R. R. Tolkien', + 'J. R. R', + ), + ), + 'default', + << array( + array(), + array( + array( + new TableCell('Dont break'."\n".'here', array('colspan' => 2)), + ), + new TableSeparator(), + array( + 'foo', + new TableCell('Dont break'."\n".'here', array('rowspan' => 2)), + ), + array( + 'bar', + ), + ), + 'default', + <<<'TABLE' ++-------+------------+ +| Dont break | +| here | ++-------+------------+ +| foo | Dont break | +| bar | here | ++-------+------------+ + +TABLE + , + true, + ), + ); + } + + /** + * @requires extension mbstring + */ + public function testRenderMultiByte() + { + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders(array('■■')) + ->setRows(array(array(1234))) + ->setStyle('default') + ; + $table->render(); + + $expected = +<<<'TABLE' ++------+ +| ■■ | ++------+ +| 1234 | ++------+ + +TABLE; + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + public function testTableCellWithNumericIntValue() + { + $table = new Table($output = $this->getOutputStream()); + + $table->setRows(array(array(new TableCell(12345)))); + $table->render(); + + $expected = +<<<'TABLE' ++-------+ +| 12345 | ++-------+ + +TABLE; + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + public function testTableCellWithNumericFloatValue() + { + $table = new Table($output = $this->getOutputStream()); + + $table->setRows(array(array(new TableCell(12345.01)))); + $table->render(); + + $expected = +<<<'TABLE' ++----------+ +| 12345.01 | ++----------+ + +TABLE; + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + public function testStyle() + { + $style = new TableStyle(); + $style + ->setHorizontalBorderChar('.') + ->setVerticalBorderChar('.') + ->setCrossingChar('.') + ; + + Table::setStyleDefinition('dotfull', $style); + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders(array('Foo')) + ->setRows(array(array('Bar'))) + ->setStyle('dotfull'); + $table->render(); + + $expected = +<<<'TABLE' +....... +. Foo . +....... +. Bar . +....... + +TABLE; + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + public function testRowSeparator() + { + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders(array('Foo')) + ->setRows(array( + array('Bar1'), + new TableSeparator(), + array('Bar2'), + new TableSeparator(), + array('Bar3'), + )); + $table->render(); + + $expected = +<<<'TABLE' ++------+ +| Foo | ++------+ +| Bar1 | ++------+ +| Bar2 | ++------+ +| Bar3 | ++------+ + +TABLE; + + $this->assertEquals($expected, $this->getOutputContent($output)); + + $this->assertEquals($table, $table->addRow(new TableSeparator()), 'fluent interface on addRow() with a single TableSeparator() works'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Style "absent" is not defined. + */ + public function testIsNotDefinedStyleException() + { + $table = new Table($this->getOutputStream()); + $table->setStyle('absent'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Style "absent" is not defined. + */ + public function testGetStyleDefinition() + { + Table::getStyleDefinition('absent'); + } + + protected function getOutputStream($decorated = false) + { + return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, $decorated); + } + + protected function getOutputContent(StreamOutput $output) + { + rewind($output->getStream()); + + return str_replace(PHP_EOL, "\n", stream_get_contents($output->getStream())); + } +} diff --git a/core/vendor/symfony/console/Tests/Input/ArgvInputTest.php b/core/vendor/symfony/console/Tests/Input/ArgvInputTest.php new file mode 100644 index 0000000..5f813ee --- /dev/null +++ b/core/vendor/symfony/console/Tests/Input/ArgvInputTest.php @@ -0,0 +1,416 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class ArgvInputTest extends TestCase +{ + public function testConstructor() + { + $_SERVER['argv'] = array('cli.php', 'foo'); + $input = new ArgvInput(); + $r = new \ReflectionObject($input); + $p = $r->getProperty('tokens'); + $p->setAccessible(true); + + $this->assertEquals(array('foo'), $p->getValue($input), '__construct() automatically get its input from the argv server variable'); + } + + public function testParseArguments() + { + $input = new ArgvInput(array('cli.php', 'foo')); + $input->bind(new InputDefinition(array(new InputArgument('name')))); + $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() parses required arguments'); + + $input->bind(new InputDefinition(array(new InputArgument('name')))); + $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() is stateless'); + } + + /** + * @dataProvider provideOptions + */ + public function testParseOptions($input, $options, $expectedOptions, $message) + { + $input = new ArgvInput($input); + $input->bind(new InputDefinition($options)); + + $this->assertEquals($expectedOptions, $input->getOptions(), $message); + } + + public function provideOptions() + { + return array( + array( + array('cli.php', '--foo'), + array(new InputOption('foo')), + array('foo' => true), + '->parse() parses long options without a value', + ), + array( + array('cli.php', '--foo=bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)), + array('foo' => 'bar'), + '->parse() parses long options with a required value (with a = separator)', + ), + array( + array('cli.php', '--foo', 'bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)), + array('foo' => 'bar'), + '->parse() parses long options with a required value (with a space separator)', + ), + array( + array('cli.php', '--foo='), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)), + array('foo' => null), + '->parse() parses long options with optional value which is empty (with a = separator) as null', + ), + array( + array('cli.php', '--foo=', 'bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputArgument('name', InputArgument::REQUIRED)), + array('foo' => null), + '->parse() parses long options with optional value which is empty (with a = separator) followed by an argument', + ), + array( + array('cli.php', '-f'), + array(new InputOption('foo', 'f')), + array('foo' => true), + '->parse() parses short options without a value', + ), + array( + array('cli.php', '-fbar'), + array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)), + array('foo' => 'bar'), + '->parse() parses short options with a required value (with no separator)', + ), + array( + array('cli.php', '-f', 'bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)), + array('foo' => 'bar'), + '->parse() parses short options with a required value (with a space separator)', + ), + array( + array('cli.php', '-f', ''), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)), + array('foo' => ''), + '->parse() parses short options with an optional empty value', + ), + array( + array('cli.php', '-f', '', 'foo'), + array(new InputArgument('name'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)), + array('foo' => ''), + '->parse() parses short options with an optional empty value followed by an argument', + ), + array( + array('cli.php', '-f', '', '-b'), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b')), + array('foo' => '', 'bar' => true), + '->parse() parses short options with an optional empty value followed by an option', + ), + array( + array('cli.php', '-f', '-b', 'foo'), + array(new InputArgument('name'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b')), + array('foo' => null, 'bar' => true), + '->parse() parses short options with an optional value which is not present', + ), + array( + array('cli.php', '-fb'), + array(new InputOption('foo', 'f'), new InputOption('bar', 'b')), + array('foo' => true, 'bar' => true), + '->parse() parses short options when they are aggregated as a single one', + ), + array( + array('cli.php', '-fb', 'bar'), + array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_REQUIRED)), + array('foo' => true, 'bar' => 'bar'), + '->parse() parses short options when they are aggregated as a single one and the last one has a required value', + ), + array( + array('cli.php', '-fb', 'bar'), + array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)), + array('foo' => true, 'bar' => 'bar'), + '->parse() parses short options when they are aggregated as a single one and the last one has an optional value', + ), + array( + array('cli.php', '-fbbar'), + array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)), + array('foo' => true, 'bar' => 'bar'), + '->parse() parses short options when they are aggregated as a single one and the last one has an optional value with no separator', + ), + array( + array('cli.php', '-fbbar'), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)), + array('foo' => 'bbar', 'bar' => null), + '->parse() parses short options when they are aggregated as a single one and one of them takes a value', + ), + ); + } + + /** + * @dataProvider provideInvalidInput + */ + public function testInvalidInput($argv, $definition, $expectedExceptionMessage) + { + if (method_exists($this, 'expectException')) { + $this->expectException('RuntimeException'); + $this->expectExceptionMessage($expectedExceptionMessage); + } else { + $this->setExpectedException('RuntimeException', $expectedExceptionMessage); + } + + $input = new ArgvInput($argv); + $input->bind($definition); + } + + public function provideInvalidInput() + { + return array( + array( + array('cli.php', '--foo'), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))), + 'The "--foo" option requires a value.', + ), + array( + array('cli.php', '-f'), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))), + 'The "--foo" option requires a value.', + ), + array( + array('cli.php', '-ffoo'), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_NONE))), + 'The "-o" option does not exist.', + ), + array( + array('cli.php', '--foo=bar'), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_NONE))), + 'The "--foo" option does not accept a value.', + ), + array( + array('cli.php', 'foo', 'bar'), + new InputDefinition(), + 'No arguments expected, got "foo".', + ), + array( + array('cli.php', 'foo', 'bar'), + new InputDefinition(array(new InputArgument('number'))), + 'Too many arguments, expected arguments "number".', + ), + array( + array('cli.php', 'foo', 'bar', 'zzz'), + new InputDefinition(array(new InputArgument('number'), new InputArgument('county'))), + 'Too many arguments, expected arguments "number" "county".', + ), + array( + array('cli.php', '--foo'), + new InputDefinition(), + 'The "--foo" option does not exist.', + ), + array( + array('cli.php', '-f'), + new InputDefinition(), + 'The "-f" option does not exist.', + ), + array( + array('cli.php', '-1'), + new InputDefinition(array(new InputArgument('number'))), + 'The "-1" option does not exist.', + ), + ); + } + + public function testParseArrayArgument() + { + $input = new ArgvInput(array('cli.php', 'foo', 'bar', 'baz', 'bat')); + $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::IS_ARRAY)))); + + $this->assertEquals(array('name' => array('foo', 'bar', 'baz', 'bat')), $input->getArguments(), '->parse() parses array arguments'); + } + + public function testParseArrayOption() + { + $input = new ArgvInput(array('cli.php', '--name=foo', '--name=bar', '--name=baz')); + $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY)))); + + $this->assertEquals(array('name' => array('foo', 'bar', 'baz')), $input->getOptions(), '->parse() parses array options ("--option=value" syntax)'); + + $input = new ArgvInput(array('cli.php', '--name', 'foo', '--name', 'bar', '--name', 'baz')); + $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY)))); + $this->assertEquals(array('name' => array('foo', 'bar', 'baz')), $input->getOptions(), '->parse() parses array options ("--option value" syntax)'); + + $input = new ArgvInput(array('cli.php', '--name=foo', '--name=bar', '--name=')); + $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY)))); + $this->assertSame(array('name' => array('foo', 'bar', null)), $input->getOptions(), '->parse() parses empty array options as null ("--option=value" syntax)'); + + $input = new ArgvInput(array('cli.php', '--name', 'foo', '--name', 'bar', '--name', '--anotherOption')); + $input->bind(new InputDefinition(array( + new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY), + new InputOption('anotherOption', null, InputOption::VALUE_NONE), + ))); + $this->assertSame(array('name' => array('foo', 'bar', null), 'anotherOption' => true), $input->getOptions(), '->parse() parses empty array options as null ("--option value" syntax)'); + } + + public function testParseNegativeNumberAfterDoubleDash() + { + $input = new ArgvInput(array('cli.php', '--', '-1')); + $input->bind(new InputDefinition(array(new InputArgument('number')))); + $this->assertEquals(array('number' => '-1'), $input->getArguments(), '->parse() parses arguments with leading dashes as arguments after having encountered a double-dash sequence'); + + $input = new ArgvInput(array('cli.php', '-f', 'bar', '--', '-1')); + $input->bind(new InputDefinition(array(new InputArgument('number'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)))); + $this->assertEquals(array('foo' => 'bar'), $input->getOptions(), '->parse() parses arguments with leading dashes as options before having encountered a double-dash sequence'); + $this->assertEquals(array('number' => '-1'), $input->getArguments(), '->parse() parses arguments with leading dashes as arguments after having encountered a double-dash sequence'); + } + + public function testParseEmptyStringArgument() + { + $input = new ArgvInput(array('cli.php', '-f', 'bar', '')); + $input->bind(new InputDefinition(array(new InputArgument('empty'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)))); + + $this->assertEquals(array('empty' => ''), $input->getArguments(), '->parse() parses empty string arguments'); + } + + public function testGetFirstArgument() + { + $input = new ArgvInput(array('cli.php', '-fbbar')); + $this->assertNull($input->getFirstArgument(), '->getFirstArgument() returns null when there is no arguments'); + + $input = new ArgvInput(array('cli.php', '-fbbar', 'foo')); + $this->assertEquals('foo', $input->getFirstArgument(), '->getFirstArgument() returns the first argument from the raw input'); + } + + public function testHasParameterOption() + { + $input = new ArgvInput(array('cli.php', '-f', 'foo')); + $this->assertTrue($input->hasParameterOption('-f'), '->hasParameterOption() returns true if the given short option is in the raw input'); + + $input = new ArgvInput(array('cli.php', '-etest')); + $this->assertTrue($input->hasParameterOption('-e'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $this->assertFalse($input->hasParameterOption('-s'), '->hasParameterOption() returns true if the given short option is in the raw input'); + + $input = new ArgvInput(array('cli.php', '--foo', 'foo')); + $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if the given short option is in the raw input'); + + $input = new ArgvInput(array('cli.php', 'foo')); + $this->assertFalse($input->hasParameterOption('--foo'), '->hasParameterOption() returns false if the given short option is not in the raw input'); + + $input = new ArgvInput(array('cli.php', '--foo=bar')); + $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if the given option with provided value is in the raw input'); + } + + public function testHasParameterOptionEdgeCasesAndLimitations() + { + $input = new ArgvInput(array('cli.php', '-fh')); + // hasParameterOption does not know if the previous short option, -f, + // takes a value or not. If -f takes a value, then -fh does NOT include + // -h; Otherwise it does. Since we do not know which short options take + // values, hasParameterOption does not support this use-case. + $this->assertFalse($input->hasParameterOption('-h'), '->hasParameterOption() returns true if the given short option is in the raw input'); + // hasParameterOption does detect that `-fh` contains `-f`, since + // `-f` is the first short option in the set. + $this->assertTrue($input->hasParameterOption('-f'), '->hasParameterOption() returns true if the given short option is in the raw input'); + // The test below happens to pass, although it might make more sense + // to disallow it, and require the use of + // $input->hasParameterOption('-f') && $input->hasParameterOption('-h') + // instead. + $this->assertTrue($input->hasParameterOption('-fh'), '->hasParameterOption() returns true if the given short option is in the raw input'); + // In theory, if -fh is supported, then -hf should also work. + // However, this is not supported. + $this->assertFalse($input->hasParameterOption('-hf'), '->hasParameterOption() returns true if the given short option is in the raw input'); + + $input = new ArgvInput(array('cli.php', '-f', '-h')); + // If hasParameterOption('-fh') is supported for 'cli.php -fh', then + // one might also expect that it should also be supported for + // 'cli.php -f -h'. However, this is not supported. + $this->assertFalse($input->hasParameterOption('-fh'), '->hasParameterOption() returns true if the given short option is in the raw input'); + } + + public function testNoWarningOnInvalidParameterOption() + { + $input = new ArgvInput(array('cli.php', '-edev')); + + $this->assertTrue($input->hasParameterOption(array('-e', ''))); + // No warning thrown + $this->assertFalse($input->hasParameterOption(array('-m', ''))); + + $this->assertEquals('dev', $input->getParameterOption(array('-e', ''))); + // No warning thrown + $this->assertFalse($input->getParameterOption(array('-m', ''))); + } + + public function testToString() + { + $input = new ArgvInput(array('cli.php', '-f', 'foo')); + $this->assertEquals('-f foo', (string) $input); + + $input = new ArgvInput(array('cli.php', '-f', '--bar=foo', 'a b c d', "A\nB'C")); + $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d').' '.escapeshellarg("A\nB'C"), (string) $input); + } + + /** + * @dataProvider provideGetParameterOptionValues + */ + public function testGetParameterOptionEqualSign($argv, $key, $expected) + { + $input = new ArgvInput($argv); + $this->assertEquals($expected, $input->getParameterOption($key), '->getParameterOption() returns the expected value'); + } + + public function provideGetParameterOptionValues() + { + return array( + array(array('app/console', 'foo:bar', '-edev'), '-e', 'dev'), + array(array('app/console', 'foo:bar', '-e', 'dev'), '-e', 'dev'), + array(array('app/console', 'foo:bar', '--env=dev'), '--env', 'dev'), + array(array('app/console', 'foo:bar', '-e', 'dev'), array('-e', '--env'), 'dev'), + array(array('app/console', 'foo:bar', '--env=dev'), array('-e', '--env'), 'dev'), + array(array('app/console', 'foo:bar', '--env=dev', '--en=1'), array('--en'), '1'), + array(array('app/console', 'foo:bar', '--env=dev', '', '--en=1'), array('--en'), '1'), + ); + } + + public function testParseSingleDashAsArgument() + { + $input = new ArgvInput(array('cli.php', '-')); + $input->bind(new InputDefinition(array(new InputArgument('file')))); + $this->assertEquals(array('file' => '-'), $input->getArguments(), '->parse() parses single dash as an argument'); + } + + public function testParseOptionWithValueOptionalGivenEmptyAndRequiredArgument() + { + $input = new ArgvInput(array('cli.php', '--foo=', 'bar')); + $input->bind(new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputArgument('name', InputArgument::REQUIRED)))); + $this->assertEquals(array('foo' => null), $input->getOptions(), '->parse() parses optional options with empty value as null'); + $this->assertEquals(array('name' => 'bar'), $input->getArguments(), '->parse() parses required arguments'); + + $input = new ArgvInput(array('cli.php', '--foo=0', 'bar')); + $input->bind(new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputArgument('name', InputArgument::REQUIRED)))); + $this->assertEquals(array('foo' => '0'), $input->getOptions(), '->parse() parses optional options with empty value as null'); + $this->assertEquals(array('name' => 'bar'), $input->getArguments(), '->parse() parses required arguments'); + } + + public function testParseOptionWithValueOptionalGivenEmptyAndOptionalArgument() + { + $input = new ArgvInput(array('cli.php', '--foo=', 'bar')); + $input->bind(new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputArgument('name', InputArgument::OPTIONAL)))); + $this->assertEquals(array('foo' => null), $input->getOptions(), '->parse() parses optional options with empty value as null'); + $this->assertEquals(array('name' => 'bar'), $input->getArguments(), '->parse() parses optional arguments'); + + $input = new ArgvInput(array('cli.php', '--foo=0', 'bar')); + $input->bind(new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputArgument('name', InputArgument::OPTIONAL)))); + $this->assertEquals(array('foo' => '0'), $input->getOptions(), '->parse() parses optional options with empty value as null'); + $this->assertEquals(array('name' => 'bar'), $input->getArguments(), '->parse() parses optional arguments'); + } +} diff --git a/core/vendor/symfony/console/Tests/Input/ArrayInputTest.php b/core/vendor/symfony/console/Tests/Input/ArrayInputTest.php new file mode 100644 index 0000000..b998172 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Input/ArrayInputTest.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class ArrayInputTest extends TestCase +{ + public function testGetFirstArgument() + { + $input = new ArrayInput(array()); + $this->assertNull($input->getFirstArgument(), '->getFirstArgument() returns null if no argument were passed'); + $input = new ArrayInput(array('name' => 'Fabien')); + $this->assertEquals('Fabien', $input->getFirstArgument(), '->getFirstArgument() returns the first passed argument'); + $input = new ArrayInput(array('--foo' => 'bar', 'name' => 'Fabien')); + $this->assertEquals('Fabien', $input->getFirstArgument(), '->getFirstArgument() returns the first passed argument'); + } + + public function testHasParameterOption() + { + $input = new ArrayInput(array('name' => 'Fabien', '--foo' => 'bar')); + $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if an option is present in the passed parameters'); + $this->assertFalse($input->hasParameterOption('--bar'), '->hasParameterOption() returns false if an option is not present in the passed parameters'); + + $input = new ArrayInput(array('--foo')); + $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if an option is present in the passed parameters'); + } + + public function testGetParameterOption() + { + $input = new ArrayInput(array('name' => 'Fabien', '--foo' => 'bar')); + $this->assertEquals('bar', $input->getParameterOption('--foo'), '->getParameterOption() returns the option of specified name'); + + $input = new ArrayInput(array('Fabien', '--foo' => 'bar')); + $this->assertEquals('bar', $input->getParameterOption('--foo'), '->getParameterOption() returns the option of specified name'); + } + + public function testParseArguments() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); + + $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() parses required arguments'); + } + + /** + * @dataProvider provideOptions + */ + public function testParseOptions($input, $options, $expectedOptions, $message) + { + $input = new ArrayInput($input, new InputDefinition($options)); + + $this->assertEquals($expectedOptions, $input->getOptions(), $message); + } + + public function provideOptions() + { + return array( + array( + array('--foo' => 'bar'), + array(new InputOption('foo')), + array('foo' => 'bar'), + '->parse() parses long options', + ), + array( + array('--foo' => 'bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, '', 'default')), + array('foo' => 'bar'), + '->parse() parses long options with a default value', + ), + array( + array('--foo' => null), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, '', 'default')), + array('foo' => 'default'), + '->parse() parses long options with a default value', + ), + array( + array('-f' => 'bar'), + array(new InputOption('foo', 'f')), + array('foo' => 'bar'), + '->parse() parses short options', + ), + ); + } + + /** + * @dataProvider provideInvalidInput + */ + public function testParseInvalidInput($parameters, $definition, $expectedExceptionMessage) + { + if (method_exists($this, 'expectException')) { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage($expectedExceptionMessage); + } else { + $this->setExpectedException('InvalidArgumentException', $expectedExceptionMessage); + } + + new ArrayInput($parameters, $definition); + } + + public function provideInvalidInput() + { + return array( + array( + array('foo' => 'foo'), + new InputDefinition(array(new InputArgument('name'))), + 'The "foo" argument does not exist.', + ), + array( + array('--foo' => null), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))), + 'The "--foo" option requires a value.', + ), + array( + array('--foo' => 'foo'), + new InputDefinition(), + 'The "--foo" option does not exist.', + ), + array( + array('-o' => 'foo'), + new InputDefinition(), + 'The "-o" option does not exist.', + ), + ); + } + + public function testToString() + { + $input = new ArrayInput(array('-f' => null, '-b' => 'bar', '--foo' => 'b a z', '--lala' => null, 'test' => 'Foo', 'test2' => "A\nB'C")); + $this->assertEquals('-f -b=bar --foo='.escapeshellarg('b a z').' --lala Foo '.escapeshellarg("A\nB'C"), (string) $input); + + $input = new ArrayInput(array('-b' => array('bval_1', 'bval_2'), '--f' => array('fval_1', 'fval_2'))); + $this->assertSame('-b=bval_1 -b=bval_2 --f=fval_1 --f=fval_2', (string) $input); + + $input = new ArrayInput(array('array_arg' => array('val_1', 'val_2'))); + $this->assertSame('val_1 val_2', (string) $input); + } +} diff --git a/core/vendor/symfony/console/Tests/Input/InputArgumentTest.php b/core/vendor/symfony/console/Tests/Input/InputArgumentTest.php new file mode 100644 index 0000000..66af98b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Input/InputArgumentTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\InputArgument; + +class InputArgumentTest extends TestCase +{ + public function testConstructor() + { + $argument = new InputArgument('foo'); + $this->assertEquals('foo', $argument->getName(), '__construct() takes a name as its first argument'); + } + + public function testModes() + { + $argument = new InputArgument('foo'); + $this->assertFalse($argument->isRequired(), '__construct() gives a "InputArgument::OPTIONAL" mode by default'); + + $argument = new InputArgument('foo', null); + $this->assertFalse($argument->isRequired(), '__construct() can take "InputArgument::OPTIONAL" as its mode'); + + $argument = new InputArgument('foo', InputArgument::OPTIONAL); + $this->assertFalse($argument->isRequired(), '__construct() can take "InputArgument::OPTIONAL" as its mode'); + + $argument = new InputArgument('foo', InputArgument::REQUIRED); + $this->assertTrue($argument->isRequired(), '__construct() can take "InputArgument::REQUIRED" as its mode'); + } + + /** + * @dataProvider provideInvalidModes + */ + public function testInvalidModes($mode) + { + if (method_exists($this, 'expectException')) { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage(sprintf('Argument mode "%s" is not valid.', $mode)); + } else { + $this->setExpectedException('InvalidArgumentException', sprintf('Argument mode "%s" is not valid.', $mode)); + } + + new InputArgument('foo', $mode); + } + + public function provideInvalidModes() + { + return array( + array('ANOTHER_ONE'), + array(-1), + ); + } + + public function testIsArray() + { + $argument = new InputArgument('foo', InputArgument::IS_ARRAY); + $this->assertTrue($argument->isArray(), '->isArray() returns true if the argument can be an array'); + $argument = new InputArgument('foo', InputArgument::OPTIONAL | InputArgument::IS_ARRAY); + $this->assertTrue($argument->isArray(), '->isArray() returns true if the argument can be an array'); + $argument = new InputArgument('foo', InputArgument::OPTIONAL); + $this->assertFalse($argument->isArray(), '->isArray() returns false if the argument can not be an array'); + } + + public function testGetDescription() + { + $argument = new InputArgument('foo', null, 'Some description'); + $this->assertEquals('Some description', $argument->getDescription(), '->getDescription() return the message description'); + } + + public function testGetDefault() + { + $argument = new InputArgument('foo', InputArgument::OPTIONAL, '', 'default'); + $this->assertEquals('default', $argument->getDefault(), '->getDefault() return the default value'); + } + + public function testSetDefault() + { + $argument = new InputArgument('foo', InputArgument::OPTIONAL, '', 'default'); + $argument->setDefault(null); + $this->assertNull($argument->getDefault(), '->setDefault() can reset the default value by passing null'); + $argument->setDefault('another'); + $this->assertEquals('another', $argument->getDefault(), '->setDefault() changes the default value'); + + $argument = new InputArgument('foo', InputArgument::OPTIONAL | InputArgument::IS_ARRAY); + $argument->setDefault(array(1, 2)); + $this->assertEquals(array(1, 2), $argument->getDefault(), '->setDefault() changes the default value'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot set a default value except for InputArgument::OPTIONAL mode. + */ + public function testSetDefaultWithRequiredArgument() + { + $argument = new InputArgument('foo', InputArgument::REQUIRED); + $argument->setDefault('default'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage A default value for an array argument must be an array. + */ + public function testSetDefaultWithArrayArgument() + { + $argument = new InputArgument('foo', InputArgument::IS_ARRAY); + $argument->setDefault('default'); + } +} diff --git a/core/vendor/symfony/console/Tests/Input/InputDefinitionTest.php b/core/vendor/symfony/console/Tests/Input/InputDefinitionTest.php new file mode 100644 index 0000000..b19708e --- /dev/null +++ b/core/vendor/symfony/console/Tests/Input/InputDefinitionTest.php @@ -0,0 +1,441 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class InputDefinitionTest extends TestCase +{ + protected static $fixtures; + + protected $foo; + protected $bar; + protected $foo1; + protected $foo2; + + public static function setUpBeforeClass() + { + self::$fixtures = __DIR__.'/../Fixtures/'; + } + + public function testConstructorArguments() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $this->assertEquals(array(), $definition->getArguments(), '__construct() creates a new InputDefinition object'); + + $definition = new InputDefinition(array($this->foo, $this->bar)); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '__construct() takes an array of InputArgument objects as its first argument'); + } + + public function testConstructorOptions() + { + $this->initializeOptions(); + + $definition = new InputDefinition(); + $this->assertEquals(array(), $definition->getOptions(), '__construct() creates a new InputDefinition object'); + + $definition = new InputDefinition(array($this->foo, $this->bar)); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '__construct() takes an array of InputOption objects as its first argument'); + } + + public function testSetArguments() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->setArguments(array($this->foo)); + $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->setArguments() sets the array of InputArgument objects'); + $definition->setArguments(array($this->bar)); + + $this->assertEquals(array('bar' => $this->bar), $definition->getArguments(), '->setArguments() clears all InputArgument objects'); + } + + public function testAddArguments() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArguments(array($this->foo)); + $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->addArguments() adds an array of InputArgument objects'); + $definition->addArguments(array($this->bar)); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '->addArguments() does not clear existing InputArgument objects'); + } + + public function testAddArgument() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo); + $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->addArgument() adds a InputArgument object'); + $definition->addArgument($this->bar); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '->addArgument() adds a InputArgument object'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage An argument with name "foo" already exists. + */ + public function testArgumentsMustHaveDifferentNames() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo); + $definition->addArgument($this->foo1); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot add an argument after an array argument. + */ + public function testArrayArgumentHasToBeLast() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument(new InputArgument('fooarray', InputArgument::IS_ARRAY)); + $definition->addArgument(new InputArgument('anotherbar')); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot add a required argument after an optional one. + */ + public function testRequiredArgumentCannotFollowAnOptionalOne() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo); + $definition->addArgument($this->foo2); + } + + public function testGetArgument() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArguments(array($this->foo)); + $this->assertEquals($this->foo, $definition->getArgument('foo'), '->getArgument() returns a InputArgument by its name'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "bar" argument does not exist. + */ + public function testGetInvalidArgument() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArguments(array($this->foo)); + $definition->getArgument('bar'); + } + + public function testHasArgument() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArguments(array($this->foo)); + + $this->assertTrue($definition->hasArgument('foo'), '->hasArgument() returns true if a InputArgument exists for the given name'); + $this->assertFalse($definition->hasArgument('bar'), '->hasArgument() returns false if a InputArgument exists for the given name'); + } + + public function testGetArgumentRequiredCount() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo2); + $this->assertEquals(1, $definition->getArgumentRequiredCount(), '->getArgumentRequiredCount() returns the number of required arguments'); + $definition->addArgument($this->foo); + $this->assertEquals(1, $definition->getArgumentRequiredCount(), '->getArgumentRequiredCount() returns the number of required arguments'); + } + + public function testGetArgumentCount() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo2); + $this->assertEquals(1, $definition->getArgumentCount(), '->getArgumentCount() returns the number of arguments'); + $definition->addArgument($this->foo); + $this->assertEquals(2, $definition->getArgumentCount(), '->getArgumentCount() returns the number of arguments'); + } + + public function testGetArgumentDefaults() + { + $definition = new InputDefinition(array( + new InputArgument('foo1', InputArgument::OPTIONAL), + new InputArgument('foo2', InputArgument::OPTIONAL, '', 'default'), + new InputArgument('foo3', InputArgument::OPTIONAL | InputArgument::IS_ARRAY), + // new InputArgument('foo4', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '', array(1, 2)), + )); + $this->assertEquals(array('foo1' => null, 'foo2' => 'default', 'foo3' => array()), $definition->getArgumentDefaults(), '->getArgumentDefaults() return the default values for each argument'); + + $definition = new InputDefinition(array( + new InputArgument('foo4', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '', array(1, 2)), + )); + $this->assertEquals(array('foo4' => array(1, 2)), $definition->getArgumentDefaults(), '->getArgumentDefaults() return the default values for each argument'); + } + + public function testSetOptions() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->setOptions() sets the array of InputOption objects'); + $definition->setOptions(array($this->bar)); + $this->assertEquals(array('bar' => $this->bar), $definition->getOptions(), '->setOptions() clears all InputOption objects'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "-f" option does not exist. + */ + public function testSetOptionsClearsOptions() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $definition->setOptions(array($this->bar)); + $definition->getOptionForShortcut('f'); + } + + public function testAddOptions() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->addOptions() adds an array of InputOption objects'); + $definition->addOptions(array($this->bar)); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '->addOptions() does not clear existing InputOption objects'); + } + + public function testAddOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(); + $definition->addOption($this->foo); + $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->addOption() adds a InputOption object'); + $definition->addOption($this->bar); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '->addOption() adds a InputOption object'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage An option named "foo" already exists. + */ + public function testAddDuplicateOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(); + $definition->addOption($this->foo); + $definition->addOption($this->foo2); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage An option with shortcut "f" already exists. + */ + public function testAddDuplicateShortcutOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(); + $definition->addOption($this->foo); + $definition->addOption($this->foo1); + } + + public function testGetOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertEquals($this->foo, $definition->getOption('foo'), '->getOption() returns a InputOption by its name'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "--bar" option does not exist. + */ + public function testGetInvalidOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $definition->getOption('bar'); + } + + public function testHasOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertTrue($definition->hasOption('foo'), '->hasOption() returns true if a InputOption exists for the given name'); + $this->assertFalse($definition->hasOption('bar'), '->hasOption() returns false if a InputOption exists for the given name'); + } + + public function testHasShortcut() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertTrue($definition->hasShortcut('f'), '->hasShortcut() returns true if a InputOption exists for the given shortcut'); + $this->assertFalse($definition->hasShortcut('b'), '->hasShortcut() returns false if a InputOption exists for the given shortcut'); + } + + public function testGetOptionForShortcut() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertEquals($this->foo, $definition->getOptionForShortcut('f'), '->getOptionForShortcut() returns a InputOption by its shortcut'); + } + + public function testGetOptionForMultiShortcut() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->multi)); + $this->assertEquals($this->multi, $definition->getOptionForShortcut('m'), '->getOptionForShortcut() returns a InputOption by its shortcut'); + $this->assertEquals($this->multi, $definition->getOptionForShortcut('mmm'), '->getOptionForShortcut() returns a InputOption by its shortcut'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "-l" option does not exist. + */ + public function testGetOptionForInvalidShortcut() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $definition->getOptionForShortcut('l'); + } + + public function testGetOptionDefaults() + { + $definition = new InputDefinition(array( + new InputOption('foo1', null, InputOption::VALUE_NONE), + new InputOption('foo2', null, InputOption::VALUE_REQUIRED), + new InputOption('foo3', null, InputOption::VALUE_REQUIRED, '', 'default'), + new InputOption('foo4', null, InputOption::VALUE_OPTIONAL), + new InputOption('foo5', null, InputOption::VALUE_OPTIONAL, '', 'default'), + new InputOption('foo6', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY), + new InputOption('foo7', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, '', array(1, 2)), + )); + $defaults = array( + 'foo1' => false, + 'foo2' => null, + 'foo3' => 'default', + 'foo4' => null, + 'foo5' => 'default', + 'foo6' => array(), + 'foo7' => array(1, 2), + ); + $this->assertSame($defaults, $definition->getOptionDefaults(), '->getOptionDefaults() returns the default values for all options'); + } + + /** + * @dataProvider getGetSynopsisData + */ + public function testGetSynopsis(InputDefinition $definition, $expectedSynopsis, $message = null) + { + $this->assertEquals($expectedSynopsis, $definition->getSynopsis(), $message ? '->getSynopsis() '.$message : ''); + } + + public function getGetSynopsisData() + { + return array( + array(new InputDefinition(array(new InputOption('foo'))), '[--foo]', 'puts optional options in square brackets'), + array(new InputDefinition(array(new InputOption('foo', 'f'))), '[-f|--foo]', 'separates shortcut with a pipe'), + array(new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))), '[-f|--foo FOO]', 'uses shortcut as value placeholder'), + array(new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL))), '[-f|--foo [FOO]]', 'puts optional values in square brackets'), + + array(new InputDefinition(array(new InputArgument('foo', InputArgument::REQUIRED))), '', 'puts arguments in angle brackets'), + array(new InputDefinition(array(new InputArgument('foo'))), '[]', 'puts optional arguments in square brackets'), + array(new InputDefinition(array(new InputArgument('foo', InputArgument::IS_ARRAY))), '[]...', 'uses an ellipsis for array arguments'), + array(new InputDefinition(array(new InputArgument('foo', InputArgument::REQUIRED | InputArgument::IS_ARRAY))), ' ()...', 'uses parenthesis and ellipsis for required array arguments'), + + array(new InputDefinition(array(new InputOption('foo'), new InputArgument('foo', InputArgument::REQUIRED))), '[--foo] [--] ', 'puts [--] between options and arguments'), + ); + } + + public function testGetShortSynopsis() + { + $definition = new InputDefinition(array(new InputOption('foo'), new InputOption('bar'), new InputArgument('cat'))); + $this->assertEquals('[options] [--] []', $definition->getSynopsis(true), '->getSynopsis(true) groups options in [options]'); + } + + /** + * @group legacy + */ + public function testLegacyAsText() + { + $definition = new InputDefinition(array( + new InputArgument('foo', InputArgument::OPTIONAL, 'The foo argument'), + new InputArgument('baz', InputArgument::OPTIONAL, 'The baz argument', true), + new InputArgument('bar', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The bar argument', array('http://foo.com/')), + new InputOption('foo', 'f', InputOption::VALUE_REQUIRED, 'The foo option'), + new InputOption('baz', null, InputOption::VALUE_OPTIONAL, 'The baz option', false), + new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL, 'The bar option', 'bar'), + new InputOption('qux', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The qux option', array('http://foo.com/', 'bar')), + new InputOption('qux2', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The qux2 option', array('foo' => 'bar')), + )); + + $this->assertStringEqualsFile(self::$fixtures.'/definition_astext.txt', $definition->asText(), '->asText() returns a textual representation of the InputDefinition'); + } + + /** + * @group legacy + */ + public function testLegacyAsXml() + { + $definition = new InputDefinition(array( + new InputArgument('foo', InputArgument::OPTIONAL, 'The foo argument'), + new InputArgument('baz', InputArgument::OPTIONAL, 'The baz argument', true), + new InputArgument('bar', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The bar argument', array('bar')), + new InputOption('foo', 'f', InputOption::VALUE_REQUIRED, 'The foo option'), + new InputOption('baz', null, InputOption::VALUE_OPTIONAL, 'The baz option', false), + new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL, 'The bar option', 'bar'), + )); + $this->assertXmlStringEqualsXmlFile(self::$fixtures.'/definition_asxml.txt', $definition->asXml(), '->asXml() returns an XML representation of the InputDefinition'); + } + + protected function initializeArguments() + { + $this->foo = new InputArgument('foo'); + $this->bar = new InputArgument('bar'); + $this->foo1 = new InputArgument('foo'); + $this->foo2 = new InputArgument('foo2', InputArgument::REQUIRED); + } + + protected function initializeOptions() + { + $this->foo = new InputOption('foo', 'f'); + $this->bar = new InputOption('bar', 'b'); + $this->foo1 = new InputOption('fooBis', 'f'); + $this->foo2 = new InputOption('foo', 'p'); + $this->multi = new InputOption('multi', 'm|mm|mmm'); + } +} diff --git a/core/vendor/symfony/console/Tests/Input/InputOptionTest.php b/core/vendor/symfony/console/Tests/Input/InputOptionTest.php new file mode 100644 index 0000000..943bf60 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Input/InputOptionTest.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\InputOption; + +class InputOptionTest extends TestCase +{ + public function testConstructor() + { + $option = new InputOption('foo'); + $this->assertEquals('foo', $option->getName(), '__construct() takes a name as its first argument'); + $option = new InputOption('--foo'); + $this->assertEquals('foo', $option->getName(), '__construct() removes the leading -- of the option name'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value. + */ + public function testArrayModeWithoutValue() + { + new InputOption('foo', 'f', InputOption::VALUE_IS_ARRAY); + } + + public function testShortcut() + { + $option = new InputOption('foo', 'f'); + $this->assertEquals('f', $option->getShortcut(), '__construct() can take a shortcut as its second argument'); + $option = new InputOption('foo', '-f|-ff|fff'); + $this->assertEquals('f|ff|fff', $option->getShortcut(), '__construct() removes the leading - of the shortcuts'); + $option = new InputOption('foo', array('f', 'ff', '-fff')); + $this->assertEquals('f|ff|fff', $option->getShortcut(), '__construct() removes the leading - of the shortcuts'); + $option = new InputOption('foo'); + $this->assertNull($option->getShortcut(), '__construct() makes the shortcut null by default'); + } + + public function testModes() + { + $option = new InputOption('foo', 'f'); + $this->assertFalse($option->acceptValue(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); + $this->assertFalse($option->isValueRequired(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); + $this->assertFalse($option->isValueOptional(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); + + $option = new InputOption('foo', 'f', null); + $this->assertFalse($option->acceptValue(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + + $option = new InputOption('foo', 'f', InputOption::VALUE_NONE); + $this->assertFalse($option->acceptValue(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + + $option = new InputOption('foo', 'f', InputOption::VALUE_REQUIRED); + $this->assertTrue($option->acceptValue(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode'); + $this->assertTrue($option->isValueRequired(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode'); + $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode'); + + $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL); + $this->assertTrue($option->acceptValue(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode'); + $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode'); + $this->assertTrue($option->isValueOptional(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode'); + } + + /** + * @dataProvider provideInvalidModes + */ + public function testInvalidModes($mode) + { + if (method_exists($this, 'expectException')) { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage(sprintf('Option mode "%s" is not valid.', $mode)); + } else { + $this->setExpectedException('InvalidArgumentException', sprintf('Option mode "%s" is not valid.', $mode)); + } + + new InputOption('foo', 'f', $mode); + } + + public function provideInvalidModes() + { + return array( + array('ANOTHER_ONE'), + array(-1), + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEmptyNameIsInvalid() + { + new InputOption(''); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testDoubleDashNameIsInvalid() + { + new InputOption('--'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSingleDashOptionIsInvalid() + { + new InputOption('foo', '-'); + } + + public function testIsArray() + { + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY); + $this->assertTrue($option->isArray(), '->isArray() returns true if the option can be an array'); + $option = new InputOption('foo', null, InputOption::VALUE_NONE); + $this->assertFalse($option->isArray(), '->isArray() returns false if the option can not be an array'); + } + + public function testGetDescription() + { + $option = new InputOption('foo', 'f', null, 'Some description'); + $this->assertEquals('Some description', $option->getDescription(), '->getDescription() returns the description message'); + } + + public function testGetDefault() + { + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', 'default'); + $this->assertEquals('default', $option->getDefault(), '->getDefault() returns the default value'); + + $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED, '', 'default'); + $this->assertEquals('default', $option->getDefault(), '->getDefault() returns the default value'); + + $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED); + $this->assertNull($option->getDefault(), '->getDefault() returns null if no default value is configured'); + + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY); + $this->assertEquals(array(), $option->getDefault(), '->getDefault() returns an empty array if option is an array'); + + $option = new InputOption('foo', null, InputOption::VALUE_NONE); + $this->assertFalse($option->getDefault(), '->getDefault() returns false if the option does not take a value'); + } + + public function testSetDefault() + { + $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED, '', 'default'); + $option->setDefault(null); + $this->assertNull($option->getDefault(), '->setDefault() can reset the default value by passing null'); + $option->setDefault('another'); + $this->assertEquals('another', $option->getDefault(), '->setDefault() changes the default value'); + + $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY); + $option->setDefault(array(1, 2)); + $this->assertEquals(array(1, 2), $option->getDefault(), '->setDefault() changes the default value'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot set a default value when using InputOption::VALUE_NONE mode. + */ + public function testDefaultValueWithValueNoneMode() + { + $option = new InputOption('foo', 'f', InputOption::VALUE_NONE); + $option->setDefault('default'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage A default value for an array option must be an array. + */ + public function testDefaultValueWithIsArrayMode() + { + $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY); + $option->setDefault('default'); + } + + public function testEquals() + { + $option = new InputOption('foo', 'f', null, 'Some description'); + $option2 = new InputOption('foo', 'f', null, 'Alternative description'); + $this->assertTrue($option->equals($option2)); + + $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description'); + $option2 = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description', true); + $this->assertFalse($option->equals($option2)); + + $option = new InputOption('foo', 'f', null, 'Some description'); + $option2 = new InputOption('bar', 'f', null, 'Some description'); + $this->assertFalse($option->equals($option2)); + + $option = new InputOption('foo', 'f', null, 'Some description'); + $option2 = new InputOption('foo', '', null, 'Some description'); + $this->assertFalse($option->equals($option2)); + + $option = new InputOption('foo', 'f', null, 'Some description'); + $option2 = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description'); + $this->assertFalse($option->equals($option2)); + } +} diff --git a/core/vendor/symfony/console/Tests/Input/InputTest.php b/core/vendor/symfony/console/Tests/Input/InputTest.php new file mode 100644 index 0000000..42abd82 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Input/InputTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class InputTest extends TestCase +{ + public function testConstructor() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); + $this->assertEquals('foo', $input->getArgument('name'), '->__construct() takes a InputDefinition as an argument'); + } + + public function testOptions() + { + $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name')))); + $this->assertEquals('foo', $input->getOption('name'), '->getOption() returns the value for the given option'); + + $input->setOption('name', 'bar'); + $this->assertEquals('bar', $input->getOption('name'), '->setOption() sets the value for a given option'); + $this->assertEquals(array('name' => 'bar'), $input->getOptions(), '->getOptions() returns all option values'); + + $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default')))); + $this->assertEquals('default', $input->getOption('bar'), '->getOption() returns the default value for optional options'); + $this->assertEquals(array('name' => 'foo', 'bar' => 'default'), $input->getOptions(), '->getOptions() returns all option values, even optional ones'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "foo" option does not exist. + */ + public function testSetInvalidOption() + { + $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default')))); + $input->setOption('foo', 'bar'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "foo" option does not exist. + */ + public function testGetInvalidOption() + { + $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default')))); + $input->getOption('foo'); + } + + public function testArguments() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); + $this->assertEquals('foo', $input->getArgument('name'), '->getArgument() returns the value for the given argument'); + + $input->setArgument('name', 'bar'); + $this->assertEquals('bar', $input->getArgument('name'), '->setArgument() sets the value for a given argument'); + $this->assertEquals(array('name' => 'bar'), $input->getArguments(), '->getArguments() returns all argument values'); + + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default')))); + $this->assertEquals('default', $input->getArgument('bar'), '->getArgument() returns the default value for optional arguments'); + $this->assertEquals(array('name' => 'foo', 'bar' => 'default'), $input->getArguments(), '->getArguments() returns all argument values, even optional ones'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "foo" argument does not exist. + */ + public function testSetInvalidArgument() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default')))); + $input->setArgument('foo', 'bar'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "foo" argument does not exist. + */ + public function testGetInvalidArgument() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default')))); + $input->getArgument('foo'); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Not enough arguments (missing: "name"). + */ + public function testValidateWithMissingArguments() + { + $input = new ArrayInput(array()); + $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::REQUIRED)))); + $input->validate(); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Not enough arguments (missing: "name"). + */ + public function testValidateWithMissingRequiredArguments() + { + $input = new ArrayInput(array('bar' => 'baz')); + $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::REQUIRED), new InputArgument('bar', InputArgument::OPTIONAL)))); + $input->validate(); + } + + public function testValidate() + { + $input = new ArrayInput(array('name' => 'foo')); + $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::REQUIRED)))); + + $this->assertNull($input->validate()); + } + + public function testSetGetInteractive() + { + $input = new ArrayInput(array()); + $this->assertTrue($input->isInteractive(), '->isInteractive() returns whether the input should be interactive or not'); + $input->setInteractive(false); + $this->assertFalse($input->isInteractive(), '->setInteractive() changes the interactive flag'); + } +} diff --git a/core/vendor/symfony/console/Tests/Input/StringInputTest.php b/core/vendor/symfony/console/Tests/Input/StringInputTest.php new file mode 100644 index 0000000..839af73 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Input/StringInputTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\StringInput; + +class StringInputTest extends TestCase +{ + /** + * @dataProvider getTokenizeData + */ + public function testTokenize($input, $tokens, $message) + { + $input = new StringInput($input); + $r = new \ReflectionClass('Symfony\Component\Console\Input\ArgvInput'); + $p = $r->getProperty('tokens'); + $p->setAccessible(true); + $this->assertEquals($tokens, $p->getValue($input), $message); + } + + public function testInputOptionWithGivenString() + { + $definition = new InputDefinition( + array(new InputOption('foo', null, InputOption::VALUE_REQUIRED)) + ); + + // call to bind + $input = new StringInput('--foo=bar'); + $input->bind($definition); + $this->assertEquals('bar', $input->getOption('foo')); + } + + /** + * @group legacy + */ + public function testLegacyInputOptionDefinitionInConstructor() + { + $definition = new InputDefinition( + array(new InputOption('foo', null, InputOption::VALUE_REQUIRED)) + ); + + $input = new StringInput('--foo=bar', $definition); + $this->assertEquals('bar', $input->getOption('foo')); + } + + public function getTokenizeData() + { + return array( + array('', array(), '->tokenize() parses an empty string'), + array('foo', array('foo'), '->tokenize() parses arguments'), + array(' foo bar ', array('foo', 'bar'), '->tokenize() ignores whitespaces between arguments'), + array('"quoted"', array('quoted'), '->tokenize() parses quoted arguments'), + array("'quoted'", array('quoted'), '->tokenize() parses quoted arguments'), + array("'a\rb\nc\td'", array("a\rb\nc\td"), '->tokenize() parses whitespace chars in strings'), + array("'a'\r'b'\n'c'\t'd'", array('a', 'b', 'c', 'd'), '->tokenize() parses whitespace chars between args as spaces'), + array('\"quoted\"', array('"quoted"'), '->tokenize() parses escaped-quoted arguments'), + array("\'quoted\'", array('\'quoted\''), '->tokenize() parses escaped-quoted arguments'), + array('-a', array('-a'), '->tokenize() parses short options'), + array('-azc', array('-azc'), '->tokenize() parses aggregated short options'), + array('-awithavalue', array('-awithavalue'), '->tokenize() parses short options with a value'), + array('-a"foo bar"', array('-afoo bar'), '->tokenize() parses short options with a value'), + array('-a"foo bar""foo bar"', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'), + array('-a\'foo bar\'', array('-afoo bar'), '->tokenize() parses short options with a value'), + array('-a\'foo bar\'\'foo bar\'', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'), + array('-a\'foo bar\'"foo bar"', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'), + array('--long-option', array('--long-option'), '->tokenize() parses long options'), + array('--long-option=foo', array('--long-option=foo'), '->tokenize() parses long options with a value'), + array('--long-option="foo bar"', array('--long-option=foo bar'), '->tokenize() parses long options with a value'), + array('--long-option="foo bar""another"', array('--long-option=foo baranother'), '->tokenize() parses long options with a value'), + array('--long-option=\'foo bar\'', array('--long-option=foo bar'), '->tokenize() parses long options with a value'), + array("--long-option='foo bar''another'", array('--long-option=foo baranother'), '->tokenize() parses long options with a value'), + array("--long-option='foo bar'\"another\"", array('--long-option=foo baranother'), '->tokenize() parses long options with a value'), + array('foo -a -ffoo --long bar', array('foo', '-a', '-ffoo', '--long', 'bar'), '->tokenize() parses when several arguments and options'), + ); + } + + public function testToString() + { + $input = new StringInput('-f foo'); + $this->assertEquals('-f foo', (string) $input); + + $input = new StringInput('-f --bar=foo "a b c d"'); + $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d'), (string) $input); + + $input = new StringInput('-f --bar=foo \'a b c d\' '."'A\nB\\'C'"); + $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d').' '.escapeshellarg("A\nB'C"), (string) $input); + } +} diff --git a/core/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php b/core/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php new file mode 100644 index 0000000..dac911b --- /dev/null +++ b/core/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Logger; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Tests\Fixtures\DummyOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Console logger test. + * + * @author Kévin Dunglas + * @author Jordi Boggiano + */ +class ConsoleLoggerTest extends TestCase +{ + /** + * @var DummyOutput + */ + protected $output; + + /** + * @return LoggerInterface + */ + public function getLogger() + { + $this->output = new DummyOutput(OutputInterface::VERBOSITY_VERBOSE); + + return new ConsoleLogger($this->output, array( + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, + LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL, + LogLevel::DEBUG => OutputInterface::VERBOSITY_NORMAL, + )); + } + + /** + * Return the log messages in order. + * + * @return string[] + */ + public function getLogs() + { + return $this->output->getLogs(); + } + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Symfony\Component\Console\Tests\Logger\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Symfony\Component\Console\Tests\Logger\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest()), + 'object' => new \DateTime(), + 'resource' => fopen('php://memory', 'r'), + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!', + ); + $this->assertEquals($expected, $this->getLogs()); + } +} + +class DummyTest +{ + public function __toString() + { + } +} diff --git a/core/vendor/symfony/console/Tests/Output/ConsoleOutputTest.php b/core/vendor/symfony/console/Tests/Output/ConsoleOutputTest.php new file mode 100644 index 0000000..db39a02 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Output/ConsoleOutputTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Output; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\Output; + +class ConsoleOutputTest extends TestCase +{ + public function testConstructor() + { + $output = new ConsoleOutput(Output::VERBOSITY_QUIET, true); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument'); + $this->assertSame($output->getFormatter(), $output->getErrorOutput()->getFormatter(), '__construct() takes a formatter or null as the third argument'); + } + + public function testSetFormatter() + { + $output = new ConsoleOutput(); + $outputFormatter = new OutputFormatter(); + $output->setFormatter($outputFormatter); + $this->assertSame($outputFormatter, $output->getFormatter()); + } + + public function testSetVerbosity() + { + $output = new ConsoleOutput(); + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + $this->assertSame(Output::VERBOSITY_VERBOSE, $output->getVerbosity()); + } +} diff --git a/core/vendor/symfony/console/Tests/Output/NullOutputTest.php b/core/vendor/symfony/console/Tests/Output/NullOutputTest.php new file mode 100644 index 0000000..b7ff4be --- /dev/null +++ b/core/vendor/symfony/console/Tests/Output/NullOutputTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Output; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Output\OutputInterface; + +class NullOutputTest extends TestCase +{ + public function testConstructor() + { + $output = new NullOutput(); + + ob_start(); + $output->write('foo'); + $buffer = ob_get_clean(); + + $this->assertSame('', $buffer, '->write() does nothing (at least nothing is printed)'); + $this->assertFalse($output->isDecorated(), '->isDecorated() returns false'); + } + + public function testVerbosity() + { + $output = new NullOutput(); + $this->assertSame(OutputInterface::VERBOSITY_QUIET, $output->getVerbosity(), '->getVerbosity() returns VERBOSITY_QUIET for NullOutput by default'); + + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + $this->assertSame(OutputInterface::VERBOSITY_QUIET, $output->getVerbosity(), '->getVerbosity() always returns VERBOSITY_QUIET for NullOutput'); + } + + public function testSetFormatter() + { + $output = new NullOutput(); + $outputFormatter = new OutputFormatter(); + $output->setFormatter($outputFormatter); + $this->assertNotSame($outputFormatter, $output->getFormatter()); + } + + public function testSetVerbosity() + { + $output = new NullOutput(); + $output->setVerbosity(Output::VERBOSITY_NORMAL); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity()); + } + + public function testSetDecorated() + { + $output = new NullOutput(); + $output->setDecorated(true); + $this->assertFalse($output->isDecorated()); + } + + public function testIsQuiet() + { + $output = new NullOutput(); + $this->assertTrue($output->isQuiet()); + } + + public function testIsVerbose() + { + $output = new NullOutput(); + $this->assertFalse($output->isVerbose()); + } + + public function testIsVeryVerbose() + { + $output = new NullOutput(); + $this->assertFalse($output->isVeryVerbose()); + } + + public function testIsDebug() + { + $output = new NullOutput(); + $this->assertFalse($output->isDebug()); + } +} diff --git a/core/vendor/symfony/console/Tests/Output/OutputTest.php b/core/vendor/symfony/console/Tests/Output/OutputTest.php new file mode 100644 index 0000000..f122c07 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Output/OutputTest.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Output; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +class OutputTest extends TestCase +{ + public function testConstructor() + { + $output = new TestOutput(Output::VERBOSITY_QUIET, true); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument'); + $this->assertTrue($output->isDecorated(), '__construct() takes the decorated flag as its second argument'); + } + + public function testSetIsDecorated() + { + $output = new TestOutput(); + $output->setDecorated(true); + $this->assertTrue($output->isDecorated(), 'setDecorated() sets the decorated flag'); + } + + public function testSetGetVerbosity() + { + $output = new TestOutput(); + $output->setVerbosity(Output::VERBOSITY_QUIET); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '->setVerbosity() sets the verbosity'); + + $this->assertTrue($output->isQuiet()); + $this->assertFalse($output->isVerbose()); + $this->assertFalse($output->isVeryVerbose()); + $this->assertFalse($output->isDebug()); + + $output->setVerbosity(Output::VERBOSITY_NORMAL); + $this->assertFalse($output->isQuiet()); + $this->assertFalse($output->isVerbose()); + $this->assertFalse($output->isVeryVerbose()); + $this->assertFalse($output->isDebug()); + + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + $this->assertFalse($output->isQuiet()); + $this->assertTrue($output->isVerbose()); + $this->assertFalse($output->isVeryVerbose()); + $this->assertFalse($output->isDebug()); + + $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); + $this->assertFalse($output->isQuiet()); + $this->assertTrue($output->isVerbose()); + $this->assertTrue($output->isVeryVerbose()); + $this->assertFalse($output->isDebug()); + + $output->setVerbosity(Output::VERBOSITY_DEBUG); + $this->assertFalse($output->isQuiet()); + $this->assertTrue($output->isVerbose()); + $this->assertTrue($output->isVeryVerbose()); + $this->assertTrue($output->isDebug()); + } + + public function testWriteWithVerbosityQuiet() + { + $output = new TestOutput(Output::VERBOSITY_QUIET); + $output->writeln('foo'); + $this->assertEquals('', $output->output, '->writeln() outputs nothing if verbosity is set to VERBOSITY_QUIET'); + } + + public function testWriteAnArrayOfMessages() + { + $output = new TestOutput(); + $output->writeln(array('foo', 'bar')); + $this->assertEquals("foo\nbar\n", $output->output, '->writeln() can take an array of messages to output'); + } + + /** + * @dataProvider provideWriteArguments + */ + public function testWriteRawMessage($message, $type, $expectedOutput) + { + $output = new TestOutput(); + $output->writeln($message, $type); + $this->assertEquals($expectedOutput, $output->output); + } + + public function provideWriteArguments() + { + return array( + array('foo', Output::OUTPUT_RAW, "foo\n"), + array('foo', Output::OUTPUT_PLAIN, "foo\n"), + ); + } + + public function testWriteWithDecorationTurnedOff() + { + $output = new TestOutput(); + $output->setDecorated(false); + $output->writeln('foo'); + $this->assertEquals("foo\n", $output->output, '->writeln() strips decoration tags if decoration is set to false'); + } + + public function testWriteDecoratedMessage() + { + $fooStyle = new OutputFormatterStyle('yellow', 'red', array('blink')); + $output = new TestOutput(); + $output->getFormatter()->setStyle('FOO', $fooStyle); + $output->setDecorated(true); + $output->writeln('foo'); + $this->assertEquals("\033[33;41;5mfoo\033[39;49;25m\n", $output->output, '->writeln() decorates the output'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unknown output type given (24) + */ + public function testWriteWithInvalidOutputType() + { + $output = new TestOutput(); + $output->writeln('foo', 24); + } + + public function testWriteWithInvalidStyle() + { + $output = new TestOutput(); + + $output->clear(); + $output->write('foo'); + $this->assertEquals('foo', $output->output, '->write() do nothing when a style does not exist'); + + $output->clear(); + $output->writeln('foo'); + $this->assertEquals("foo\n", $output->output, '->writeln() do nothing when a style does not exist'); + } +} + +class TestOutput extends Output +{ + public $output = ''; + + public function clear() + { + $this->output = ''; + } + + protected function doWrite($message, $newline) + { + $this->output .= $message.($newline ? "\n" : ''); + } +} diff --git a/core/vendor/symfony/console/Tests/Output/StreamOutputTest.php b/core/vendor/symfony/console/Tests/Output/StreamOutputTest.php new file mode 100644 index 0000000..780b568 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Output/StreamOutputTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Output; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Output\StreamOutput; + +class StreamOutputTest extends TestCase +{ + protected $stream; + + protected function setUp() + { + $this->stream = fopen('php://memory', 'a', false); + } + + protected function tearDown() + { + $this->stream = null; + } + + public function testConstructor() + { + $output = new StreamOutput($this->stream, Output::VERBOSITY_QUIET, true); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument'); + $this->assertTrue($output->isDecorated(), '__construct() takes the decorated flag as its second argument'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The StreamOutput class needs a stream as its first argument. + */ + public function testStreamIsRequired() + { + new StreamOutput('foo'); + } + + public function testGetStream() + { + $output = new StreamOutput($this->stream); + $this->assertEquals($this->stream, $output->getStream(), '->getStream() returns the current stream'); + } + + public function testDoWrite() + { + $output = new StreamOutput($this->stream); + $output->writeln('foo'); + rewind($output->getStream()); + $this->assertEquals('foo'.PHP_EOL, stream_get_contents($output->getStream()), '->doWrite() writes to the stream'); + } +} diff --git a/core/vendor/symfony/console/Tests/Style/SymfonyStyleTest.php b/core/vendor/symfony/console/Tests/Style/SymfonyStyleTest.php new file mode 100644 index 0000000..ee9b09f --- /dev/null +++ b/core/vendor/symfony/console/Tests/Style/SymfonyStyleTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Style; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Console\Tester\CommandTester; + +class SymfonyStyleTest extends TestCase +{ + /** @var Command */ + protected $command; + /** @var CommandTester */ + protected $tester; + + protected function setUp() + { + $this->command = new Command('sfstyle'); + $this->tester = new CommandTester($this->command); + } + + protected function tearDown() + { + $this->command = null; + $this->tester = null; + } + + /** + * @dataProvider inputCommandToOutputFilesProvider + */ + public function testOutputs($inputCommandFilepath, $outputFilepath) + { + $code = require $inputCommandFilepath; + $this->command->setCode($code); + $this->tester->execute(array(), array('interactive' => false, 'decorated' => false)); + $this->assertStringEqualsFile($outputFilepath, $this->tester->getDisplay(true)); + } + + public function inputCommandToOutputFilesProvider() + { + $baseDir = __DIR__.'/../Fixtures/Style/SymfonyStyle'; + + return array_map(null, glob($baseDir.'/command/command_*.php'), glob($baseDir.'/output/output_*.txt')); + } +} + +/** + * Use this class in tests to force the line length + * and ensure a consistent output for expectations. + */ +class SymfonyStyleWithForcedLineLength extends SymfonyStyle +{ + public function __construct(InputInterface $input, OutputInterface $output) + { + parent::__construct($input, $output); + + $ref = new \ReflectionProperty(get_parent_class($this), 'lineLength'); + $ref->setAccessible(true); + $ref->setValue($this, 120); + } +} diff --git a/core/vendor/symfony/console/Tests/Tester/ApplicationTesterTest.php b/core/vendor/symfony/console/Tests/Tester/ApplicationTesterTest.php new file mode 100644 index 0000000..57e7136 --- /dev/null +++ b/core/vendor/symfony/console/Tests/Tester/ApplicationTesterTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Tester; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Tester\ApplicationTester; + +class ApplicationTesterTest extends TestCase +{ + protected $application; + protected $tester; + + protected function setUp() + { + $this->application = new Application(); + $this->application->setAutoExit(false); + $this->application->register('foo') + ->addArgument('foo') + ->setCode(function ($input, $output) { $output->writeln('foo'); }) + ; + + $this->tester = new ApplicationTester($this->application); + $this->tester->run(array('command' => 'foo', 'foo' => 'bar'), array('interactive' => false, 'decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE)); + } + + protected function tearDown() + { + $this->application = null; + $this->tester = null; + } + + public function testRun() + { + $this->assertFalse($this->tester->getInput()->isInteractive(), '->execute() takes an interactive option'); + $this->assertFalse($this->tester->getOutput()->isDecorated(), '->execute() takes a decorated option'); + $this->assertEquals(Output::VERBOSITY_VERBOSE, $this->tester->getOutput()->getVerbosity(), '->execute() takes a verbosity option'); + } + + public function testGetInput() + { + $this->assertEquals('bar', $this->tester->getInput()->getArgument('foo'), '->getInput() returns the current input instance'); + } + + public function testGetOutput() + { + rewind($this->tester->getOutput()->getStream()); + $this->assertEquals('foo'.PHP_EOL, stream_get_contents($this->tester->getOutput()->getStream()), '->getOutput() returns the current output instance'); + } + + public function testGetDisplay() + { + $this->assertEquals('foo'.PHP_EOL, $this->tester->getDisplay(), '->getDisplay() returns the display of the last execution'); + } + + public function testGetStatusCode() + { + $this->assertSame(0, $this->tester->getStatusCode(), '->getStatusCode() returns the status code'); + } +} diff --git a/core/vendor/symfony/console/Tests/Tester/CommandTesterTest.php b/core/vendor/symfony/console/Tests/Tester/CommandTesterTest.php new file mode 100644 index 0000000..8d4e05a --- /dev/null +++ b/core/vendor/symfony/console/Tests/Tester/CommandTesterTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Tester; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Tester\CommandTester; + +class CommandTesterTest extends TestCase +{ + protected $command; + protected $tester; + + protected function setUp() + { + $this->command = new Command('foo'); + $this->command->addArgument('command'); + $this->command->addArgument('foo'); + $this->command->setCode(function ($input, $output) { $output->writeln('foo'); }); + + $this->tester = new CommandTester($this->command); + $this->tester->execute(array('foo' => 'bar'), array('interactive' => false, 'decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE)); + } + + protected function tearDown() + { + $this->command = null; + $this->tester = null; + } + + public function testExecute() + { + $this->assertFalse($this->tester->getInput()->isInteractive(), '->execute() takes an interactive option'); + $this->assertFalse($this->tester->getOutput()->isDecorated(), '->execute() takes a decorated option'); + $this->assertEquals(Output::VERBOSITY_VERBOSE, $this->tester->getOutput()->getVerbosity(), '->execute() takes a verbosity option'); + } + + public function testGetInput() + { + $this->assertEquals('bar', $this->tester->getInput()->getArgument('foo'), '->getInput() returns the current input instance'); + } + + public function testGetOutput() + { + rewind($this->tester->getOutput()->getStream()); + $this->assertEquals('foo'.PHP_EOL, stream_get_contents($this->tester->getOutput()->getStream()), '->getOutput() returns the current output instance'); + } + + public function testGetDisplay() + { + $this->assertEquals('foo'.PHP_EOL, $this->tester->getDisplay(), '->getDisplay() returns the display of the last execution'); + } + + public function testGetStatusCode() + { + $this->assertSame(0, $this->tester->getStatusCode(), '->getStatusCode() returns the status code'); + } + + public function testCommandFromApplication() + { + $application = new Application(); + $application->setAutoExit(false); + + $command = new Command('foo'); + $command->setCode(function ($input, $output) { $output->writeln('foo'); }); + + $application->add($command); + + $tester = new CommandTester($application->find('foo')); + + // check that there is no need to pass the command name here + $this->assertEquals(0, $tester->execute(array())); + } +} diff --git a/core/vendor/symfony/console/composer.json b/core/vendor/symfony/console/composer.json new file mode 100644 index 0000000..9f63559 --- /dev/null +++ b/core/vendor/symfony/console/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/console", + "type": "library", + "description": "Symfony Console Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2" + }, + "require-dev": { + "symfony/event-dispatcher": "~2.1", + "symfony/process": "~2.1", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/event-dispatcher": "", + "symfony/process": "", + "psr/log-implementation": "For using the console logger" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Console\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + } +} diff --git a/core/vendor/symfony/console/phpunit.xml.dist b/core/vendor/symfony/console/phpunit.xml.dist new file mode 100644 index 0000000..7e7ff5b --- /dev/null +++ b/core/vendor/symfony/console/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/core/vendor/symfony/css-selector/.gitignore b/core/vendor/symfony/css-selector/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/core/vendor/symfony/css-selector/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/core/vendor/symfony/css-selector/CHANGELOG.md b/core/vendor/symfony/css-selector/CHANGELOG.md new file mode 100644 index 0000000..4061ff2 --- /dev/null +++ b/core/vendor/symfony/css-selector/CHANGELOG.md @@ -0,0 +1,13 @@ +CHANGELOG +========= + +2.8.0 +----- + + * Added the `CssSelectorConverter` class as a non-static API for the component. + * Deprecated the `CssSelector` static API of the component. + +2.1.0 +----- + + * none diff --git a/core/vendor/symfony/css-selector/CssSelector.php b/core/vendor/symfony/css-selector/CssSelector.php new file mode 100644 index 0000000..c1f8c88 --- /dev/null +++ b/core/vendor/symfony/css-selector/CssSelector.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector; + +@trigger_error('The '.__NAMESPACE__.'\CssSelector class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the \Symfony\Component\CssSelector\CssSelectorConverter class instead.', E_USER_DEPRECATED); + +/** + * CssSelector is the main entry point of the component and can convert CSS + * selectors to XPath expressions. + * + * $xpath = CssSelector::toXpath('h1.foo'); + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * Copyright (c) 2007-2012 Ian Bicking and contributors. See AUTHORS + * for more details. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. Neither the name of Ian Bicking 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 IAN BICKING 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 Fabien Potencier + * + * @deprecated as of 2.8, will be removed in 3.0. Use the \Symfony\Component\CssSelector\CssSelectorConverter class instead. + */ +class CssSelector +{ + private static $html = true; + + /** + * Translates a CSS expression to its XPath equivalent. + * Optionally, a prefix can be added to the resulting XPath + * expression with the $prefix parameter. + * + * @param mixed $cssExpr The CSS expression + * @param string $prefix An optional prefix for the XPath expression + * + * @return string + */ + public static function toXPath($cssExpr, $prefix = 'descendant-or-self::') + { + $converter = new CssSelectorConverter(self::$html); + + return $converter->toXPath($cssExpr, $prefix); + } + + /** + * Enables the HTML extension. + */ + public static function enableHtmlExtension() + { + self::$html = true; + } + + /** + * Disables the HTML extension. + */ + public static function disableHtmlExtension() + { + self::$html = false; + } +} diff --git a/core/vendor/symfony/css-selector/CssSelectorConverter.php b/core/vendor/symfony/css-selector/CssSelectorConverter.php new file mode 100644 index 0000000..8d66dbd --- /dev/null +++ b/core/vendor/symfony/css-selector/CssSelectorConverter.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector; + +use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser; +use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser; +use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser; +use Symfony\Component\CssSelector\Parser\Shortcut\HashParser; +use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension; +use Symfony\Component\CssSelector\XPath\Translator; + +/** + * CssSelectorConverter is the main entry point of the component and can convert CSS + * selectors to XPath expressions. + * + * @author Christophe Coevoet + */ +class CssSelectorConverter +{ + private $translator; + + /** + * @param bool $html Whether HTML support should be enabled. Disable it for XML documents + */ + public function __construct($html = true) + { + $this->translator = new Translator(); + + if ($html) { + $this->translator->registerExtension(new HtmlExtension($this->translator)); + } + + $this->translator + ->registerParserShortcut(new EmptyStringParser()) + ->registerParserShortcut(new ElementParser()) + ->registerParserShortcut(new ClassParser()) + ->registerParserShortcut(new HashParser()) + ; + } + + /** + * Translates a CSS expression to its XPath equivalent. + * + * Optionally, a prefix can be added to the resulting XPath + * expression with the $prefix parameter. + * + * @param string $cssExpr The CSS expression + * @param string $prefix An optional prefix for the XPath expression + * + * @return string + */ + public function toXPath($cssExpr, $prefix = 'descendant-or-self::') + { + return $this->translator->cssToXPath($cssExpr, $prefix); + } +} diff --git a/core/vendor/symfony/css-selector/Exception/ExceptionInterface.php b/core/vendor/symfony/css-selector/Exception/ExceptionInterface.php new file mode 100644 index 0000000..e4c5ae1 --- /dev/null +++ b/core/vendor/symfony/css-selector/Exception/ExceptionInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +/** + * Interface for exceptions. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +interface ExceptionInterface +{ +} diff --git a/core/vendor/symfony/css-selector/Exception/ExpressionErrorException.php b/core/vendor/symfony/css-selector/Exception/ExpressionErrorException.php new file mode 100644 index 0000000..fd5deea --- /dev/null +++ b/core/vendor/symfony/css-selector/Exception/ExpressionErrorException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class ExpressionErrorException extends ParseException +{ +} diff --git a/core/vendor/symfony/css-selector/Exception/InternalErrorException.php b/core/vendor/symfony/css-selector/Exception/InternalErrorException.php new file mode 100644 index 0000000..e60e5ed --- /dev/null +++ b/core/vendor/symfony/css-selector/Exception/InternalErrorException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class InternalErrorException extends ParseException +{ +} diff --git a/core/vendor/symfony/css-selector/Exception/ParseException.php b/core/vendor/symfony/css-selector/Exception/ParseException.php new file mode 100644 index 0000000..3b0b0ee --- /dev/null +++ b/core/vendor/symfony/css-selector/Exception/ParseException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Fabien Potencier + */ +class ParseException extends \Exception implements ExceptionInterface +{ +} diff --git a/core/vendor/symfony/css-selector/Exception/SyntaxErrorException.php b/core/vendor/symfony/css-selector/Exception/SyntaxErrorException.php new file mode 100644 index 0000000..cb3158a --- /dev/null +++ b/core/vendor/symfony/css-selector/Exception/SyntaxErrorException.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +use Symfony\Component\CssSelector\Parser\Token; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class SyntaxErrorException extends ParseException +{ + /** + * @param string $expectedValue + * @param Token $foundToken + * + * @return self + */ + public static function unexpectedToken($expectedValue, Token $foundToken) + { + return new self(sprintf('Expected %s, but %s found.', $expectedValue, $foundToken)); + } + + /** + * @param string $pseudoElement + * @param string $unexpectedLocation + * + * @return self + */ + public static function pseudoElementFound($pseudoElement, $unexpectedLocation) + { + return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation)); + } + + /** + * @param int $position + * + * @return self + */ + public static function unclosedString($position) + { + return new self(sprintf('Unclosed/invalid string at %s.', $position)); + } + + /** + * @return self + */ + public static function nestedNot() + { + return new self('Got nested ::not().'); + } + + /** + * @return self + */ + public static function stringAsFunctionArgument() + { + return new self('String not allowed as function argument.'); + } +} diff --git a/core/vendor/symfony/css-selector/LICENSE b/core/vendor/symfony/css-selector/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/core/vendor/symfony/css-selector/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 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/core/vendor/symfony/css-selector/Node/AbstractNode.php b/core/vendor/symfony/css-selector/Node/AbstractNode.php new file mode 100644 index 0000000..1d5d8ff --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/AbstractNode.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Abstract base node class. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +abstract class AbstractNode implements NodeInterface +{ + /** + * @var string + */ + private $nodeName; + + /** + * @return string + */ + public function getNodeName() + { + if (null === $this->nodeName) { + $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', \get_called_class()); + } + + return $this->nodeName; + } +} diff --git a/core/vendor/symfony/css-selector/Node/AttributeNode.php b/core/vendor/symfony/css-selector/Node/AttributeNode.php new file mode 100644 index 0000000..1caccb6 --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/AttributeNode.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "[| ]" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class AttributeNode extends AbstractNode +{ + private $selector; + private $namespace; + private $attribute; + private $operator; + private $value; + + /** + * @param NodeInterface $selector + * @param string $namespace + * @param string $attribute + * @param string $operator + * @param string $value + */ + public function __construct(NodeInterface $selector, $namespace, $attribute, $operator, $value) + { + $this->selector = $selector; + $this->namespace = $namespace; + $this->attribute = $attribute; + $this->operator = $operator; + $this->value = $value; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return string + */ + public function getAttribute() + { + return $this->attribute; + } + + /** + * @return string + */ + public function getOperator() + { + return $this->operator; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute; + + return 'exists' === $this->operator + ? sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute) + : sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value); + } +} diff --git a/core/vendor/symfony/css-selector/Node/ClassNode.php b/core/vendor/symfony/css-selector/Node/ClassNode.php new file mode 100644 index 0000000..69462e8 --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/ClassNode.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "." node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class ClassNode extends AbstractNode +{ + private $selector; + private $name; + + /** + * @param NodeInterface $selector + * @param string $name + */ + public function __construct(NodeInterface $selector, $name) + { + $this->selector = $selector; + $this->name = $name; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name); + } +} diff --git a/core/vendor/symfony/css-selector/Node/CombinedSelectorNode.php b/core/vendor/symfony/css-selector/Node/CombinedSelectorNode.php new file mode 100644 index 0000000..2aa583a --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/CombinedSelectorNode.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a combined node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class CombinedSelectorNode extends AbstractNode +{ + private $selector; + private $combinator; + private $subSelector; + + /** + * @param NodeInterface $selector + * @param string $combinator + * @param NodeInterface $subSelector + */ + public function __construct(NodeInterface $selector, $combinator, NodeInterface $subSelector) + { + $this->selector = $selector; + $this->combinator = $combinator; + $this->subSelector = $subSelector; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getCombinator() + { + return $this->combinator; + } + + /** + * @return NodeInterface + */ + public function getSubSelector() + { + return $this->subSelector; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $combinator = ' ' === $this->combinator ? '' : $this->combinator; + + return sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector); + } +} diff --git a/core/vendor/symfony/css-selector/Node/ElementNode.php b/core/vendor/symfony/css-selector/Node/ElementNode.php new file mode 100644 index 0000000..54869af --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/ElementNode.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "|" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class ElementNode extends AbstractNode +{ + private $namespace; + private $element; + + /** + * @param string|null $namespace + * @param string|null $element + */ + public function __construct($namespace = null, $element = null) + { + $this->namespace = $namespace; + $this->element = $element; + } + + /** + * @return string|null + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return string|null + */ + public function getElement() + { + return $this->element; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return new Specificity(0, 0, $this->element ? 1 : 0); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $element = $this->element ?: '*'; + + return sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element); + } +} diff --git a/core/vendor/symfony/css-selector/Node/FunctionNode.php b/core/vendor/symfony/css-selector/Node/FunctionNode.php new file mode 100644 index 0000000..5026825 --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/FunctionNode.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +use Symfony\Component\CssSelector\Parser\Token; + +/** + * Represents a ":()" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class FunctionNode extends AbstractNode +{ + private $selector; + private $name; + private $arguments; + + /** + * @param NodeInterface $selector + * @param string $name + * @param Token[] $arguments + */ + public function __construct(NodeInterface $selector, $name, array $arguments = array()) + { + $this->selector = $selector; + $this->name = strtolower($name); + $this->arguments = $arguments; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return Token[] + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $arguments = implode(', ', array_map(function (Token $token) { + return "'".$token->getValue()."'"; + }, $this->arguments)); + + return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); + } +} diff --git a/core/vendor/symfony/css-selector/Node/HashNode.php b/core/vendor/symfony/css-selector/Node/HashNode.php new file mode 100644 index 0000000..ebf9a98 --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/HashNode.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "#" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class HashNode extends AbstractNode +{ + private $selector; + private $id; + + /** + * @param NodeInterface $selector + * @param string $id + */ + public function __construct(NodeInterface $selector, $id) + { + $this->selector = $selector; + $this->id = $id; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id); + } +} diff --git a/core/vendor/symfony/css-selector/Node/NegationNode.php b/core/vendor/symfony/css-selector/Node/NegationNode.php new file mode 100644 index 0000000..bf97cae --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/NegationNode.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a ":not()" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class NegationNode extends AbstractNode +{ + private $selector; + private $subSelector; + + public function __construct(NodeInterface $selector, NodeInterface $subSelector) + { + $this->selector = $selector; + $this->subSelector = $subSelector; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return NodeInterface + */ + public function getSubSelector() + { + return $this->subSelector; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector); + } +} diff --git a/core/vendor/symfony/css-selector/Node/NodeInterface.php b/core/vendor/symfony/css-selector/Node/NodeInterface.php new file mode 100644 index 0000000..d919e20 --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/NodeInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Interface for nodes. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +interface NodeInterface +{ + /** + * Returns node's name. + * + * @return string + */ + public function getNodeName(); + + /** + * Returns node's specificity. + * + * @return Specificity + */ + public function getSpecificity(); + + /** + * Returns node's string representation. + * + * @return string + */ + public function __toString(); +} diff --git a/core/vendor/symfony/css-selector/Node/PseudoNode.php b/core/vendor/symfony/css-selector/Node/PseudoNode.php new file mode 100644 index 0000000..3842c69 --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/PseudoNode.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a ":" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class PseudoNode extends AbstractNode +{ + private $selector; + private $identifier; + + /** + * @param NodeInterface $selector + * @param string $identifier + */ + public function __construct(NodeInterface $selector, $identifier) + { + $this->selector = $selector; + $this->identifier = strtolower($identifier); + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier); + } +} diff --git a/core/vendor/symfony/css-selector/Node/SelectorNode.php b/core/vendor/symfony/css-selector/Node/SelectorNode.php new file mode 100644 index 0000000..057107f --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/SelectorNode.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "(::|:)" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class SelectorNode extends AbstractNode +{ + private $tree; + private $pseudoElement; + + /** + * @param NodeInterface $tree + * @param string|null $pseudoElement + */ + public function __construct(NodeInterface $tree, $pseudoElement = null) + { + $this->tree = $tree; + $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; + } + + /** + * @return NodeInterface + */ + public function getTree() + { + return $this->tree; + } + + /** + * @return string|null + */ + public function getPseudoElement() + { + return $this->pseudoElement; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : ''); + } +} diff --git a/core/vendor/symfony/css-selector/Node/Specificity.php b/core/vendor/symfony/css-selector/Node/Specificity.php new file mode 100644 index 0000000..6aa70d7 --- /dev/null +++ b/core/vendor/symfony/css-selector/Node/Specificity.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a node specificity. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @see http://www.w3.org/TR/selectors/#specificity + * + * @author Jean-François Simon + * + * @internal + */ +class Specificity +{ + const A_FACTOR = 100; + const B_FACTOR = 10; + const C_FACTOR = 1; + + private $a; + private $b; + private $c; + + /** + * @param int $a + * @param int $b + * @param int $c + */ + public function __construct($a, $b, $c) + { + $this->a = $a; + $this->b = $b; + $this->c = $c; + } + + /** + * @return self + */ + public function plus(Specificity $specificity) + { + return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c); + } + + /** + * Returns global specificity value. + * + * @return int + */ + public function getValue() + { + return $this->a * self::A_FACTOR + $this->b * self::B_FACTOR + $this->c * self::C_FACTOR; + } + + /** + * Returns -1 if the object specificity is lower than the argument, + * 0 if they are equal, and 1 if the argument is lower. + * + * @return int + */ + public function compareTo(Specificity $specificity) + { + if ($this->a !== $specificity->a) { + return $this->a > $specificity->a ? 1 : -1; + } + + if ($this->b !== $specificity->b) { + return $this->b > $specificity->b ? 1 : -1; + } + + if ($this->c !== $specificity->c) { + return $this->c > $specificity->c ? 1 : -1; + } + + return 0; + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Handler/CommentHandler.php b/core/vendor/symfony/css-selector/Parser/Handler/CommentHandler.php new file mode 100644 index 0000000..a29775c --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Handler/CommentHandler.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class CommentHandler implements HandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + if ('/*' !== $reader->getSubstring(2)) { + return false; + } + + $offset = $reader->getOffset('*/'); + + if (false === $offset) { + $reader->moveToEnd(); + } else { + $reader->moveForward($offset + 2); + } + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Handler/HandlerInterface.php b/core/vendor/symfony/css-selector/Parser/Handler/HandlerInterface.php new file mode 100644 index 0000000..de931f6 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Handler/HandlerInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector handler interface. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +interface HandlerInterface +{ + /** + * @return bool + */ + public function handle(Reader $reader, TokenStream $stream); +} diff --git a/core/vendor/symfony/css-selector/Parser/Handler/HashHandler.php b/core/vendor/symfony/css-selector/Parser/Handler/HashHandler.php new file mode 100644 index 0000000..e451328 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Handler/HashHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class HashHandler implements HandlerInterface +{ + private $patterns; + private $escaping; + + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getHashPattern()); + + if (!$match) { + return false; + } + + $value = $this->escaping->escapeUnicode($match[1]); + $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition())); + $reader->moveForward(\strlen($match[0])); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php b/core/vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php new file mode 100644 index 0000000..1591fcb --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class IdentifierHandler implements HandlerInterface +{ + private $patterns; + private $escaping; + + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getIdentifierPattern()); + + if (!$match) { + return false; + } + + $value = $this->escaping->escapeUnicode($match[0]); + $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition())); + $reader->moveForward(\strlen($match[0])); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Handler/NumberHandler.php b/core/vendor/symfony/css-selector/Parser/Handler/NumberHandler.php new file mode 100644 index 0000000..5955903 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Handler/NumberHandler.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class NumberHandler implements HandlerInterface +{ + private $patterns; + + public function __construct(TokenizerPatterns $patterns) + { + $this->patterns = $patterns; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getNumberPattern()); + + if (!$match) { + return false; + } + + $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition())); + $reader->moveForward(\strlen($match[0])); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Handler/StringHandler.php b/core/vendor/symfony/css-selector/Parser/Handler/StringHandler.php new file mode 100644 index 0000000..00155b0 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Handler/StringHandler.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Exception\InternalErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class StringHandler implements HandlerInterface +{ + private $patterns; + private $escaping; + + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $quote = $reader->getSubstring(1); + + if (!\in_array($quote, array("'", '"'))) { + return false; + } + + $reader->moveForward(1); + $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote)); + + if (!$match) { + throw new InternalErrorException(sprintf('Should have found at least an empty match at %s.', $reader->getPosition())); + } + + // check unclosed strings + if (\strlen($match[0]) === $reader->getRemainingLength()) { + throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); + } + + // check quotes pairs validity + if ($quote !== $reader->getSubstring(1, \strlen($match[0]))) { + throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); + } + + $string = $this->escaping->escapeUnicodeAndNewLine($match[0]); + $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition())); + $reader->moveForward(\strlen($match[0]) + 1); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.php b/core/vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.php new file mode 100644 index 0000000..396467a --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector whitespace handler. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class WhitespaceHandler implements HandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern('~^[ \t\r\n\f]+~'); + + if (false === $match) { + return false; + } + + $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition())); + $reader->moveForward(\strlen($match[0])); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Parser.php b/core/vendor/symfony/css-selector/Parser/Parser.php new file mode 100644 index 0000000..87c05a7 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Parser.php @@ -0,0 +1,384 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node; +use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer; + +/** + * CSS selector parser. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class Parser implements ParserInterface +{ + private $tokenizer; + + public function __construct(Tokenizer $tokenizer = null) + { + $this->tokenizer = $tokenizer ?: new Tokenizer(); + } + + /** + * {@inheritdoc} + */ + public function parse($source) + { + $reader = new Reader($source); + $stream = $this->tokenizer->tokenize($reader); + + return $this->parseSelectorList($stream); + } + + /** + * Parses the arguments for ":nth-child()" and friends. + * + * @param Token[] $tokens + * + * @return array + * + * @throws SyntaxErrorException + */ + public static function parseSeries(array $tokens) + { + foreach ($tokens as $token) { + if ($token->isString()) { + throw SyntaxErrorException::stringAsFunctionArgument(); + } + } + + $joined = trim(implode('', array_map(function (Token $token) { + return $token->getValue(); + }, $tokens))); + + $int = function ($string) { + if (!is_numeric($string)) { + throw SyntaxErrorException::stringAsFunctionArgument(); + } + + return (int) $string; + }; + + switch (true) { + case 'odd' === $joined: + return array(2, 1); + case 'even' === $joined: + return array(2, 0); + case 'n' === $joined: + return array(1, 0); + case false === strpos($joined, 'n'): + return array(0, $int($joined)); + } + + $split = explode('n', $joined); + $first = isset($split[0]) ? $split[0] : null; + + return array( + $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1, + isset($split[1]) && $split[1] ? $int($split[1]) : 0, + ); + } + + /** + * Parses selector nodes. + * + * @return array + */ + private function parseSelectorList(TokenStream $stream) + { + $stream->skipWhitespace(); + $selectors = array(); + + while (true) { + $selectors[] = $this->parserSelectorNode($stream); + + if ($stream->getPeek()->isDelimiter(array(','))) { + $stream->getNext(); + $stream->skipWhitespace(); + } else { + break; + } + } + + return $selectors; + } + + /** + * Parses next selector or combined node. + * + * @return Node\SelectorNode + * + * @throws SyntaxErrorException + */ + private function parserSelectorNode(TokenStream $stream) + { + list($result, $pseudoElement) = $this->parseSimpleSelector($stream); + + while (true) { + $stream->skipWhitespace(); + $peek = $stream->getPeek(); + + if ($peek->isFileEnd() || $peek->isDelimiter(array(','))) { + break; + } + + if (null !== $pseudoElement) { + throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); + } + + if ($peek->isDelimiter(array('+', '>', '~'))) { + $combinator = $stream->getNext()->getValue(); + $stream->skipWhitespace(); + } else { + $combinator = ' '; + } + + list($nextSelector, $pseudoElement) = $this->parseSimpleSelector($stream); + $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector); + } + + return new Node\SelectorNode($result, $pseudoElement); + } + + /** + * Parses next simple node (hash, class, pseudo, negation). + * + * @param TokenStream $stream + * @param bool $insideNegation + * + * @return array + * + * @throws SyntaxErrorException + */ + private function parseSimpleSelector(TokenStream $stream, $insideNegation = false) + { + $stream->skipWhitespace(); + + $selectorStart = \count($stream->getUsed()); + $result = $this->parseElementNode($stream); + $pseudoElement = null; + + while (true) { + $peek = $stream->getPeek(); + if ($peek->isWhitespace() + || $peek->isFileEnd() + || $peek->isDelimiter(array(',', '+', '>', '~')) + || ($insideNegation && $peek->isDelimiter(array(')'))) + ) { + break; + } + + if (null !== $pseudoElement) { + throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); + } + + if ($peek->isHash()) { + $result = new Node\HashNode($result, $stream->getNext()->getValue()); + } elseif ($peek->isDelimiter(array('.'))) { + $stream->getNext(); + $result = new Node\ClassNode($result, $stream->getNextIdentifier()); + } elseif ($peek->isDelimiter(array('['))) { + $stream->getNext(); + $result = $this->parseAttributeNode($result, $stream); + } elseif ($peek->isDelimiter(array(':'))) { + $stream->getNext(); + + if ($stream->getPeek()->isDelimiter(array(':'))) { + $stream->getNext(); + $pseudoElement = $stream->getNextIdentifier(); + + continue; + } + + $identifier = $stream->getNextIdentifier(); + if (\in_array(strtolower($identifier), array('first-line', 'first-letter', 'before', 'after'))) { + // Special case: CSS 2.1 pseudo-elements can have a single ':'. + // Any new pseudo-element must have two. + $pseudoElement = $identifier; + + continue; + } + + if (!$stream->getPeek()->isDelimiter(array('('))) { + $result = new Node\PseudoNode($result, $identifier); + + continue; + } + + $stream->getNext(); + $stream->skipWhitespace(); + + if ('not' === strtolower($identifier)) { + if ($insideNegation) { + throw SyntaxErrorException::nestedNot(); + } + + list($argument, $argumentPseudoElement) = $this->parseSimpleSelector($stream, true); + $next = $stream->getNext(); + + if (null !== $argumentPseudoElement) { + throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()'); + } + + if (!$next->isDelimiter(array(')'))) { + throw SyntaxErrorException::unexpectedToken('")"', $next); + } + + $result = new Node\NegationNode($result, $argument); + } else { + $arguments = array(); + $next = null; + + while (true) { + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if ($next->isIdentifier() + || $next->isString() + || $next->isNumber() + || $next->isDelimiter(array('+', '-')) + ) { + $arguments[] = $next; + } elseif ($next->isDelimiter(array(')'))) { + break; + } else { + throw SyntaxErrorException::unexpectedToken('an argument', $next); + } + } + + if (empty($arguments)) { + throw SyntaxErrorException::unexpectedToken('at least one argument', $next); + } + + $result = new Node\FunctionNode($result, $identifier, $arguments); + } + } else { + throw SyntaxErrorException::unexpectedToken('selector', $peek); + } + } + + if (\count($stream->getUsed()) === $selectorStart) { + throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek()); + } + + return array($result, $pseudoElement); + } + + /** + * Parses next element node. + * + * @return Node\ElementNode + */ + private function parseElementNode(TokenStream $stream) + { + $peek = $stream->getPeek(); + + if ($peek->isIdentifier() || $peek->isDelimiter(array('*'))) { + if ($peek->isIdentifier()) { + $namespace = $stream->getNext()->getValue(); + } else { + $stream->getNext(); + $namespace = null; + } + + if ($stream->getPeek()->isDelimiter(array('|'))) { + $stream->getNext(); + $element = $stream->getNextIdentifierOrStar(); + } else { + $element = $namespace; + $namespace = null; + } + } else { + $element = $namespace = null; + } + + return new Node\ElementNode($namespace, $element); + } + + /** + * Parses next attribute node. + * + * @return Node\AttributeNode + * + * @throws SyntaxErrorException + */ + private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream) + { + $stream->skipWhitespace(); + $attribute = $stream->getNextIdentifierOrStar(); + + if (null === $attribute && !$stream->getPeek()->isDelimiter(array('|'))) { + throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek()); + } + + if ($stream->getPeek()->isDelimiter(array('|'))) { + $stream->getNext(); + + if ($stream->getPeek()->isDelimiter(array('='))) { + $namespace = null; + $stream->getNext(); + $operator = '|='; + } else { + $namespace = $attribute; + $attribute = $stream->getNextIdentifier(); + $operator = null; + } + } else { + $namespace = $operator = null; + } + + if (null === $operator) { + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if ($next->isDelimiter(array(']'))) { + return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null); + } elseif ($next->isDelimiter(array('='))) { + $operator = '='; + } elseif ($next->isDelimiter(array('^', '$', '*', '~', '|', '!')) + && $stream->getPeek()->isDelimiter(array('=')) + ) { + $operator = $next->getValue().'='; + $stream->getNext(); + } else { + throw SyntaxErrorException::unexpectedToken('operator', $next); + } + } + + $stream->skipWhitespace(); + $value = $stream->getNext(); + + if ($value->isNumber()) { + // if the value is a number, it's casted into a string + $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition()); + } + + if (!($value->isIdentifier() || $value->isString())) { + throw SyntaxErrorException::unexpectedToken('string or identifier', $value); + } + + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if (!$next->isDelimiter(array(']'))) { + throw SyntaxErrorException::unexpectedToken('"]"', $next); + } + + return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue()); + } +} diff --git a/core/vendor/symfony/css-selector/Parser/ParserInterface.php b/core/vendor/symfony/css-selector/Parser/ParserInterface.php new file mode 100644 index 0000000..c5af203 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/ParserInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +use Symfony\Component\CssSelector\Node\SelectorNode; + +/** + * CSS selector parser interface. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +interface ParserInterface +{ + /** + * Parses given selector source into an array of tokens. + * + * @param string $source + * + * @return SelectorNode[] + */ + public function parse($source); +} diff --git a/core/vendor/symfony/css-selector/Parser/Reader.php b/core/vendor/symfony/css-selector/Parser/Reader.php new file mode 100644 index 0000000..076cb71 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Reader.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +/** + * CSS selector reader. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class Reader +{ + private $source; + private $length; + private $position = 0; + + /** + * @param string $source + */ + public function __construct($source) + { + $this->source = $source; + $this->length = \strlen($source); + } + + /** + * @return bool + */ + public function isEOF() + { + return $this->position >= $this->length; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @return int + */ + public function getRemainingLength() + { + return $this->length - $this->position; + } + + /** + * @param int $length + * @param int $offset + * + * @return string + */ + public function getSubstring($length, $offset = 0) + { + return substr($this->source, $this->position + $offset, $length); + } + + /** + * @param string $string + * + * @return int + */ + public function getOffset($string) + { + $position = strpos($this->source, $string, $this->position); + + return false === $position ? false : $position - $this->position; + } + + /** + * @param string $pattern + * + * @return array|false + */ + public function findPattern($pattern) + { + $source = substr($this->source, $this->position); + + if (preg_match($pattern, $source, $matches)) { + return $matches; + } + + return false; + } + + /** + * @param int $length + */ + public function moveForward($length) + { + $this->position += $length; + } + + public function moveToEnd() + { + $this->position = $this->length; + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Shortcut/ClassParser.php b/core/vendor/symfony/css-selector/Parser/Shortcut/ClassParser.php new file mode 100644 index 0000000..c513de5 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Shortcut/ClassParser.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector class parser shortcut. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class ClassParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, optional element, and required class + // $source = 'test|input.ab6bd_field'; + // $matches = array (size=4) + // 0 => string 'test|input.ab6bd_field' (length=22) + // 1 => string 'test' (length=4) + // 2 => string 'input' (length=5) + // 3 => string 'ab6bd_field' (length=11) + if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+\.([\w-]++)$/i', trim($source), $matches)) { + return array( + new SelectorNode(new ClassNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])), + ); + } + + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php b/core/vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php new file mode 100644 index 0000000..c29f5e4 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector element parser shortcut. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class ElementParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, required element or `*` + // $source = 'testns|testel'; + // $matches = array (size=3) + // 0 => string 'testns|testel' (length=13) + // 1 => string 'testns' (length=6) + // 2 => string 'testel' (length=6) + if (preg_match('/^(?:([a-z]++)\|)?([\w-]++|\*)$/i', trim($source), $matches)) { + return array(new SelectorNode(new ElementNode($matches[1] ?: null, $matches[2]))); + } + + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php b/core/vendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php new file mode 100644 index 0000000..16d374a --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector class parser shortcut. + * + * This shortcut ensure compatibility with previous version. + * - The parser fails to parse an empty string. + * - In the previous version, an empty string matches each tags. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class EmptyStringParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an empty string + if ('' == $source) { + return array(new SelectorNode(new ElementNode(null, '*'))); + } + + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Shortcut/HashParser.php b/core/vendor/symfony/css-selector/Parser/Shortcut/HashParser.php new file mode 100644 index 0000000..3f3883b --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Shortcut/HashParser.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\HashNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector hash parser shortcut. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class HashParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, optional element, and required id + // $source = 'test|input#ab6bd_field'; + // $matches = array (size=4) + // 0 => string 'test|input#ab6bd_field' (length=22) + // 1 => string 'test' (length=4) + // 2 => string 'input' (length=5) + // 3 => string 'ab6bd_field' (length=11) + if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+#([\w-]++)$/i', trim($source), $matches)) { + return array( + new SelectorNode(new HashNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])), + ); + } + + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Token.php b/core/vendor/symfony/css-selector/Parser/Token.php new file mode 100644 index 0000000..72baae7 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Token.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +/** + * CSS selector token. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class Token +{ + const TYPE_FILE_END = 'eof'; + const TYPE_DELIMITER = 'delimiter'; + const TYPE_WHITESPACE = 'whitespace'; + const TYPE_IDENTIFIER = 'identifier'; + const TYPE_HASH = 'hash'; + const TYPE_NUMBER = 'number'; + const TYPE_STRING = 'string'; + + private $type; + private $value; + private $position; + + /** + * @param int $type + * @param string $value + * @param int $position + */ + public function __construct($type, $value, $position) + { + $this->type = $type; + $this->value = $value; + $this->position = $position; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @return bool + */ + public function isFileEnd() + { + return self::TYPE_FILE_END === $this->type; + } + + /** + * @return bool + */ + public function isDelimiter(array $values = array()) + { + if (self::TYPE_DELIMITER !== $this->type) { + return false; + } + + if (empty($values)) { + return true; + } + + return \in_array($this->value, $values); + } + + /** + * @return bool + */ + public function isWhitespace() + { + return self::TYPE_WHITESPACE === $this->type; + } + + /** + * @return bool + */ + public function isIdentifier() + { + return self::TYPE_IDENTIFIER === $this->type; + } + + /** + * @return bool + */ + public function isHash() + { + return self::TYPE_HASH === $this->type; + } + + /** + * @return bool + */ + public function isNumber() + { + return self::TYPE_NUMBER === $this->type; + } + + /** + * @return bool + */ + public function isString() + { + return self::TYPE_STRING === $this->type; + } + + /** + * @return string + */ + public function __toString() + { + if ($this->value) { + return sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position); + } + + return sprintf('<%s at %s>', $this->type, $this->position); + } +} diff --git a/core/vendor/symfony/css-selector/Parser/TokenStream.php b/core/vendor/symfony/css-selector/Parser/TokenStream.php new file mode 100644 index 0000000..d2aee54 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/TokenStream.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +use Symfony\Component\CssSelector\Exception\InternalErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; + +/** + * CSS selector token stream. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class TokenStream +{ + /** + * @var Token[] + */ + private $tokens = array(); + + /** + * @var Token[] + */ + private $used = array(); + + /** + * @var int + */ + private $cursor = 0; + + /** + * @var Token|null + */ + private $peeked; + + /** + * @var bool + */ + private $peeking = false; + + /** + * Pushes a token. + * + * @return $this + */ + public function push(Token $token) + { + $this->tokens[] = $token; + + return $this; + } + + /** + * Freezes stream. + * + * @return $this + */ + public function freeze() + { + return $this; + } + + /** + * Returns next token. + * + * @return Token + * + * @throws InternalErrorException If there is no more token + */ + public function getNext() + { + if ($this->peeking) { + $this->peeking = false; + $this->used[] = $this->peeked; + + return $this->peeked; + } + + if (!isset($this->tokens[$this->cursor])) { + throw new InternalErrorException('Unexpected token stream end.'); + } + + return $this->tokens[$this->cursor++]; + } + + /** + * Returns peeked token. + * + * @return Token + */ + public function getPeek() + { + if (!$this->peeking) { + $this->peeked = $this->getNext(); + $this->peeking = true; + } + + return $this->peeked; + } + + /** + * Returns used tokens. + * + * @return Token[] + */ + public function getUsed() + { + return $this->used; + } + + /** + * Returns nex identifier token. + * + * @return string The identifier token value + * + * @throws SyntaxErrorException If next token is not an identifier + */ + public function getNextIdentifier() + { + $next = $this->getNext(); + + if (!$next->isIdentifier()) { + throw SyntaxErrorException::unexpectedToken('identifier', $next); + } + + return $next->getValue(); + } + + /** + * Returns nex identifier or star delimiter token. + * + * @return string|null The identifier token value or null if star found + * + * @throws SyntaxErrorException If next token is not an identifier or a star delimiter + */ + public function getNextIdentifierOrStar() + { + $next = $this->getNext(); + + if ($next->isIdentifier()) { + return $next->getValue(); + } + + if ($next->isDelimiter(array('*'))) { + return; + } + + throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); + } + + /** + * Skips next whitespace if any. + */ + public function skipWhitespace() + { + $peek = $this->getPeek(); + + if ($peek->isWhitespace()) { + $this->getNext(); + } + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Tokenizer/Tokenizer.php b/core/vendor/symfony/css-selector/Parser/Tokenizer/Tokenizer.php new file mode 100644 index 0000000..e32b4d2 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Tokenizer/Tokenizer.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Tokenizer; + +use Symfony\Component\CssSelector\Parser\Handler; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector tokenizer. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class Tokenizer +{ + /** + * @var Handler\HandlerInterface[] + */ + private $handlers; + + public function __construct() + { + $patterns = new TokenizerPatterns(); + $escaping = new TokenizerEscaping($patterns); + + $this->handlers = array( + new Handler\WhitespaceHandler(), + new Handler\IdentifierHandler($patterns, $escaping), + new Handler\HashHandler($patterns, $escaping), + new Handler\StringHandler($patterns, $escaping), + new Handler\NumberHandler($patterns), + new Handler\CommentHandler(), + ); + } + + /** + * Tokenize selector source code. + * + * @return TokenStream + */ + public function tokenize(Reader $reader) + { + $stream = new TokenStream(); + + while (!$reader->isEOF()) { + foreach ($this->handlers as $handler) { + if ($handler->handle($reader, $stream)) { + continue 2; + } + } + + $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition())); + $reader->moveForward(1); + } + + return $stream + ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition())) + ->freeze(); + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php b/core/vendor/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php new file mode 100644 index 0000000..55ea421 --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Tokenizer; + +/** + * CSS selector tokenizer escaping applier. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class TokenizerEscaping +{ + private $patterns; + + public function __construct(TokenizerPatterns $patterns) + { + $this->patterns = $patterns; + } + + /** + * @param string $value + * + * @return string + */ + public function escapeUnicode($value) + { + $value = $this->replaceUnicodeSequences($value); + + return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value); + } + + /** + * @param string $value + * + * @return string + */ + public function escapeUnicodeAndNewLine($value) + { + $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value); + + return $this->escapeUnicode($value); + } + + /** + * @param string $value + * + * @return string + */ + private function replaceUnicodeSequences($value) + { + return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) { + $c = hexdec($match[1]); + + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + }, $value); + } +} diff --git a/core/vendor/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php b/core/vendor/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php new file mode 100644 index 0000000..bc6130d --- /dev/null +++ b/core/vendor/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Tokenizer; + +/** + * CSS selector tokenizer patterns builder. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class TokenizerPatterns +{ + private $unicodeEscapePattern; + private $simpleEscapePattern; + private $newLineEscapePattern; + private $escapePattern; + private $stringEscapePattern; + private $nonAsciiPattern; + private $nmCharPattern; + private $nmStartPattern; + private $identifierPattern; + private $hashPattern; + private $numberPattern; + private $quotedStringPattern; + + public function __construct() + { + $this->unicodeEscapePattern = '\\\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?'; + $this->simpleEscapePattern = '\\\\(.)'; + $this->newLineEscapePattern = '\\\\(?:\n|\r\n|\r|\f)'; + $this->escapePattern = $this->unicodeEscapePattern.'|\\\\[^\n\r\f0-9a-f]'; + $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern; + $this->nonAsciiPattern = '[^\x00-\x7F]'; + $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; + $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; + $this->identifierPattern = '-?(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; + $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; + $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; + $this->quotedStringPattern = '([^\n\r\f%s]|'.$this->stringEscapePattern.')*'; + } + + /** + * @return string + */ + public function getNewLineEscapePattern() + { + return '~^'.$this->newLineEscapePattern.'~'; + } + + /** + * @return string + */ + public function getSimpleEscapePattern() + { + return '~^'.$this->simpleEscapePattern.'~'; + } + + /** + * @return string + */ + public function getUnicodeEscapePattern() + { + return '~^'.$this->unicodeEscapePattern.'~i'; + } + + /** + * @return string + */ + public function getIdentifierPattern() + { + return '~^'.$this->identifierPattern.'~i'; + } + + /** + * @return string + */ + public function getHashPattern() + { + return '~^'.$this->hashPattern.'~i'; + } + + /** + * @return string + */ + public function getNumberPattern() + { + return '~^'.$this->numberPattern.'~'; + } + + /** + * @param string $quote + * + * @return string + */ + public function getQuotedStringPattern($quote) + { + return '~^'.sprintf($this->quotedStringPattern, $quote).'~i'; + } +} diff --git a/core/vendor/symfony/css-selector/README.md b/core/vendor/symfony/css-selector/README.md new file mode 100644 index 0000000..7c4c411 --- /dev/null +++ b/core/vendor/symfony/css-selector/README.md @@ -0,0 +1,20 @@ +CssSelector Component +===================== + +The CssSelector component converts CSS selectors to XPath expressions. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/css_selector.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +Credits +------- + +This component is a port of the Python cssselect library +[v0.7.1](https://github.com/SimonSapin/cssselect/releases/tag/v0.7.1), +which is distributed under the BSD license. diff --git a/core/vendor/symfony/css-selector/Tests/CssSelectorConverterTest.php b/core/vendor/symfony/css-selector/Tests/CssSelectorConverterTest.php new file mode 100644 index 0000000..a3eea7a --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/CssSelectorConverterTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\CssSelectorConverter; + +class CssSelectorConverterTest extends TestCase +{ + public function testCssToXPath() + { + $converter = new CssSelectorConverter(); + + $this->assertEquals('descendant-or-self::*', $converter->toXPath('')); + $this->assertEquals('descendant-or-self::h1', $converter->toXPath('h1')); + $this->assertEquals("descendant-or-self::h1[@id = 'foo']", $converter->toXPath('h1#foo')); + $this->assertEquals("descendant-or-self::h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", $converter->toXPath('h1.foo')); + $this->assertEquals('descendant-or-self::foo:h1', $converter->toXPath('foo|h1')); + $this->assertEquals('descendant-or-self::h1', $converter->toXPath('H1')); + } + + public function testCssToXPathXml() + { + $converter = new CssSelectorConverter(false); + + $this->assertEquals('descendant-or-self::H1', $converter->toXPath('H1')); + } + + /** + * @expectedException \Symfony\Component\CssSelector\Exception\ParseException + * @expectedExceptionMessage Expected identifier, but found. + */ + public function testParseExceptions() + { + $converter = new CssSelectorConverter(); + $converter->toXPath('h1:'); + } + + /** @dataProvider getCssToXPathWithoutPrefixTestData */ + public function testCssToXPathWithoutPrefix($css, $xpath) + { + $converter = new CssSelectorConverter(); + + $this->assertEquals($xpath, $converter->toXPath($css, ''), '->parse() parses an input string and returns a node'); + } + + public function getCssToXPathWithoutPrefixTestData() + { + return array( + array('h1', 'h1'), + array('foo|h1', 'foo:h1'), + array('h1, h2, h3', 'h1 | h2 | h3'), + array('h1:nth-child(3n+1)', "*/*[(name() = 'h1') and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]"), + array('h1 > p', 'h1/p'), + array('h1#foo', "h1[@id = 'foo']"), + array('h1.foo', "h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('h1[class*="foo bar"]', "h1[@class and contains(@class, 'foo bar')]"), + array('h1[foo|class*="foo bar"]', "h1[@foo:class and contains(@foo:class, 'foo bar')]"), + array('h1[class]', 'h1[@class]'), + array('h1 .foo', "h1/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('h1 #foo', "h1/descendant-or-self::*/*[@id = 'foo']"), + array('h1 [class*=foo]', "h1/descendant-or-self::*/*[@class and contains(@class, 'foo')]"), + array('div>.foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('div > .foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/CssSelectorTest.php b/core/vendor/symfony/css-selector/Tests/CssSelectorTest.php new file mode 100644 index 0000000..f8ba536 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/CssSelectorTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\CssSelector; + +/** + * @group legacy + */ +class CssSelectorTest extends TestCase +{ + public function testCssToXPath() + { + $this->assertEquals('descendant-or-self::*', CssSelector::toXPath('')); + $this->assertEquals('descendant-or-self::h1', CssSelector::toXPath('h1')); + $this->assertEquals("descendant-or-self::h1[@id = 'foo']", CssSelector::toXPath('h1#foo')); + $this->assertEquals("descendant-or-self::h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", CssSelector::toXPath('h1.foo')); + $this->assertEquals('descendant-or-self::foo:h1', CssSelector::toXPath('foo|h1')); + } + + /** @dataProvider getCssToXPathWithoutPrefixTestData */ + public function testCssToXPathWithoutPrefix($css, $xpath) + { + $this->assertEquals($xpath, CssSelector::toXPath($css, ''), '->parse() parses an input string and returns a node'); + } + + public function testParseExceptions() + { + try { + CssSelector::toXPath('h1:'); + $this->fail('->parse() throws an Exception if the css selector is not valid'); + } catch (\Exception $e) { + $this->assertInstanceOf('\Symfony\Component\CssSelector\Exception\ParseException', $e, '->parse() throws an Exception if the css selector is not valid'); + $this->assertEquals('Expected identifier, but found.', $e->getMessage(), '->parse() throws an Exception if the css selector is not valid'); + } + } + + public function getCssToXPathWithoutPrefixTestData() + { + return array( + array('h1', 'h1'), + array('foo|h1', 'foo:h1'), + array('h1, h2, h3', 'h1 | h2 | h3'), + array('h1:nth-child(3n+1)', "*/*[(name() = 'h1') and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]"), + array('h1 > p', 'h1/p'), + array('h1#foo', "h1[@id = 'foo']"), + array('h1.foo', "h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('h1[class*="foo bar"]', "h1[@class and contains(@class, 'foo bar')]"), + array('h1[foo|class*="foo bar"]', "h1[@foo:class and contains(@foo:class, 'foo bar')]"), + array('h1[class]', 'h1[@class]'), + array('h1 .foo', "h1/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('h1 #foo', "h1/descendant-or-self::*/*[@id = 'foo']"), + array('h1 [class*=foo]', "h1/descendant-or-self::*/*[@class and contains(@class, 'foo')]"), + array('div>.foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('div > .foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/AbstractNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/AbstractNodeTest.php new file mode 100644 index 0000000..5955513 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/AbstractNodeTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Node\NodeInterface; + +abstract class AbstractNodeTest extends TestCase +{ + /** @dataProvider getToStringConversionTestData */ + public function testToStringConversion(NodeInterface $node, $representation) + { + $this->assertEquals($representation, (string) $node); + } + + /** @dataProvider getSpecificityValueTestData */ + public function testSpecificityValue(NodeInterface $node, $value) + { + $this->assertEquals($value, $node->getSpecificity()->getValue()); + } + + abstract public function getToStringConversionTestData(); + + abstract public function getSpecificityValueTestData(); +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/AttributeNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/AttributeNodeTest.php new file mode 100644 index 0000000..1fd090f --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/AttributeNodeTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\AttributeNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class AttributeNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 'Attribute[Element[*][attribute]]'), + array(new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), "Attribute[Element[*][attribute $= 'value']]"), + array(new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), "Attribute[Element[*][namespace|attribute $= 'value']]"), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 10), + array(new AttributeNode(new ElementNode(null, 'element'), null, 'attribute', 'exists', null), 11), + array(new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), 10), + array(new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), 10), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/ClassNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/ClassNodeTest.php new file mode 100644 index 0000000..e0ab45a --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/ClassNodeTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class ClassNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new ClassNode(new ElementNode(), 'class'), 'Class[Element[*].class]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new ClassNode(new ElementNode(), 'class'), 10), + array(new ClassNode(new ElementNode(null, 'element'), 'class'), 11), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/CombinedSelectorNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/CombinedSelectorNodeTest.php new file mode 100644 index 0000000..9547298 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/CombinedSelectorNodeTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\CombinedSelectorNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class CombinedSelectorNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 'CombinedSelector[Element[*] > Element[*]]'), + array(new CombinedSelectorNode(new ElementNode(), ' ', new ElementNode()), 'CombinedSelector[Element[*] Element[*]]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 0), + array(new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode()), 1), + array(new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode(null, 'element')), 2), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/ElementNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/ElementNodeTest.php new file mode 100644 index 0000000..6d24789 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/ElementNodeTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; + +class ElementNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new ElementNode(), 'Element[*]'), + array(new ElementNode(null, 'element'), 'Element[element]'), + array(new ElementNode('namespace', 'element'), 'Element[namespace|element]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new ElementNode(), 0), + array(new ElementNode(null, 'element'), 1), + array(new ElementNode('namespace', 'element'), 1), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/FunctionNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/FunctionNodeTest.php new file mode 100644 index 0000000..ee3ce51 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/FunctionNodeTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Parser\Token; + +class FunctionNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new FunctionNode(new ElementNode(), 'function'), 'Function[Element[*]:function()]'), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_IDENTIFIER, 'value', 0), + )), "Function[Element[*]:function(['value'])]"), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_STRING, 'value1', 0), + new Token(Token::TYPE_NUMBER, 'value2', 0), + )), "Function[Element[*]:function(['value1', 'value2'])]"), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new FunctionNode(new ElementNode(), 'function'), 10), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_IDENTIFIER, 'value', 0), + )), 10), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_STRING, 'value1', 0), + new Token(Token::TYPE_NUMBER, 'value2', 0), + )), 10), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/HashNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/HashNodeTest.php new file mode 100644 index 0000000..3bc74da --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/HashNodeTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\HashNode; + +class HashNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new HashNode(new ElementNode(), 'id'), 'Hash[Element[*]#id]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new HashNode(new ElementNode(), 'id'), 100), + array(new HashNode(new ElementNode(null, 'id'), 'class'), 101), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/NegationNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/NegationNodeTest.php new file mode 100644 index 0000000..ed4d248 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/NegationNodeTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\NegationNode; + +class NegationNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 'Negation[Element[*]:not(Class[Element[*].class])]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 10), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/PseudoNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/PseudoNodeTest.php new file mode 100644 index 0000000..bc57813 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/PseudoNodeTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\PseudoNode; + +class PseudoNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new PseudoNode(new ElementNode(), 'pseudo'), 'Pseudo[Element[*]:pseudo]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new PseudoNode(new ElementNode(), 'pseudo'), 10), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/SelectorNodeTest.php b/core/vendor/symfony/css-selector/Tests/Node/SelectorNodeTest.php new file mode 100644 index 0000000..5badf71 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/SelectorNodeTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; + +class SelectorNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new SelectorNode(new ElementNode()), 'Selector[Element[*]]'), + array(new SelectorNode(new ElementNode(), 'pseudo'), 'Selector[Element[*]::pseudo]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new SelectorNode(new ElementNode()), 0), + array(new SelectorNode(new ElementNode(), 'pseudo'), 1), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Node/SpecificityTest.php b/core/vendor/symfony/css-selector/Tests/Node/SpecificityTest.php new file mode 100644 index 0000000..b58eb89 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Node/SpecificityTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Node\Specificity; + +class SpecificityTest extends TestCase +{ + /** @dataProvider getValueTestData */ + public function testValue(Specificity $specificity, $value) + { + $this->assertEquals($value, $specificity->getValue()); + } + + /** @dataProvider getValueTestData */ + public function testPlusValue(Specificity $specificity, $value) + { + $this->assertEquals($value + 123, $specificity->plus(new Specificity(1, 2, 3))->getValue()); + } + + public function getValueTestData() + { + return array( + array(new Specificity(0, 0, 0), 0), + array(new Specificity(0, 0, 2), 2), + array(new Specificity(0, 3, 0), 30), + array(new Specificity(4, 0, 0), 400), + array(new Specificity(4, 3, 2), 432), + ); + } + + /** @dataProvider getCompareTestData */ + public function testCompareTo(Specificity $a, Specificity $b, $result) + { + $this->assertEquals($result, $a->compareTo($b)); + } + + public function getCompareTestData() + { + return array( + array(new Specificity(0, 0, 0), new Specificity(0, 0, 0), 0), + array(new Specificity(0, 0, 1), new Specificity(0, 0, 1), 0), + array(new Specificity(0, 0, 2), new Specificity(0, 0, 1), 1), + array(new Specificity(0, 0, 2), new Specificity(0, 0, 3), -1), + array(new Specificity(0, 4, 0), new Specificity(0, 4, 0), 0), + array(new Specificity(0, 6, 0), new Specificity(0, 5, 11), 1), + array(new Specificity(0, 7, 0), new Specificity(0, 8, 0), -1), + array(new Specificity(9, 0, 0), new Specificity(9, 0, 0), 0), + array(new Specificity(11, 0, 0), new Specificity(10, 11, 0), 1), + array(new Specificity(12, 11, 0), new Specificity(13, 0, 0), -1), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Handler/AbstractHandlerTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Handler/AbstractHandlerTest.php new file mode 100644 index 0000000..f5c9dc8 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Handler/AbstractHandlerTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * @author Jean-François Simon + */ +abstract class AbstractHandlerTest extends TestCase +{ + /** @dataProvider getHandleValueTestData */ + public function testHandleValue($value, Token $expectedToken, $remainingContent) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertTrue($this->generateHandler()->handle($reader, $stream)); + $this->assertEquals($expectedToken, $stream->getNext()); + $this->assertRemainingContent($reader, $remainingContent); + } + + /** @dataProvider getDontHandleValueTestData */ + public function testDontHandleValue($value) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertFalse($this->generateHandler()->handle($reader, $stream)); + $this->assertStreamEmpty($stream); + $this->assertRemainingContent($reader, $value); + } + + abstract public function getHandleValueTestData(); + + abstract public function getDontHandleValueTestData(); + + abstract protected function generateHandler(); + + protected function assertStreamEmpty(TokenStream $stream) + { + $property = new \ReflectionProperty($stream, 'tokens'); + $property->setAccessible(true); + + $this->assertEquals(array(), $property->getValue($stream)); + } + + protected function assertRemainingContent(Reader $reader, $remainingContent) + { + if ('' === $remainingContent) { + $this->assertEquals(0, $reader->getRemainingLength()); + $this->assertTrue($reader->isEOF()); + } else { + $this->assertEquals(\strlen($remainingContent), $reader->getRemainingLength()); + $this->assertEquals(0, $reader->getOffset($remainingContent)); + } + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Handler/CommentHandlerTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Handler/CommentHandlerTest.php new file mode 100644 index 0000000..3961bf7 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Handler/CommentHandlerTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\CommentHandler; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +class CommentHandlerTest extends AbstractHandlerTest +{ + /** @dataProvider getHandleValueTestData */ + public function testHandleValue($value, Token $unusedArgument, $remainingContent) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertTrue($this->generateHandler()->handle($reader, $stream)); + // comments are ignored (not pushed as token in stream) + $this->assertStreamEmpty($stream); + $this->assertRemainingContent($reader, $remainingContent); + } + + public function getHandleValueTestData() + { + return array( + // 2nd argument only exists for inherited method compatibility + array('/* comment */', new Token(null, null, null), ''), + array('/* comment */foo', new Token(null, null, null), 'foo'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('+'), + array(' '), + ); + } + + protected function generateHandler() + { + return new CommentHandler(); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Handler/HashHandlerTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Handler/HashHandlerTest.php new file mode 100644 index 0000000..5730120 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Handler/HashHandlerTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\HashHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +class HashHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('#id', new Token(Token::TYPE_HASH, 'id', 0), ''), + array('#123', new Token(Token::TYPE_HASH, '123', 0), ''), + + array('#id.class', new Token(Token::TYPE_HASH, 'id', 0), '.class'), + array('#id element', new Token(Token::TYPE_HASH, 'id', 0), ' element'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('id'), + array('123'), + array('<'), + array('<'), + array('#'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new HashHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Handler/IdentifierHandlerTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Handler/IdentifierHandlerTest.php new file mode 100644 index 0000000..f56430c --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Handler/IdentifierHandlerTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\IdentifierHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +class IdentifierHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('foo', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ''), + array('foo|bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '|bar'), + array('foo.class', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '.class'), + array('foo[attr]', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '[attr]'), + array('foo bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ' bar'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('+'), + array(' '), + array('*|foo'), + array('/* comment */'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new IdentifierHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Handler/NumberHandlerTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Handler/NumberHandlerTest.php new file mode 100644 index 0000000..675fd05 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Handler/NumberHandlerTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\NumberHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +class NumberHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('12', new Token(Token::TYPE_NUMBER, '12', 0), ''), + array('12.34', new Token(Token::TYPE_NUMBER, '12.34', 0), ''), + array('+12.34', new Token(Token::TYPE_NUMBER, '+12.34', 0), ''), + array('-12.34', new Token(Token::TYPE_NUMBER, '-12.34', 0), ''), + + array('12 arg', new Token(Token::TYPE_NUMBER, '12', 0), ' arg'), + array('12]', new Token(Token::TYPE_NUMBER, '12', 0), ']'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('hello'), + array('>'), + array('+'), + array(' '), + array('/* comment */'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new NumberHandler($patterns); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Handler/StringHandlerTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Handler/StringHandlerTest.php new file mode 100644 index 0000000..8ea5d4d --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Handler/StringHandlerTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\StringHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +class StringHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('"hello"', new Token(Token::TYPE_STRING, 'hello', 1), ''), + array('"1"', new Token(Token::TYPE_STRING, '1', 1), ''), + array('" "', new Token(Token::TYPE_STRING, ' ', 1), ''), + array('""', new Token(Token::TYPE_STRING, '', 1), ''), + array("'hello'", new Token(Token::TYPE_STRING, 'hello', 1), ''), + + array("'foo'bar", new Token(Token::TYPE_STRING, 'foo', 1), 'bar'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('hello'), + array('>'), + array('1'), + array(' '), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new StringHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Handler/WhitespaceHandlerTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Handler/WhitespaceHandlerTest.php new file mode 100644 index 0000000..f5f9e71 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Handler/WhitespaceHandlerTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\WhitespaceHandler; +use Symfony\Component\CssSelector\Parser\Token; + +class WhitespaceHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array(' ', new Token(Token::TYPE_WHITESPACE, ' ', 0), ''), + array("\n", new Token(Token::TYPE_WHITESPACE, "\n", 0), ''), + array("\t", new Token(Token::TYPE_WHITESPACE, "\t", 0), ''), + + array(' foo', new Token(Token::TYPE_WHITESPACE, ' ', 0), 'foo'), + array(' .foo', new Token(Token::TYPE_WHITESPACE, ' ', 0), '.foo'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('1'), + array('a'), + ); + } + + protected function generateHandler() + { + return new WhitespaceHandler(); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/ParserTest.php b/core/vendor/symfony/css-selector/Tests/Parser/ParserTest.php new file mode 100644 index 0000000..53b35a9 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/ParserTest.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\Parser\Token; + +class ParserTest extends TestCase +{ + /** @dataProvider getParserTestData */ + public function testParser($source, $representation) + { + $parser = new Parser(); + + $this->assertEquals($representation, array_map(function (SelectorNode $node) { + return (string) $node->getTree(); + }, $parser->parse($source))); + } + + /** @dataProvider getParserExceptionTestData */ + public function testParserException($source, $message) + { + $parser = new Parser(); + + try { + $parser->parse($source); + $this->fail('Parser should throw a SyntaxErrorException.'); + } catch (SyntaxErrorException $e) { + $this->assertEquals($message, $e->getMessage()); + } + } + + /** @dataProvider getPseudoElementsTestData */ + public function testPseudoElements($source, $element, $pseudo) + { + $parser = new Parser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($element, (string) $selector->getTree()); + $this->assertEquals($pseudo, (string) $selector->getPseudoElement()); + } + + /** @dataProvider getSpecificityTestData */ + public function testSpecificity($source, $value) + { + $parser = new Parser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($value, $selector->getSpecificity()->getValue()); + } + + /** @dataProvider getParseSeriesTestData */ + public function testParseSeries($series, $a, $b) + { + $parser = new Parser(); + $selectors = $parser->parse(sprintf(':nth-child(%s)', $series)); + $this->assertCount(1, $selectors); + + /** @var FunctionNode $function */ + $function = $selectors[0]->getTree(); + $this->assertEquals(array($a, $b), Parser::parseSeries($function->getArguments())); + } + + /** @dataProvider getParseSeriesExceptionTestData */ + public function testParseSeriesException($series) + { + $parser = new Parser(); + $selectors = $parser->parse(sprintf(':nth-child(%s)', $series)); + $this->assertCount(1, $selectors); + + /** @var FunctionNode $function */ + $function = $selectors[0]->getTree(); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + Parser::parseSeries($function->getArguments()); + } + + public function getParserTestData() + { + return array( + array('*', array('Element[*]')), + array('*|*', array('Element[*]')), + array('*|foo', array('Element[foo]')), + array('foo|*', array('Element[foo|*]')), + array('foo|bar', array('Element[foo|bar]')), + array('#foo#bar', array('Hash[Hash[Element[*]#foo]#bar]')), + array('div>.foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div> .foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div >.foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div > .foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array("div \n> \t \t .foo", array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('td.foo,.bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo, .bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array("td.foo\t\r\n\f ,\t\r\n\f .bar", array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo,.bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo, .bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array("td.foo\t\r\n\f ,\t\r\n\f .bar", array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('div, td.foo, div.bar span', array('Element[div]', 'Class[Element[td].foo]', 'CombinedSelector[Class[Element[div].bar] Element[span]]')), + array('div > p', array('CombinedSelector[Element[div] > Element[p]]')), + array('td:first', array('Pseudo[Element[td]:first]')), + array('td :first', array('CombinedSelector[Element[td] Pseudo[Element[*]:first]]')), + array('a[name]', array('Attribute[Element[a][name]]')), + array("a[ name\t]", array('Attribute[Element[a][name]]')), + array('a [name]', array('CombinedSelector[Element[a] Attribute[Element[*][name]]]')), + array('a[rel="include"]', array("Attribute[Element[a][rel = 'include']]")), + array('a[rel = include]', array("Attribute[Element[a][rel = 'include']]")), + array("a[hreflang |= 'en']", array("Attribute[Element[a][hreflang |= 'en']]")), + array('a[hreflang|=en]', array("Attribute[Element[a][hreflang |= 'en']]")), + array('div:nth-child(10)', array("Function[Element[div]:nth-child(['10'])]")), + array(':nth-child(2n+2)', array("Function[Element[*]:nth-child(['2', 'n', '+2'])]")), + array('div:nth-of-type(10)', array("Function[Element[div]:nth-of-type(['10'])]")), + array('div div:nth-of-type(10) .aclass', array("CombinedSelector[CombinedSelector[Element[div] Function[Element[div]:nth-of-type(['10'])]] Class[Element[*].aclass]]")), + array('label:only', array('Pseudo[Element[label]:only]')), + array('a:lang(fr)', array("Function[Element[a]:lang(['fr'])]")), + array('div:contains("foo")', array("Function[Element[div]:contains(['foo'])]")), + array('div#foobar', array('Hash[Element[div]#foobar]')), + array('div:not(div.foo)', array('Negation[Element[div]:not(Class[Element[div].foo])]')), + array('td ~ th', array('CombinedSelector[Element[td] ~ Element[th]]')), + array('.foo[data-bar][data-baz=0]', array("Attribute[Attribute[Class[Element[*].foo][data-bar]][data-baz = '0']]")), + ); + } + + public function getParserExceptionTestData() + { + return array( + array('attributes(href)/html/body/a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '(', 10))->getMessage()), + array('attributes(href)', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '(', 10))->getMessage()), + array('html/body/a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '/', 4))->getMessage()), + array(' ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 1))->getMessage()), + array('div, ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 5))->getMessage()), + array(' , div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, ',', 1))->getMessage()), + array('p, , div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, ',', 3))->getMessage()), + array('div > ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 6))->getMessage()), + array(' > div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '>', 2))->getMessage()), + array('foo|#bar', SyntaxErrorException::unexpectedToken('identifier or "*"', new Token(Token::TYPE_HASH, 'bar', 4))->getMessage()), + array('#.foo', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '#', 0))->getMessage()), + array('.#foo', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_HASH, 'foo', 1))->getMessage()), + array(':#foo', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_HASH, 'foo', 1))->getMessage()), + array('[*]', SyntaxErrorException::unexpectedToken('"|"', new Token(Token::TYPE_DELIMITER, ']', 2))->getMessage()), + array('[foo|]', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_DELIMITER, ']', 5))->getMessage()), + array('[#]', SyntaxErrorException::unexpectedToken('identifier or "*"', new Token(Token::TYPE_DELIMITER, '#', 1))->getMessage()), + array('[foo=#]', SyntaxErrorException::unexpectedToken('string or identifier', new Token(Token::TYPE_DELIMITER, '#', 5))->getMessage()), + array(':nth-child()', SyntaxErrorException::unexpectedToken('at least one argument', new Token(Token::TYPE_DELIMITER, ')', 11))->getMessage()), + array('[href]a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_IDENTIFIER, 'a', 6))->getMessage()), + array('[rel:stylesheet]', SyntaxErrorException::unexpectedToken('operator', new Token(Token::TYPE_DELIMITER, ':', 4))->getMessage()), + array('[rel=stylesheet', SyntaxErrorException::unexpectedToken('"]"', new Token(Token::TYPE_FILE_END, '', 15))->getMessage()), + array(':lang(fr', SyntaxErrorException::unexpectedToken('an argument', new Token(Token::TYPE_FILE_END, '', 8))->getMessage()), + array(':contains("foo', SyntaxErrorException::unclosedString(10)->getMessage()), + array('foo!', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '!', 3))->getMessage()), + ); + } + + public function getPseudoElementsTestData() + { + return array( + array('foo', 'Element[foo]', ''), + array('*', 'Element[*]', ''), + array(':empty', 'Pseudo[Element[*]:empty]', ''), + array(':BEfore', 'Element[*]', 'before'), + array(':aftER', 'Element[*]', 'after'), + array(':First-Line', 'Element[*]', 'first-line'), + array(':First-Letter', 'Element[*]', 'first-letter'), + array('::befoRE', 'Element[*]', 'before'), + array('::AFter', 'Element[*]', 'after'), + array('::firsT-linE', 'Element[*]', 'first-line'), + array('::firsT-letteR', 'Element[*]', 'first-letter'), + array('::Selection', 'Element[*]', 'selection'), + array('foo:after', 'Element[foo]', 'after'), + array('foo::selection', 'Element[foo]', 'selection'), + array('lorem#ipsum ~ a#b.c[href]:empty::selection', 'CombinedSelector[Hash[Element[lorem]#ipsum] ~ Pseudo[Attribute[Class[Hash[Element[a]#b].c][href]]:empty]]', 'selection'), + array('video::-webkit-media-controls', 'Element[video]', '-webkit-media-controls'), + ); + } + + public function getSpecificityTestData() + { + return array( + array('*', 0), + array(' foo', 1), + array(':empty ', 10), + array(':before', 1), + array('*:before', 1), + array(':nth-child(2)', 10), + array('.bar', 10), + array('[baz]', 10), + array('[baz="4"]', 10), + array('[baz^="4"]', 10), + array('#lipsum', 100), + array(':not(*)', 0), + array(':not(foo)', 1), + array(':not(.foo)', 10), + array(':not([foo])', 10), + array(':not(:empty)', 10), + array(':not(#foo)', 100), + array('foo:empty', 11), + array('foo:before', 2), + array('foo::before', 2), + array('foo:empty::before', 12), + array('#lorem + foo#ipsum:first-child > bar:first-line', 213), + ); + } + + public function getParseSeriesTestData() + { + return array( + array('1n+3', 1, 3), + array('1n +3', 1, 3), + array('1n + 3', 1, 3), + array('1n+ 3', 1, 3), + array('1n-3', 1, -3), + array('1n -3', 1, -3), + array('1n - 3', 1, -3), + array('1n- 3', 1, -3), + array('n-5', 1, -5), + array('odd', 2, 1), + array('even', 2, 0), + array('3n', 3, 0), + array('n', 1, 0), + array('+n', 1, 0), + array('-n', -1, 0), + array('5', 0, 5), + ); + } + + public function getParseSeriesExceptionTestData() + { + return array( + array('foo'), + array('n+'), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/ReaderTest.php b/core/vendor/symfony/css-selector/Tests/Parser/ReaderTest.php new file mode 100644 index 0000000..21eb608 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/ReaderTest.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Parser\Reader; + +class ReaderTest extends TestCase +{ + public function testIsEOF() + { + $reader = new Reader(''); + $this->assertTrue($reader->isEOF()); + + $reader = new Reader('hello'); + $this->assertFalse($reader->isEOF()); + + $this->assignPosition($reader, 2); + $this->assertFalse($reader->isEOF()); + + $this->assignPosition($reader, 5); + $this->assertTrue($reader->isEOF()); + } + + public function testGetRemainingLength() + { + $reader = new Reader('hello'); + $this->assertEquals(5, $reader->getRemainingLength()); + + $this->assignPosition($reader, 2); + $this->assertEquals(3, $reader->getRemainingLength()); + + $this->assignPosition($reader, 5); + $this->assertEquals(0, $reader->getRemainingLength()); + } + + public function testGetSubstring() + { + $reader = new Reader('hello'); + $this->assertEquals('he', $reader->getSubstring(2)); + $this->assertEquals('el', $reader->getSubstring(2, 1)); + + $this->assignPosition($reader, 2); + $this->assertEquals('ll', $reader->getSubstring(2)); + $this->assertEquals('lo', $reader->getSubstring(2, 1)); + } + + public function testGetOffset() + { + $reader = new Reader('hello'); + $this->assertEquals(2, $reader->getOffset('ll')); + $this->assertFalse($reader->getOffset('w')); + + $this->assignPosition($reader, 2); + $this->assertEquals(0, $reader->getOffset('ll')); + $this->assertFalse($reader->getOffset('he')); + } + + public function testFindPattern() + { + $reader = new Reader('hello'); + + $this->assertFalse($reader->findPattern('/world/')); + $this->assertEquals(array('hello', 'h'), $reader->findPattern('/^([a-z]).*/')); + + $this->assignPosition($reader, 2); + $this->assertFalse($reader->findPattern('/^h.*/')); + $this->assertEquals(array('llo'), $reader->findPattern('/^llo$/')); + } + + public function testMoveForward() + { + $reader = new Reader('hello'); + $this->assertEquals(0, $reader->getPosition()); + + $reader->moveForward(2); + $this->assertEquals(2, $reader->getPosition()); + } + + public function testToEnd() + { + $reader = new Reader('hello'); + $reader->moveToEnd(); + $this->assertTrue($reader->isEOF()); + } + + private function assignPosition(Reader $reader, $value) + { + $position = new \ReflectionProperty($reader, 'position'); + $position->setAccessible(true); + $position->setValue($reader, $value); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/ClassParserTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/ClassParserTest.php new file mode 100644 index 0000000..7e92f5b --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/ClassParserTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Shortcut; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser; + +/** + * @author Jean-François Simon + */ +class ClassParserTest extends TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new ClassParser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('.testclass', 'Class[Element[*].testclass]'), + array('testel.testclass', 'Class[Element[testel].testclass]'), + array('testns|.testclass', 'Class[Element[testns|*].testclass]'), + array('testns|*.testclass', 'Class[Element[testns|*].testclass]'), + array('testns|testel.testclass', 'Class[Element[testns|testel].testclass]'), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/ElementParserTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/ElementParserTest.php new file mode 100644 index 0000000..05a730f --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/ElementParserTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Shortcut; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser; + +/** + * @author Jean-François Simon + */ +class ElementParserTest extends TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new ElementParser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('*', 'Element[*]'), + array('testel', 'Element[testel]'), + array('testns|*', 'Element[testns|*]'), + array('testns|testel', 'Element[testns|testel]'), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/EmptyStringParserTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/EmptyStringParserTest.php new file mode 100644 index 0000000..1cf742c --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/EmptyStringParserTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Shortcut; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser; + +/** + * @author Jean-François Simon + */ +class EmptyStringParserTest extends TestCase +{ + public function testParse() + { + $parser = new EmptyStringParser(); + $selectors = $parser->parse(''); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals('Element[*]', (string) $selector->getTree()); + + $selectors = $parser->parse('this will produce an empty array'); + $this->assertCount(0, $selectors); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/HashParserTest.php b/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/HashParserTest.php new file mode 100644 index 0000000..82f555d --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/Shortcut/HashParserTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Shortcut; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\HashParser; + +/** + * @author Jean-François Simon + */ +class HashParserTest extends TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new HashParser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('#testid', 'Hash[Element[*]#testid]'), + array('testel#testid', 'Hash[Element[testel]#testid]'), + array('testns|#testid', 'Hash[Element[testns|*]#testid]'), + array('testns|*#testid', 'Hash[Element[testns|*]#testid]'), + array('testns|testel#testid', 'Hash[Element[testns|testel]#testid]'), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/Parser/TokenStreamTest.php b/core/vendor/symfony/css-selector/Tests/Parser/TokenStreamTest.php new file mode 100644 index 0000000..44c751a --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/Parser/TokenStreamTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +class TokenStreamTest extends TestCase +{ + public function testGetNext() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'title', 3)); + + $this->assertSame($t1, $stream->getNext()); + $this->assertSame($t2, $stream->getNext()); + $this->assertSame($t3, $stream->getNext()); + } + + public function testGetPeek() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'title', 3)); + + $this->assertSame($t1, $stream->getPeek()); + $this->assertSame($t1, $stream->getNext()); + $this->assertSame($t2, $stream->getPeek()); + $this->assertSame($t2, $stream->getPeek()); + $this->assertSame($t2, $stream->getNext()); + } + + public function testGetNextIdentifier() + { + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + + $this->assertEquals('h1', $stream->getNextIdentifier()); + } + + public function testFailToGetNextIdentifier() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->getNextIdentifier(); + } + + public function testGetNextIdentifierOrStar() + { + $stream = new TokenStream(); + + $stream->push(new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $this->assertEquals('h1', $stream->getNextIdentifierOrStar()); + + $stream->push(new Token(Token::TYPE_DELIMITER, '*', 0)); + $this->assertNull($stream->getNextIdentifierOrStar()); + } + + public function testFailToGetNextIdentifierOrStar() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->getNextIdentifierOrStar(); + } + + public function testSkipWhitespace() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_WHITESPACE, ' ', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'h1', 3)); + + $stream->skipWhitespace(); + $this->assertSame($t1, $stream->getNext()); + + $stream->skipWhitespace(); + $this->assertSame($t3, $stream->getNext()); + } +} diff --git a/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/ids.html b/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/ids.html new file mode 100644 index 0000000..5799fad --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/ids.html @@ -0,0 +1,48 @@ + + + + +
    + + + + link +
      +
    1. content
    2. +
    3. +
      +
      +
    4. +
    5. +
    6. +
    7. +
    8. +
    9. +
    +

    + hi there + guy + + + + + + + +

    + + +
    +

    +
      +
    + + + + +
    +
    + diff --git a/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/lang.xml b/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/lang.xml new file mode 100644 index 0000000..14f8dbe --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/lang.xml @@ -0,0 +1,11 @@ + + a + b + c + d + e + f + + + + diff --git a/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/shakespear.html b/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/shakespear.html new file mode 100644 index 0000000..15d1ad3 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/XPath/Fixtures/shakespear.html @@ -0,0 +1,308 @@ + + + + + + +
    +
    +

    As You Like It

    +
    + by William Shakespeare +
    +
    +

    ACT I, SCENE III. A room in the palace.

    +
    +
    Enter CELIA and ROSALIND
    +
    +
    CELIA
    +
    +
    Why, cousin! why, Rosalind! Cupid have mercy! not a word?
    +
    +
    ROSALIND
    +
    +
    Not one to throw at a dog.
    +
    +
    CELIA
    +
    +
    No, thy words are too precious to be cast away upon
    +
    curs; throw some of them at me; come, lame me with reasons.
    +
    +
    ROSALIND
    +
    CELIA
    +
    +
    But is all this for your father?
    +
    +
    +
    Then there were two cousins laid up; when the one
    +
    should be lamed with reasons and the other mad
    +
    without any.
    +
    +
    ROSALIND
    +
    +
    No, some of it is for my child's father. O, how
    +
    full of briers is this working-day world!
    +
    +
    CELIA
    +
    +
    They are but burs, cousin, thrown upon thee in
    +
    holiday foolery: if we walk not in the trodden
    +
    paths our very petticoats will catch them.
    +
    +
    ROSALIND
    +
    +
    I could shake them off my coat: these burs are in my heart.
    +
    +
    CELIA
    +
    +
    Hem them away.
    +
    +
    ROSALIND
    +
    +
    I would try, if I could cry 'hem' and have him.
    +
    +
    CELIA
    +
    +
    Come, come, wrestle with thy affections.
    +
    +
    ROSALIND
    +
    +
    O, they take the part of a better wrestler than myself!
    +
    +
    CELIA
    +
    +
    O, a good wish upon you! you will try in time, in
    +
    despite of a fall. But, turning these jests out of
    +
    service, let us talk in good earnest: is it
    +
    possible, on such a sudden, you should fall into so
    +
    strong a liking with old Sir Rowland's youngest son?
    +
    +
    ROSALIND
    +
    +
    The duke my father loved his father dearly.
    +
    +
    CELIA
    +
    +
    Doth it therefore ensue that you should love his son
    +
    dearly? By this kind of chase, I should hate him,
    +
    for my father hated his father dearly; yet I hate
    +
    not Orlando.
    +
    +
    ROSALIND
    +
    +
    No, faith, hate him not, for my sake.
    +
    +
    CELIA
    +
    +
    Why should I not? doth he not deserve well?
    +
    +
    ROSALIND
    +
    +
    Let me love him for that, and do you love him
    +
    because I do. Look, here comes the duke.
    +
    +
    CELIA
    +
    +
    With his eyes full of anger.
    +
    Enter DUKE FREDERICK, with Lords
    +
    +
    DUKE FREDERICK
    +
    +
    Mistress, dispatch you with your safest haste
    +
    And get you from our court.
    +
    +
    ROSALIND
    +
    +
    Me, uncle?
    +
    +
    DUKE FREDERICK
    +
    +
    You, cousin
    +
    Within these ten days if that thou be'st found
    +
    So near our public court as twenty miles,
    +
    Thou diest for it.
    +
    +
    ROSALIND
    +
    +
    I do beseech your grace,
    +
    Let me the knowledge of my fault bear with me:
    +
    If with myself I hold intelligence
    +
    Or have acquaintance with mine own desires,
    +
    If that I do not dream or be not frantic,--
    +
    As I do trust I am not--then, dear uncle,
    +
    Never so much as in a thought unborn
    +
    Did I offend your highness.
    +
    +
    DUKE FREDERICK
    +
    +
    Thus do all traitors:
    +
    If their purgation did consist in words,
    +
    They are as innocent as grace itself:
    +
    Let it suffice thee that I trust thee not.
    +
    +
    ROSALIND
    +
    +
    Yet your mistrust cannot make me a traitor:
    +
    Tell me whereon the likelihood depends.
    +
    +
    DUKE FREDERICK
    +
    +
    Thou art thy father's daughter; there's enough.
    +
    +
    ROSALIND
    +
    +
    So was I when your highness took his dukedom;
    +
    So was I when your highness banish'd him:
    +
    Treason is not inherited, my lord;
    +
    Or, if we did derive it from our friends,
    +
    What's that to me? my father was no traitor:
    +
    Then, good my liege, mistake me not so much
    +
    To think my poverty is treacherous.
    +
    +
    CELIA
    +
    +
    Dear sovereign, hear me speak.
    +
    +
    DUKE FREDERICK
    +
    +
    Ay, Celia; we stay'd her for your sake,
    +
    Else had she with her father ranged along.
    +
    +
    CELIA
    +
    +
    I did not then entreat to have her stay;
    +
    It was your pleasure and your own remorse:
    +
    I was too young that time to value her;
    +
    But now I know her: if she be a traitor,
    +
    Why so am I; we still have slept together,
    +
    Rose at an instant, learn'd, play'd, eat together,
    +
    And wheresoever we went, like Juno's swans,
    +
    Still we went coupled and inseparable.
    +
    +
    DUKE FREDERICK
    +
    +
    She is too subtle for thee; and her smoothness,
    +
    Her very silence and her patience
    +
    Speak to the people, and they pity her.
    +
    Thou art a fool: she robs thee of thy name;
    +
    And thou wilt show more bright and seem more virtuous
    +
    When she is gone. Then open not thy lips:
    +
    Firm and irrevocable is my doom
    +
    Which I have pass'd upon her; she is banish'd.
    +
    +
    CELIA
    +
    +
    Pronounce that sentence then on me, my liege:
    +
    I cannot live out of her company.
    +
    +
    DUKE FREDERICK
    +
    +
    You are a fool. You, niece, provide yourself:
    +
    If you outstay the time, upon mine honour,
    +
    And in the greatness of my word, you die.
    +
    Exeunt DUKE FREDERICK and Lords
    +
    +
    CELIA
    +
    +
    O my poor Rosalind, whither wilt thou go?
    +
    Wilt thou change fathers? I will give thee mine.
    +
    I charge thee, be not thou more grieved than I am.
    +
    +
    ROSALIND
    +
    +
    I have more cause.
    +
    +
    CELIA
    +
    +
    Thou hast not, cousin;
    +
    Prithee be cheerful: know'st thou not, the duke
    +
    Hath banish'd me, his daughter?
    +
    +
    ROSALIND
    +
    +
    That he hath not.
    +
    +
    CELIA
    +
    +
    No, hath not? Rosalind lacks then the love
    +
    Which teacheth thee that thou and I am one:
    +
    Shall we be sunder'd? shall we part, sweet girl?
    +
    No: let my father seek another heir.
    +
    Therefore devise with me how we may fly,
    +
    Whither to go and what to bear with us;
    +
    And do not seek to take your change upon you,
    +
    To bear your griefs yourself and leave me out;
    +
    For, by this heaven, now at our sorrows pale,
    +
    Say what thou canst, I'll go along with thee.
    +
    +
    ROSALIND
    +
    +
    Why, whither shall we go?
    +
    +
    CELIA
    +
    +
    To seek my uncle in the forest of Arden.
    +
    +
    ROSALIND
    +
    +
    Alas, what danger will it be to us,
    +
    Maids as we are, to travel forth so far!
    +
    Beauty provoketh thieves sooner than gold.
    +
    +
    CELIA
    +
    +
    I'll put myself in poor and mean attire
    +
    And with a kind of umber smirch my face;
    +
    The like do you: so shall we pass along
    +
    And never stir assailants.
    +
    +
    ROSALIND
    +
    +
    Were it not better,
    +
    Because that I am more than common tall,
    +
    That I did suit me all points like a man?
    +
    A gallant curtle-axe upon my thigh,
    +
    A boar-spear in my hand; and--in my heart
    +
    Lie there what hidden woman's fear there will--
    +
    We'll have a swashing and a martial outside,
    +
    As many other mannish cowards have
    +
    That do outface it with their semblances.
    +
    +
    CELIA
    +
    +
    What shall I call thee when thou art a man?
    +
    +
    ROSALIND
    +
    +
    I'll have no worse a name than Jove's own page;
    +
    And therefore look you call me Ganymede.
    +
    But what will you be call'd?
    +
    +
    CELIA
    +
    +
    Something that hath a reference to my state
    +
    No longer Celia, but Aliena.
    +
    +
    ROSALIND
    +
    +
    But, cousin, what if we assay'd to steal
    +
    The clownish fool out of your father's court?
    +
    Would he not be a comfort to our travel?
    +
    +
    CELIA
    +
    +
    He'll go along o'er the wide world with me;
    +
    Leave me alone to woo him. Let's away,
    +
    And get our jewels and our wealth together,
    +
    Devise the fittest time and safest way
    +
    To hide us from pursuit that will be made
    +
    After my flight. Now go we in content
    +
    To liberty and not to banishment.
    +
    Exeunt
    +
    +
    +
    +
    + + diff --git a/core/vendor/symfony/css-selector/Tests/XPath/TranslatorTest.php b/core/vendor/symfony/css-selector/Tests/XPath/TranslatorTest.php new file mode 100644 index 0000000..6104582 --- /dev/null +++ b/core/vendor/symfony/css-selector/Tests/XPath/TranslatorTest.php @@ -0,0 +1,327 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\XPath; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension; +use Symfony\Component\CssSelector\XPath\Translator; + +class TranslatorTest extends TestCase +{ + /** @dataProvider getXpathLiteralTestData */ + public function testXpathLiteral($value, $literal) + { + $this->assertEquals($literal, Translator::getXpathLiteral($value)); + } + + /** @dataProvider getCssToXPathTestData */ + public function testCssToXPath($css, $xpath) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $this->assertEquals($xpath, $translator->cssToXPath($css, '')); + } + + /** @dataProvider getXmlLangTestData */ + public function testXmlLang($css, array $elementsId) + { + $translator = new Translator(); + $document = new \SimpleXMLElement(file_get_contents(__DIR__.'/Fixtures/lang.xml')); + $elements = $document->xpath($translator->cssToXPath($css)); + $this->assertCount(\count($elementsId), $elements); + foreach ($elements as $element) { + $this->assertTrue(\in_array($element->attributes()->id, $elementsId)); + } + } + + /** @dataProvider getHtmlIdsTestData */ + public function testHtmlIds($css, array $elementsId) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $document = new \DOMDocument(); + $document->strictErrorChecking = false; + $internalErrors = libxml_use_internal_errors(true); + $document->loadHTMLFile(__DIR__.'/Fixtures/ids.html'); + $document = simplexml_import_dom($document); + $elements = $document->xpath($translator->cssToXPath($css)); + $this->assertCount(\count($elementsId), $elementsId); + foreach ($elements as $element) { + if (null !== $element->attributes()->id) { + $this->assertTrue(\in_array($element->attributes()->id, $elementsId)); + } + } + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + } + + /** @dataProvider getHtmlShakespearTestData */ + public function testHtmlShakespear($css, $count) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $document = new \DOMDocument(); + $document->strictErrorChecking = false; + $document->loadHTMLFile(__DIR__.'/Fixtures/shakespear.html'); + $document = simplexml_import_dom($document); + $bodies = $document->xpath('//body'); + $elements = $bodies[0]->xpath($translator->cssToXPath($css)); + $this->assertCount($count, $elements); + } + + public function getXpathLiteralTestData() + { + return array( + array('foo', "'foo'"), + array("foo's bar", '"foo\'s bar"'), + array("foo's \"middle\" bar", 'concat(\'foo\', "\'", \'s "middle" bar\')'), + array("foo's 'middle' \"bar\"", 'concat(\'foo\', "\'", \'s \', "\'", \'middle\', "\'", \' "bar"\')'), + ); + } + + public function getCssToXPathTestData() + { + return array( + array('*', '*'), + array('e', 'e'), + array('*|e', 'e'), + array('e|f', 'e:f'), + array('e[foo]', 'e[@foo]'), + array('e[foo|bar]', 'e[@foo:bar]'), + array('e[foo="bar"]', "e[@foo = 'bar']"), + array('e[foo~="bar"]', "e[@foo and contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]"), + array('e[foo^="bar"]', "e[@foo and starts-with(@foo, 'bar')]"), + array('e[foo$="bar"]', "e[@foo and substring(@foo, string-length(@foo)-2) = 'bar']"), + array('e[foo*="bar"]', "e[@foo and contains(@foo, 'bar')]"), + array('e[foo!="bar"]', "e[not(@foo) or @foo != 'bar']"), + array('e[foo!="bar"][foo!="baz"]', "e[(not(@foo) or @foo != 'bar') and (not(@foo) or @foo != 'baz')]"), + array('e[hreflang|="en"]', "e[@hreflang and (@hreflang = 'en' or starts-with(@hreflang, 'en-'))]"), + array('e:nth-child(1)', "*/*[(name() = 'e') and (position() = 1)]"), + array('e:nth-last-child(1)', "*/*[(name() = 'e') and (position() = last() - 0)]"), + array('e:nth-last-child(2n+2)', "*/*[(name() = 'e') and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]"), + array('e:nth-of-type(1)', '*/e[position() = 1]'), + array('e:nth-last-of-type(1)', '*/e[position() = last() - 0]'), + array('div e:nth-last-of-type(1) .aclass', "div/descendant-or-self::*/e[position() = last() - 0]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]"), + array('e:first-child', "*/*[(name() = 'e') and (position() = 1)]"), + array('e:last-child', "*/*[(name() = 'e') and (position() = last())]"), + array('e:first-of-type', '*/e[position() = 1]'), + array('e:last-of-type', '*/e[position() = last()]'), + array('e:only-child', "*/*[(name() = 'e') and (last() = 1)]"), + array('e:only-of-type', 'e[last() = 1]'), + array('e:empty', 'e[not(*) and not(string-length())]'), + array('e:EmPTY', 'e[not(*) and not(string-length())]'), + array('e:root', 'e[not(parent::*)]'), + array('e:hover', 'e[0]'), + array('e:contains("foo")', "e[contains(string(.), 'foo')]"), + array('e:ConTains(foo)', "e[contains(string(.), 'foo')]"), + array('e.warning', "e[@class and contains(concat(' ', normalize-space(@class), ' '), ' warning ')]"), + array('e#myid', "e[@id = 'myid']"), + array('e:not(:nth-child(odd))', 'e[not(position() - 1 >= 0 and (position() - 1) mod 2 = 0)]'), + array('e:nOT(*)', 'e[0]'), + array('e f', 'e/descendant-or-self::*/f'), + array('e > f', 'e/f'), + array('e + f', "e/following-sibling::*[(name() = 'f') and (position() = 1)]"), + array('e ~ f', 'e/following-sibling::f'), + array('div#container p', "div[@id = 'container']/descendant-or-self::*/p"), + ); + } + + public function getXmlLangTestData() + { + return array( + array(':lang("EN")', array('first', 'second', 'third', 'fourth')), + array(':lang("en-us")', array('second', 'fourth')), + array(':lang(en-nz)', array('third')), + array(':lang(fr)', array('fifth')), + array(':lang(ru)', array('sixth')), + array(":lang('ZH')", array('eighth')), + array(':lang(de) :lang(zh)', array('eighth')), + array(':lang(en), :lang(zh)', array('first', 'second', 'third', 'fourth', 'eighth')), + array(':lang(es)', array()), + ); + } + + public function getHtmlIdsTestData() + { + return array( + array('div', array('outer-div', 'li-div', 'foobar-div')), + array('DIV', array('outer-div', 'li-div', 'foobar-div')), // case-insensitive in HTML + array('div div', array('li-div')), + array('div, div div', array('outer-div', 'li-div', 'foobar-div')), + array('a[name]', array('name-anchor')), + array('a[NAme]', array('name-anchor')), // case-insensitive in HTML: + array('a[rel]', array('tag-anchor', 'nofollow-anchor')), + array('a[rel="tag"]', array('tag-anchor')), + array('a[href*="localhost"]', array('tag-anchor')), + array('a[href*=""]', array()), + array('a[href^="http"]', array('tag-anchor', 'nofollow-anchor')), + array('a[href^="http:"]', array('tag-anchor')), + array('a[href^=""]', array()), + array('a[href$="org"]', array('nofollow-anchor')), + array('a[href$=""]', array()), + array('div[foobar~="bc"]', array('foobar-div')), + array('div[foobar~="cde"]', array('foobar-div')), + array('[foobar~="ab bc"]', array('foobar-div')), + array('[foobar~=""]', array()), + array('[foobar~=" \t"]', array()), + array('div[foobar~="cd"]', array()), + array('*[lang|="En"]', array('second-li')), + array('[lang|="En-us"]', array('second-li')), + // Attribute values are case sensitive + array('*[lang|="en"]', array()), + array('[lang|="en-US"]', array()), + array('*[lang|="e"]', array()), + // ... :lang() is not. + array(':lang("EN")', array('second-li', 'li-div')), + array('*:lang(en-US)', array('second-li', 'li-div')), + array(':lang("e")', array()), + array('li:nth-child(3)', array('third-li')), + array('li:nth-child(10)', array()), + array('li:nth-child(2n)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(even)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(2n+0)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(+2n+1)', array('first-li', 'third-li', 'fifth-li', 'seventh-li')), + array('li:nth-child(odd)', array('first-li', 'third-li', 'fifth-li', 'seventh-li')), + array('li:nth-child(2n+4)', array('fourth-li', 'sixth-li')), + array('li:nth-child(3n+1)', array('first-li', 'fourth-li', 'seventh-li')), + array('li:nth-child(n)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n-1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n+1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n+3)', array('third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(-n)', array()), + array('li:nth-child(-n-1)', array()), + array('li:nth-child(-n+1)', array('first-li')), + array('li:nth-child(-n+3)', array('first-li', 'second-li', 'third-li')), + array('li:nth-last-child(0)', array()), + array('li:nth-last-child(2n)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(even)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(2n+2)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(n)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n-1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n-3)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n+1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n+3)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li')), + array('li:nth-last-child(-n)', array()), + array('li:nth-last-child(-n-1)', array()), + array('li:nth-last-child(-n+1)', array('seventh-li')), + array('li:nth-last-child(-n+3)', array('fifth-li', 'sixth-li', 'seventh-li')), + array('ol:first-of-type', array('first-ol')), + array('ol:nth-child(1)', array('first-ol')), + array('ol:nth-of-type(2)', array('second-ol')), + array('ol:nth-last-of-type(1)', array('second-ol')), + array('span:only-child', array('foobar-span')), + array('li div:only-child', array('li-div')), + array('div *:only-child', array('li-div', 'foobar-span')), + array('p:only-of-type', array('paragraph')), + array('a:empty', array('name-anchor')), + array('a:EMpty', array('name-anchor')), + array('li:empty', array('third-li', 'fourth-li', 'fifth-li', 'sixth-li')), + array(':root', array('html')), + array('html:root', array('html')), + array('li:root', array()), + array('* :root', array()), + array('*:contains("link")', array('html', 'outer-div', 'tag-anchor', 'nofollow-anchor')), + array(':CONtains("link")', array('html', 'outer-div', 'tag-anchor', 'nofollow-anchor')), + array('*:contains("LInk")', array()), // case sensitive + array('*:contains("e")', array('html', 'nil', 'outer-div', 'first-ol', 'first-li', 'paragraph', 'p-em')), + array('*:contains("E")', array()), // case-sensitive + array('.a', array('first-ol')), + array('.b', array('first-ol')), + array('*.a', array('first-ol')), + array('ol.a', array('first-ol')), + array('.c', array('first-ol', 'third-li', 'fourth-li')), + array('*.c', array('first-ol', 'third-li', 'fourth-li')), + array('ol *.c', array('third-li', 'fourth-li')), + array('ol li.c', array('third-li', 'fourth-li')), + array('li ~ li.c', array('third-li', 'fourth-li')), + array('ol > li.c', array('third-li', 'fourth-li')), + array('#first-li', array('first-li')), + array('li#first-li', array('first-li')), + array('*#first-li', array('first-li')), + array('li div', array('li-div')), + array('li > div', array('li-div')), + array('div div', array('li-div')), + array('div > div', array()), + array('div>.c', array('first-ol')), + array('div > .c', array('first-ol')), + array('div + div', array('foobar-div')), + array('a ~ a', array('tag-anchor', 'nofollow-anchor')), + array('a[rel="tag"] ~ a', array('nofollow-anchor')), + array('ol#first-ol li:last-child', array('seventh-li')), + array('ol#first-ol *:last-child', array('li-div', 'seventh-li')), + array('#outer-div:first-child', array('outer-div')), + array('#outer-div :first-child', array('name-anchor', 'first-li', 'li-div', 'p-b', 'checkbox-fieldset-disabled', 'area-href')), + array('a[href]', array('tag-anchor', 'nofollow-anchor')), + array(':not(*)', array()), + array('a:not([href])', array('name-anchor')), + array('ol :Not(li[class])', array('first-li', 'second-li', 'li-div', 'fifth-li', 'sixth-li', 'seventh-li')), + // HTML-specific + array(':link', array('link-href', 'tag-anchor', 'nofollow-anchor', 'area-href')), + array(':visited', array()), + array(':enabled', array('link-href', 'tag-anchor', 'nofollow-anchor', 'checkbox-unchecked', 'text-checked', 'checkbox-checked', 'area-href')), + array(':disabled', array('checkbox-disabled', 'checkbox-disabled-checked', 'fieldset', 'checkbox-fieldset-disabled')), + array(':checked', array('checkbox-checked', 'checkbox-disabled-checked')), + ); + } + + public function getHtmlShakespearTestData() + { + return array( + array('*', 246), + array('div:contains(CELIA)', 26), + array('div:only-child', 22), // ? + array('div:nth-child(even)', 106), + array('div:nth-child(2n)', 106), + array('div:nth-child(odd)', 137), + array('div:nth-child(2n+1)', 137), + array('div:nth-child(n)', 243), + array('div:last-child', 53), + array('div:first-child', 51), + array('div > div', 242), + array('div + div', 190), + array('div ~ div', 190), + array('body', 1), + array('body div', 243), + array('div', 243), + array('div div', 242), + array('div div div', 241), + array('div, div, div', 243), + array('div, a, span', 243), + array('.dialog', 51), + array('div.dialog', 51), + array('div .dialog', 51), + array('div.character, div.dialog', 99), + array('div.direction.dialog', 0), + array('div.dialog.direction', 0), + array('div.dialog.scene', 1), + array('div.scene.scene', 1), + array('div.scene .scene', 0), + array('div.direction .dialog ', 0), + array('div .dialog .direction', 4), + array('div.dialog .dialog .direction', 4), + array('#speech5', 1), + array('div#speech5', 1), + array('div #speech5', 1), + array('div.scene div.dialog', 49), + array('div#scene1 div.dialog div', 142), + array('#scene1 #speech1', 1), + array('div[class]', 103), + array('div[class=dialog]', 50), + array('div[class^=dia]', 51), + array('div[class$=log]', 50), + array('div[class*=sce]', 1), + array('div[class|=dialog]', 50), // ? Seems right + array('div[class!=madeup]', 243), // ? Seems right + array('div[class~=dialog]', 51), // ? Seems right + ); + } +} diff --git a/core/vendor/symfony/css-selector/XPath/Extension/AbstractExtension.php b/core/vendor/symfony/css-selector/XPath/Extension/AbstractExtension.php new file mode 100644 index 0000000..026ac06 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Extension/AbstractExtension.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +/** + * XPath expression translator abstract extension. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +abstract class AbstractExtension implements ExtensionInterface +{ + /** + * {@inheritdoc} + */ + public function getNodeTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getCombinationTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getAttributeMatchingTranslators() + { + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php b/core/vendor/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php new file mode 100644 index 0000000..2078dca --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator attribute extension. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class AttributeMatchingExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getAttributeMatchingTranslators() + { + return array( + 'exists' => array($this, 'translateExists'), + '=' => array($this, 'translateEquals'), + '~=' => array($this, 'translateIncludes'), + '|=' => array($this, 'translateDashMatch'), + '^=' => array($this, 'translatePrefixMatch'), + '$=' => array($this, 'translateSuffixMatch'), + '*=' => array($this, 'translateSubstringMatch'), + '!=' => array($this, 'translateDifferent'), + ); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateExists(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($attribute); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateEquals(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value))); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateIncludes(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and contains(concat(\' \', normalize-space(%1$s), \' \'), %2$s)', + $attribute, + Translator::getXpathLiteral(' '.$value.' ') + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateDashMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf( + '%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))', + $attribute, + Translator::getXpathLiteral($value), + Translator::getXpathLiteral($value.'-') + )); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translatePrefixMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and starts-with(%1$s, %2$s)', + $attribute, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateSuffixMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s', + $attribute, + \strlen($value) - 1, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateSubstringMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and contains(%1$s, %2$s)', + $attribute, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateDifferent(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf( + $value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s', + $attribute, + Translator::getXpathLiteral($value) + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'attribute-matching'; + } +} diff --git a/core/vendor/symfony/css-selector/XPath/Extension/CombinationExtension.php b/core/vendor/symfony/css-selector/XPath/Extension/CombinationExtension.php new file mode 100644 index 0000000..0c9cc03 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Extension/CombinationExtension.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator combination extension. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class CombinationExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getCombinationTranslators() + { + return array( + ' ' => array($this, 'translateDescendant'), + '>' => array($this, 'translateChild'), + '+' => array($this, 'translateDirectAdjacent'), + '~' => array($this, 'translateIndirectAdjacent'), + ); + } + + /** + * @return XPathExpr + */ + public function translateDescendant(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/descendant-or-self::*/', $combinedXpath); + } + + /** + * @return XPathExpr + */ + public function translateChild(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/', $combinedXpath); + } + + /** + * @return XPathExpr + */ + public function translateDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath + ->join('/following-sibling::', $combinedXpath) + ->addNameTest() + ->addCondition('position() = 1'); + } + + /** + * @return XPathExpr + */ + public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/following-sibling::', $combinedXpath); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'combination'; + } +} diff --git a/core/vendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php b/core/vendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php new file mode 100644 index 0000000..3607022 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +/** + * XPath expression translator extension interface. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +interface ExtensionInterface +{ + /** + * Returns node translators. + * + * These callables will receive the node as first argument and the translator as second argument. + * + * @return callable[] + */ + public function getNodeTranslators(); + + /** + * Returns combination translators. + * + * @return callable[] + */ + public function getCombinationTranslators(); + + /** + * Returns function translators. + * + * @return callable[] + */ + public function getFunctionTranslators(); + + /** + * Returns pseudo-class translators. + * + * @return callable[] + */ + public function getPseudoClassTranslators(); + + /** + * Returns attribute operation translators. + * + * @return callable[] + */ + public function getAttributeMatchingTranslators(); + + /** + * Returns extension name. + * + * @return string + */ + public function getName(); +} diff --git a/core/vendor/symfony/css-selector/XPath/Extension/FunctionExtension.php b/core/vendor/symfony/css-selector/XPath/Extension/FunctionExtension.php new file mode 100644 index 0000000..4d34d0e --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Extension/FunctionExtension.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator function extension. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class FunctionExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array( + 'nth-child' => array($this, 'translateNthChild'), + 'nth-last-child' => array($this, 'translateNthLastChild'), + 'nth-of-type' => array($this, 'translateNthOfType'), + 'nth-last-of-type' => array($this, 'translateNthLastOfType'), + 'contains' => array($this, 'translateContains'), + 'lang' => array($this, 'translateLang'), + ); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * @param bool $last + * @param bool $addNameTest + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateNthChild(XPathExpr $xpath, FunctionNode $function, $last = false, $addNameTest = true) + { + try { + list($a, $b) = Parser::parseSeries($function->getArguments()); + } catch (SyntaxErrorException $e) { + throw new ExpressionErrorException(sprintf('Invalid series: %s', implode(', ', $function->getArguments())), 0, $e); + } + + $xpath->addStarPrefix(); + if ($addNameTest) { + $xpath->addNameTest(); + } + + if (0 === $a) { + return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b)); + } + + if ($a < 0) { + if ($b < 1) { + return $xpath->addCondition('false()'); + } + + $sign = '<='; + } else { + $sign = '>='; + } + + $expr = 'position()'; + + if ($last) { + $expr = 'last() - '.$expr; + --$b; + } + + if (0 !== $b) { + $expr .= ' - '.$b; + } + + $conditions = array(sprintf('%s %s 0', $expr, $sign)); + + if (1 !== $a && -1 !== $a) { + $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a); + } + + return $xpath->addCondition(implode(' and ', $conditions)); + + // todo: handle an+b, odd, even + // an+b means every-a, plus b, e.g., 2n+1 means odd + // 0n+b means b + // n+0 means a=1, i.e., all elements + // an means every a elements, i.e., 2n means even + // -n means -1n + // -1n+6 means elements 6 and previous + } + + /** + * @return XPathExpr + */ + public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function) + { + return $this->translateNthChild($xpath, $function, true); + } + + /** + * @return XPathExpr + */ + public function translateNthOfType(XPathExpr $xpath, FunctionNode $function) + { + return $this->translateNthChild($xpath, $function, false, false); + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.'); + } + + return $this->translateNthChild($xpath, $function, true, false); + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateContains(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException('Expected a single string or identifier for :contains(), got '.implode(', ', $arguments)); + } + } + + return $xpath->addCondition(sprintf( + 'contains(string(.), %s)', + Translator::getXpathLiteral($arguments[0]->getValue()) + )); + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLang(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments)); + } + } + + return $xpath->addCondition(sprintf( + 'lang(%s)', + Translator::getXpathLiteral($arguments[0]->getValue()) + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'function'; + } +} diff --git a/core/vendor/symfony/css-selector/XPath/Extension/HtmlExtension.php b/core/vendor/symfony/css-selector/XPath/Extension/HtmlExtension.php new file mode 100644 index 0000000..cd8e0d5 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Extension/HtmlExtension.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator HTML extension. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class HtmlExtension extends AbstractExtension +{ + public function __construct(Translator $translator) + { + $translator + ->getExtension('node') + ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true) + ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); + } + + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array( + 'checked' => array($this, 'translateChecked'), + 'link' => array($this, 'translateLink'), + 'disabled' => array($this, 'translateDisabled'), + 'enabled' => array($this, 'translateEnabled'), + 'selected' => array($this, 'translateSelected'), + 'invalid' => array($this, 'translateInvalid'), + 'hover' => array($this, 'translateHover'), + 'visited' => array($this, 'translateVisited'), + ); + } + + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array( + 'lang' => array($this, 'translateLang'), + ); + } + + /** + * @return XPathExpr + */ + public function translateChecked(XPathExpr $xpath) + { + return $xpath->addCondition( + '(@checked ' + ."and (name(.) = 'input' or name(.) = 'command')" + ."and (@type = 'checkbox' or @type = 'radio'))" + ); + } + + /** + * @return XPathExpr + */ + public function translateLink(XPathExpr $xpath) + { + return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')"); + } + + /** + * @return XPathExpr + */ + public function translateDisabled(XPathExpr $xpath) + { + return $xpath->addCondition( + '(' + .'@disabled and' + .'(' + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + ." or name(.) = 'command'" + ." or name(.) = 'fieldset'" + ." or name(.) = 'optgroup'" + ." or name(.) = 'option'" + .')' + .') or (' + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + .')' + .' and ancestor::fieldset[@disabled]' + ); + // todo: in the second half, add "and is not a descendant of that fieldset element's first legend element child, if any." + } + + /** + * @return XPathExpr + */ + public function translateEnabled(XPathExpr $xpath) + { + return $xpath->addCondition( + '(' + .'@href and (' + ."name(.) = 'a'" + ." or name(.) = 'link'" + ." or name(.) = 'area'" + .')' + .') or (' + .'(' + ."name(.) = 'command'" + ." or name(.) = 'fieldset'" + ." or name(.) = 'optgroup'" + .')' + .' and not(@disabled)' + .') or (' + .'(' + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + ." or name(.) = 'keygen'" + .')' + .' and not (@disabled or ancestor::fieldset[@disabled])' + .') or (' + ."name(.) = 'option' and not(" + .'@disabled or ancestor::optgroup[@disabled]' + .')' + .')' + ); + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLang(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments)); + } + } + + return $xpath->addCondition(sprintf( + 'ancestor-or-self::*[@lang][1][starts-with(concat(' + ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')" + .', %s)]', + 'lang', + Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-') + )); + } + + /** + * @return XPathExpr + */ + public function translateSelected(XPathExpr $xpath) + { + return $xpath->addCondition("(@selected and name(.) = 'option')"); + } + + /** + * @return XPathExpr + */ + public function translateInvalid(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * @return XPathExpr + */ + public function translateHover(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * @return XPathExpr + */ + public function translateVisited(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'html'; + } +} diff --git a/core/vendor/symfony/css-selector/XPath/Extension/NodeExtension.php b/core/vendor/symfony/css-selector/XPath/Extension/NodeExtension.php new file mode 100644 index 0000000..715d961 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Extension/NodeExtension.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Node; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator node extension. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class NodeExtension extends AbstractExtension +{ + const ELEMENT_NAME_IN_LOWER_CASE = 1; + const ATTRIBUTE_NAME_IN_LOWER_CASE = 2; + const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4; + + private $flags; + + /** + * @param int $flags + */ + public function __construct($flags = 0) + { + $this->flags = $flags; + } + + /** + * @param int $flag + * @param bool $on + * + * @return $this + */ + public function setFlag($flag, $on) + { + if ($on && !$this->hasFlag($flag)) { + $this->flags += $flag; + } + + if (!$on && $this->hasFlag($flag)) { + $this->flags -= $flag; + } + + return $this; + } + + /** + * @param int $flag + * + * @return bool + */ + public function hasFlag($flag) + { + return (bool) ($this->flags & $flag); + } + + /** + * {@inheritdoc} + */ + public function getNodeTranslators() + { + return array( + 'Selector' => array($this, 'translateSelector'), + 'CombinedSelector' => array($this, 'translateCombinedSelector'), + 'Negation' => array($this, 'translateNegation'), + 'Function' => array($this, 'translateFunction'), + 'Pseudo' => array($this, 'translatePseudo'), + 'Attribute' => array($this, 'translateAttribute'), + 'Class' => array($this, 'translateClass'), + 'Hash' => array($this, 'translateHash'), + 'Element' => array($this, 'translateElement'), + ); + } + + /** + * @return XPathExpr + */ + public function translateSelector(Node\SelectorNode $node, Translator $translator) + { + return $translator->nodeToXPath($node->getTree()); + } + + /** + * @return XPathExpr + */ + public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator) + { + return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector()); + } + + /** + * @return XPathExpr + */ + public function translateNegation(Node\NegationNode $node, Translator $translator) + { + $xpath = $translator->nodeToXPath($node->getSelector()); + $subXpath = $translator->nodeToXPath($node->getSubSelector()); + $subXpath->addNameTest(); + + if ($subXpath->getCondition()) { + return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition())); + } + + return $xpath->addCondition('0'); + } + + /** + * @return XPathExpr + */ + public function translateFunction(Node\FunctionNode $node, Translator $translator) + { + $xpath = $translator->nodeToXPath($node->getSelector()); + + return $translator->addFunction($xpath, $node); + } + + /** + * @return XPathExpr + */ + public function translatePseudo(Node\PseudoNode $node, Translator $translator) + { + $xpath = $translator->nodeToXPath($node->getSelector()); + + return $translator->addPseudoClass($xpath, $node->getIdentifier()); + } + + /** + * @return XPathExpr + */ + public function translateAttribute(Node\AttributeNode $node, Translator $translator) + { + $name = $node->getAttribute(); + $safe = $this->isSafeName($name); + + if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) { + $name = strtolower($name); + } + + if ($node->getNamespace()) { + $name = sprintf('%s:%s', $node->getNamespace(), $name); + $safe = $safe && $this->isSafeName($node->getNamespace()); + } + + $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name)); + $value = $node->getValue(); + $xpath = $translator->nodeToXPath($node->getSelector()); + + if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) { + $value = strtolower($value); + } + + return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value); + } + + /** + * @return XPathExpr + */ + public function translateClass(Node\ClassNode $node, Translator $translator) + { + $xpath = $translator->nodeToXPath($node->getSelector()); + + return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName()); + } + + /** + * @return XPathExpr + */ + public function translateHash(Node\HashNode $node, Translator $translator) + { + $xpath = $translator->nodeToXPath($node->getSelector()); + + return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId()); + } + + /** + * @return XPathExpr + */ + public function translateElement(Node\ElementNode $node) + { + $element = $node->getElement(); + + if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) { + $element = strtolower($element); + } + + if ($element) { + $safe = $this->isSafeName($element); + } else { + $element = '*'; + $safe = true; + } + + if ($node->getNamespace()) { + $element = sprintf('%s:%s', $node->getNamespace(), $element); + $safe = $safe && $this->isSafeName($node->getNamespace()); + } + + $xpath = new XPathExpr('', $element); + + if (!$safe) { + $xpath->addNameTest(); + } + + return $xpath; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'node'; + } + + /** + * Tests if given name is safe. + * + * @param string $name + * + * @return bool + */ + private function isSafeName($name) + { + return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name); + } +} diff --git a/core/vendor/symfony/css-selector/XPath/Extension/PseudoClassExtension.php b/core/vendor/symfony/css-selector/XPath/Extension/PseudoClassExtension.php new file mode 100644 index 0000000..378dfb7 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Extension/PseudoClassExtension.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator pseudo-class extension. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class PseudoClassExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array( + 'root' => array($this, 'translateRoot'), + 'first-child' => array($this, 'translateFirstChild'), + 'last-child' => array($this, 'translateLastChild'), + 'first-of-type' => array($this, 'translateFirstOfType'), + 'last-of-type' => array($this, 'translateLastOfType'), + 'only-child' => array($this, 'translateOnlyChild'), + 'only-of-type' => array($this, 'translateOnlyOfType'), + 'empty' => array($this, 'translateEmpty'), + ); + } + + /** + * @return XPathExpr + */ + public function translateRoot(XPathExpr $xpath) + { + return $xpath->addCondition('not(parent::*)'); + } + + /** + * @return XPathExpr + */ + public function translateFirstChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('position() = 1'); + } + + /** + * @return XPathExpr + */ + public function translateLastChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('position() = last()'); + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateFirstOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:first-of-type" is not implemented.'); + } + + return $xpath + ->addStarPrefix() + ->addCondition('position() = 1'); + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLastOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:last-of-type" is not implemented.'); + } + + return $xpath + ->addStarPrefix() + ->addCondition('position() = last()'); + } + + /** + * @return XPathExpr + */ + public function translateOnlyChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('last() = 1'); + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateOnlyOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:only-of-type" is not implemented.'); + } + + return $xpath->addCondition('last() = 1'); + } + + /** + * @return XPathExpr + */ + public function translateEmpty(XPathExpr $xpath) + { + return $xpath->addCondition('not(*) and not(string-length())'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pseudo-class'; + } +} diff --git a/core/vendor/symfony/css-selector/XPath/Translator.php b/core/vendor/symfony/css-selector/XPath/Translator.php new file mode 100644 index 0000000..f8585a0 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/Translator.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Node\NodeInterface; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class Translator implements TranslatorInterface +{ + private $mainParser; + + /** + * @var ParserInterface[] + */ + private $shortcutParsers = array(); + + /** + * @var Extension\ExtensionInterface[] + */ + private $extensions = array(); + + private $nodeTranslators = array(); + private $combinationTranslators = array(); + private $functionTranslators = array(); + private $pseudoClassTranslators = array(); + private $attributeMatchingTranslators = array(); + + public function __construct(ParserInterface $parser = null) + { + $this->mainParser = $parser ?: new Parser(); + + $this + ->registerExtension(new Extension\NodeExtension()) + ->registerExtension(new Extension\CombinationExtension()) + ->registerExtension(new Extension\FunctionExtension()) + ->registerExtension(new Extension\PseudoClassExtension()) + ->registerExtension(new Extension\AttributeMatchingExtension()) + ; + } + + /** + * @param string $element + * + * @return string + */ + public static function getXpathLiteral($element) + { + if (false === strpos($element, "'")) { + return "'".$element."'"; + } + + if (false === strpos($element, '"')) { + return '"'.$element.'"'; + } + + $string = $element; + $parts = array(); + while (true) { + if (false !== $pos = strpos($string, "'")) { + $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = "\"'\""; + $string = substr($string, $pos + 1); + } else { + $parts[] = "'$string'"; + break; + } + } + + return sprintf('concat(%s)', implode(', ', $parts)); + } + + /** + * {@inheritdoc} + */ + public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::') + { + $selectors = $this->parseSelectors($cssExpr); + + /** @var SelectorNode $selector */ + foreach ($selectors as $index => $selector) { + if (null !== $selector->getPseudoElement()) { + throw new ExpressionErrorException('Pseudo-elements are not supported.'); + } + + $selectors[$index] = $this->selectorToXPath($selector, $prefix); + } + + return implode(' | ', $selectors); + } + + /** + * {@inheritdoc} + */ + public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::') + { + return ($prefix ?: '').$this->nodeToXPath($selector); + } + + /** + * Registers an extension. + * + * @return $this + */ + public function registerExtension(Extension\ExtensionInterface $extension) + { + $this->extensions[$extension->getName()] = $extension; + + $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators()); + $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators()); + $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators()); + $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators()); + $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators()); + + return $this; + } + + /** + * @param string $name + * + * @return Extension\ExtensionInterface + * + * @throws ExpressionErrorException + */ + public function getExtension($name) + { + if (!isset($this->extensions[$name])) { + throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name)); + } + + return $this->extensions[$name]; + } + + /** + * Registers a shortcut parser. + * + * @return $this + */ + public function registerParserShortcut(ParserInterface $shortcut) + { + $this->shortcutParsers[] = $shortcut; + + return $this; + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function nodeToXPath(NodeInterface $node) + { + if (!isset($this->nodeTranslators[$node->getNodeName()])) { + throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName())); + } + + return \call_user_func($this->nodeTranslators[$node->getNodeName()], $node, $this); + } + + /** + * @param string $combiner + * @param NodeInterface $xpath + * @param NodeInterface $combinedXpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addCombination($combiner, NodeInterface $xpath, NodeInterface $combinedXpath) + { + if (!isset($this->combinationTranslators[$combiner])) { + throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner)); + } + + return \call_user_func($this->combinationTranslators[$combiner], $this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath)); + } + + /** + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addFunction(XPathExpr $xpath, FunctionNode $function) + { + if (!isset($this->functionTranslators[$function->getName()])) { + throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName())); + } + + return \call_user_func($this->functionTranslators[$function->getName()], $xpath, $function); + } + + /** + * @param XPathExpr $xpath + * @param string $pseudoClass + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addPseudoClass(XPathExpr $xpath, $pseudoClass) + { + if (!isset($this->pseudoClassTranslators[$pseudoClass])) { + throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass)); + } + + return \call_user_func($this->pseudoClassTranslators[$pseudoClass], $xpath); + } + + /** + * @param XPathExpr $xpath + * @param string $operator + * @param string $attribute + * @param string $value + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addAttributeMatching(XPathExpr $xpath, $operator, $attribute, $value) + { + if (!isset($this->attributeMatchingTranslators[$operator])) { + throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator)); + } + + return \call_user_func($this->attributeMatchingTranslators[$operator], $xpath, $attribute, $value); + } + + /** + * @param string $css + * + * @return SelectorNode[] + */ + private function parseSelectors($css) + { + foreach ($this->shortcutParsers as $shortcut) { + $tokens = $shortcut->parse($css); + + if (!empty($tokens)) { + return $tokens; + } + } + + return $this->mainParser->parse($css); + } +} diff --git a/core/vendor/symfony/css-selector/XPath/TranslatorInterface.php b/core/vendor/symfony/css-selector/XPath/TranslatorInterface.php new file mode 100644 index 0000000..0b5de83 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/TranslatorInterface.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath; + +use Symfony\Component\CssSelector\Node\SelectorNode; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +interface TranslatorInterface +{ + /** + * Translates a CSS selector to an XPath expression. + * + * @param string $cssExpr + * @param string $prefix + * + * @return string + */ + public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::'); + + /** + * Translates a parsed selector node to an XPath expression. + * + * @param SelectorNode $selector + * @param string $prefix + * + * @return string + */ + public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::'); +} diff --git a/core/vendor/symfony/css-selector/XPath/XPathExpr.php b/core/vendor/symfony/css-selector/XPath/XPathExpr.php new file mode 100644 index 0000000..63e3ac3 --- /dev/null +++ b/core/vendor/symfony/css-selector/XPath/XPathExpr.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + * + * @internal + */ +class XPathExpr +{ + private $path; + private $element; + private $condition; + + /** + * @param string $path + * @param string $element + * @param string $condition + * @param bool $starPrefix + */ + public function __construct($path = '', $element = '*', $condition = '', $starPrefix = false) + { + $this->path = $path; + $this->element = $element; + $this->condition = $condition; + + if ($starPrefix) { + $this->addStarPrefix(); + } + } + + /** + * @return string + */ + public function getElement() + { + return $this->element; + } + + /** + * @param $condition + * + * @return $this + */ + public function addCondition($condition) + { + $this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; + + return $this; + } + + /** + * @return string + */ + public function getCondition() + { + return $this->condition; + } + + /** + * @return $this + */ + public function addNameTest() + { + if ('*' !== $this->element) { + $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); + $this->element = '*'; + } + + return $this; + } + + /** + * @return $this + */ + public function addStarPrefix() + { + $this->path .= '*/'; + + return $this; + } + + /** + * Joins another XPathExpr with a combiner. + * + * @param string $combiner + * @param XPathExpr $expr + * + * @return $this + */ + public function join($combiner, self $expr) + { + $path = $this->__toString().$combiner; + + if ('*/' !== $expr->path) { + $path .= $expr->path; + } + + $this->path = $path; + $this->element = $expr->element; + $this->condition = $expr->condition; + + return $this; + } + + /** + * @return string + */ + public function __toString() + { + $path = $this->path.$this->element; + $condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']'; + + return $path.$condition; + } +} diff --git a/core/vendor/symfony/css-selector/composer.json b/core/vendor/symfony/css-selector/composer.json new file mode 100644 index 0000000..e5bbdcc --- /dev/null +++ b/core/vendor/symfony/css-selector/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/css-selector", + "type": "library", + "description": "Symfony CssSelector Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\CssSelector\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/core/vendor/symfony/css-selector/phpunit.xml.dist b/core/vendor/symfony/css-selector/phpunit.xml.dist new file mode 100644 index 0000000..a8e537e --- /dev/null +++ b/core/vendor/symfony/css-selector/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/core/vendor/symfony/debug/.gitignore b/core/vendor/symfony/debug/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/core/vendor/symfony/debug/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/core/vendor/symfony/debug/CHANGELOG.md b/core/vendor/symfony/debug/CHANGELOG.md new file mode 100644 index 0000000..31f0de9 --- /dev/null +++ b/core/vendor/symfony/debug/CHANGELOG.md @@ -0,0 +1,35 @@ +CHANGELOG +========= + +2.7.0 +----- + +* added deprecations checking for parent interfaces/classes to DebugClassLoader +* added ZTS support to symfony_debug extension +* added symfony_debug_backtrace() to symfony_debug extension + to track the backtrace of fatal errors + +2.6.0 +----- + +* generalized ErrorHandler and ExceptionHandler, + with some new methods and others deprecated +* enhanced error messages for uncaught exceptions + +2.5.0 +----- + +* added ExceptionHandler::setHandler() +* added UndefinedMethodFatalErrorHandler +* deprecated DummyException + +2.4.0 +----- + + * added a DebugClassLoader able to wrap any autoloader providing a findFile method + * improved error messages for not found classes and functions + +2.3.0 +----- + + * added the component diff --git a/core/vendor/symfony/debug/Debug.php b/core/vendor/symfony/debug/Debug.php new file mode 100644 index 0000000..9ae3496 --- /dev/null +++ b/core/vendor/symfony/debug/Debug.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +/** + * Registers all the debug tools. + * + * @author Fabien Potencier + */ +class Debug +{ + private static $enabled = false; + + /** + * Enables the debug tools. + * + * This method registers an error handler and an exception handler. + * + * If the Symfony ClassLoader component is available, a special + * class loader is also registered. + * + * @param int $errorReportingLevel The level of error reporting you want + * @param bool $displayErrors Whether to display errors (for development) or just log them (for production) + */ + public static function enable($errorReportingLevel = null, $displayErrors = true) + { + if (static::$enabled) { + return; + } + + static::$enabled = true; + + if (null !== $errorReportingLevel) { + error_reporting($errorReportingLevel); + } else { + error_reporting(-1); + } + + if (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) { + ini_set('display_errors', 0); + ExceptionHandler::register(); + } elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) { + // CLI - display errors only if they're not already logged to STDERR + ini_set('display_errors', 1); + } + $handler = ErrorHandler::register(); + if (!$displayErrors) { + $handler->throwAt(0, true); + } + + DebugClassLoader::enable(); + } +} diff --git a/core/vendor/symfony/debug/DebugClassLoader.php b/core/vendor/symfony/debug/DebugClassLoader.php new file mode 100644 index 0000000..c34605c --- /dev/null +++ b/core/vendor/symfony/debug/DebugClassLoader.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +/** + * Autoloader checking if the class is really defined in the file found. + * + * The ClassLoader will wrap all registered autoloaders + * and will throw an exception if a file is found but does + * not declare the class. + * + * @author Fabien Potencier + * @author Christophe Coevoet + * @author Nicolas Grekas + */ +class DebugClassLoader +{ + private $classLoader; + private $isFinder; + private $loaded = array(); + private $wasFinder; + private static $caseCheck; + private static $deprecated = array(); + private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null'); + private static $darwinCache = array('/' => array('/', array())); + + /** + * @param callable|object $classLoader Passing an object is @deprecated since version 2.5 and support for it will be removed in 3.0 + */ + public function __construct($classLoader) + { + $this->wasFinder = is_object($classLoader) && method_exists($classLoader, 'findFile'); + + if ($this->wasFinder) { + @trigger_error('The '.__METHOD__.' method will no longer support receiving an object into its $classLoader argument in 3.0.', E_USER_DEPRECATED); + $this->classLoader = array($classLoader, 'loadClass'); + $this->isFinder = true; + } else { + $this->classLoader = $classLoader; + $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile'); + } + + if (!isset(self::$caseCheck)) { + $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR); + $i = strrpos($file, DIRECTORY_SEPARATOR); + $dir = substr($file, 0, 1 + $i); + $file = substr($file, 1 + $i); + $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); + $test = realpath($dir.$test); + + if (false === $test || false === $i) { + // filesystem is case sensitive + self::$caseCheck = 0; + } elseif (substr($test, -strlen($file)) === $file) { + // filesystem is case insensitive and realpath() normalizes the case of characters + self::$caseCheck = 1; + } elseif (false !== stripos(PHP_OS, 'darwin')) { + // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters + self::$caseCheck = 2; + } else { + // filesystem case checks failed, fallback to disabling them + self::$caseCheck = 0; + } + } + } + + /** + * Gets the wrapped class loader. + * + * @return callable|object A class loader. Since version 2.5, returning an object is @deprecated and support for it will be removed in 3.0 + */ + public function getClassLoader() + { + return $this->wasFinder ? $this->classLoader[0] : $this->classLoader; + } + + /** + * Wraps all autoloaders. + */ + public static function enable() + { + // Ensures we don't hit https://bugs.php.net/42098 + class_exists('Symfony\Component\Debug\ErrorHandler'); + class_exists('Psr\Log\LogLevel'); + + if (!is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (!is_array($function) || !$function[0] instanceof self) { + $function = array(new static($function), 'loadClass'); + } + + spl_autoload_register($function); + } + } + + /** + * Disables the wrapping. + */ + public static function disable() + { + if (!is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (is_array($function) && $function[0] instanceof self) { + $function = $function[0]->getClassLoader(); + } + + spl_autoload_register($function); + } + } + + /** + * Finds a file by class name. + * + * @param string $class A class name to resolve to file + * + * @return string|null + * + * @deprecated since version 2.5, to be removed in 3.0. + */ + public function findFile($class) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); + + if ($this->wasFinder) { + return $this->classLoader[0]->findFile($class); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + * + * @throws \RuntimeException + */ + public function loadClass($class) + { + ErrorHandler::stackErrors(); + + try { + if ($this->isFinder && !isset($this->loaded[$class])) { + $this->loaded[$class] = true; + if ($file = $this->classLoader[0]->findFile($class)) { + require $file; + } + } else { + call_user_func($this->classLoader, $class); + $file = false; + } + } catch (\Exception $e) { + ErrorHandler::unstackErrors(); + + throw $e; + } catch (\Throwable $e) { + ErrorHandler::unstackErrors(); + + throw $e; + } + + ErrorHandler::unstackErrors(); + + $exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false)); + + if ($class && '\\' === $class[0]) { + $class = substr($class, 1); + } + + if ($exists) { + $refl = new \ReflectionClass($class); + $name = $refl->getName(); + + if ($name !== $class && 0 === strcasecmp($name, $class)) { + throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name)); + } + + if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { + @trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED); + } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { + self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); + } else { + if (2 > $len = 1 + (strpos($name, '\\') ?: strpos($name, '_'))) { + $len = 0; + $ns = ''; + } else { + $ns = substr($name, 0, $len); + } + $parent = get_parent_class($class); + + if (!$parent || strncmp($ns, $parent, $len)) { + if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { + @trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); + } + + foreach (class_implements($class) as $interface) { + if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len) && !is_subclass_of($parent, $interface)) { + @trigger_error(sprintf('The %s %s %s that is deprecated %s', $name, interface_exists($class) ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); + } + } + } + } + } + + if ($file) { + if (!$exists) { + if (false !== strpos($class, '/')) { + throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); + } + + throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); + } + if (self::$caseCheck) { + $real = explode('\\', $class.strrchr($file, '.')); + $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file)); + + $i = count($tail) - 1; + $j = count($real) - 1; + + while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { + --$i; + --$j; + } + + array_splice($tail, 0, $i + 1); + } + if (self::$caseCheck && $tail) { + $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail); + $tailLen = strlen($tail); + $real = $refl->getFileName(); + + if (2 === self::$caseCheck) { + // realpath() on MacOSX doesn't normalize the case of characters + + $i = 1 + strrpos($real, '/'); + $file = substr($real, $i); + $real = substr($real, 0, $i); + + if (isset(self::$darwinCache[$real])) { + $kDir = $real; + } else { + $kDir = strtolower($real); + + if (isset(self::$darwinCache[$kDir])) { + $real = self::$darwinCache[$kDir][0]; + } else { + $dir = getcwd(); + chdir($real); + $real = getcwd().'/'; + chdir($dir); + + $dir = $real; + $k = $kDir; + $i = strlen($dir) - 1; + while (!isset(self::$darwinCache[$k])) { + self::$darwinCache[$k] = array($dir, array()); + self::$darwinCache[$dir] = &self::$darwinCache[$k]; + + while ('/' !== $dir[--$i]) { + } + $k = substr($k, 0, ++$i); + $dir = substr($dir, 0, $i--); + } + } + } + + $dirFiles = self::$darwinCache[$kDir][1]; + + if (isset($dirFiles[$file])) { + $kFile = $file; + } else { + $kFile = strtolower($file); + + if (!isset($dirFiles[$kFile])) { + foreach (scandir($real, 2) as $f) { + if ('.' !== $f[0]) { + $dirFiles[$f] = $f; + if ($f === $file) { + $kFile = $k = $file; + } elseif ($f !== $k = strtolower($f)) { + $dirFiles[$k] = $f; + } + } + } + self::$darwinCache[$kDir][1] = $dirFiles; + } + } + + $real .= $dirFiles[$kFile]; + } + + if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) + && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) + ) { + throw new \RuntimeException(sprintf('Case mismatch between class and real file names: %s vs %s in %s', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1))); + } + } + + return true; + } + } +} diff --git a/core/vendor/symfony/debug/ErrorHandler.php b/core/vendor/symfony/debug/ErrorHandler.php new file mode 100644 index 0000000..4671b85 --- /dev/null +++ b/core/vendor/symfony/debug/ErrorHandler.php @@ -0,0 +1,824 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Psr\Log\LogLevel; +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\ContextErrorException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\FatalThrowableError; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; + +/** + * A generic ErrorHandler for the PHP engine. + * + * Provides five bit fields that control how errors are handled: + * - thrownErrors: errors thrown as \ErrorException + * - loggedErrors: logged errors, when not @-silenced + * - scopedErrors: errors thrown or logged with their local context + * - tracedErrors: errors logged with their stack trace, only once for repeated errors + * - screamedErrors: never @-silenced errors + * + * Each error level can be logged by a dedicated PSR-3 logger object. + * Screaming only applies to logging. + * Throwing takes precedence over logging. + * Uncaught exceptions are logged as E_ERROR. + * E_DEPRECATED and E_USER_DEPRECATED levels never throw. + * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. + * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. + * As errors have a performance cost, repeated errors are all logged, so that the developer + * can see them and weight them as more important to fix than others of the same level. + * + * @author Nicolas Grekas + */ +class ErrorHandler +{ + /** + * @deprecated since version 2.6, to be removed in 3.0. + */ + const TYPE_DEPRECATION = -100; + + private $levels = array( + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + E_NOTICE => 'Notice', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_WARNING => 'Warning', + E_USER_WARNING => 'User Warning', + E_COMPILE_WARNING => 'Compile Warning', + E_CORE_WARNING => 'Core Warning', + E_USER_ERROR => 'User Error', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + E_COMPILE_ERROR => 'Compile Error', + E_PARSE => 'Parse Error', + E_ERROR => 'Error', + E_CORE_ERROR => 'Core Error', + ); + + private $loggers = array( + E_DEPRECATED => array(null, LogLevel::INFO), + E_USER_DEPRECATED => array(null, LogLevel::INFO), + E_NOTICE => array(null, LogLevel::WARNING), + E_USER_NOTICE => array(null, LogLevel::WARNING), + E_STRICT => array(null, LogLevel::WARNING), + E_WARNING => array(null, LogLevel::WARNING), + E_USER_WARNING => array(null, LogLevel::WARNING), + E_COMPILE_WARNING => array(null, LogLevel::WARNING), + E_CORE_WARNING => array(null, LogLevel::WARNING), + E_USER_ERROR => array(null, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), + E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), + E_PARSE => array(null, LogLevel::CRITICAL), + E_ERROR => array(null, LogLevel::CRITICAL), + E_CORE_ERROR => array(null, LogLevel::CRITICAL), + ); + + private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE + private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE + private $loggedErrors = 0; + + private $loggedTraces = array(); + private $isRecursive = 0; + private $isRoot = false; + private $exceptionHandler; + + private static $reservedMemory; + private static $stackedErrors = array(); + private static $stackedErrorLevels = array(); + private static $exitCode = 0; + + /** + * Same init value as thrownErrors. + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + private $displayErrors = 0x1FFF; + + /** + * Registers the error handler. + * + * @param self|null|int $handler The handler to register, or @deprecated (since version 2.6, to be removed in 3.0) bit field of thrown levels + * @param bool $replace Whether to replace or not any existing handler + * + * @return self The registered error handler + */ + public static function register($handler = null, $replace = true) + { + if (null === self::$reservedMemory) { + self::$reservedMemory = str_repeat('x', 10240); + register_shutdown_function(__CLASS__.'::handleFatalError'); + } + + $levels = -1; + + if ($handlerIsNew = !$handler instanceof self) { + // @deprecated polymorphism, to be removed in 3.0 + if (null !== $handler) { + $levels = $replace ? $handler : 0; + $replace = true; + } + $handler = new static(); + } + + if (null === $prev = set_error_handler(array($handler, 'handleError'))) { + restore_error_handler(); + // Specifying the error types earlier would expose us to https://bugs.php.net/63206 + set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors); + $handler->isRoot = true; + } + + if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) { + $handler = $prev[0]; + $replace = false; + } + if (!$replace && $prev) { + restore_error_handler(); + $handlerIsRegistered = is_array($prev) && $handler === $prev[0]; + } else { + $handlerIsRegistered = true; + } + if (is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) { + restore_exception_handler(); + if (!$handlerIsRegistered) { + $handler = $prev[0]; + } elseif ($handler !== $prev[0] && $replace) { + set_exception_handler(array($handler, 'handleException')); + $p = $prev[0]->setExceptionHandler(null); + $handler->setExceptionHandler($p); + $prev[0]->setExceptionHandler($p); + } + } else { + $handler->setExceptionHandler($prev); + } + + $handler->throwAt($levels & $handler->thrownErrors, true); + + return $handler; + } + + /** + * Sets a logger to non assigned errors levels. + * + * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param bool $replace Whether to replace or not any existing logger + */ + public function setDefaultLogger(LoggerInterface $logger, $levels = null, $replace = false) + { + $loggers = array(); + + if (is_array($levels)) { + foreach ($levels as $type => $logLevel) { + if (empty($this->loggers[$type][0]) || $replace) { + $loggers[$type] = array($logger, $logLevel); + } + } + } else { + if (null === $levels) { + $levels = E_ALL | E_STRICT; + } + foreach ($this->loggers as $type => $log) { + if (($type & $levels) && (empty($log[0]) || $replace)) { + $log[0] = $logger; + $loggers[$type] = $log; + } + } + } + + $this->setLoggers($loggers); + } + + /** + * Sets a logger for each error level. + * + * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map + * + * @return array The previous map + * + * @throws \InvalidArgumentException + */ + public function setLoggers(array $loggers) + { + $prevLogged = $this->loggedErrors; + $prev = $this->loggers; + + foreach ($loggers as $type => $log) { + if (!isset($prev[$type])) { + throw new \InvalidArgumentException('Unknown error type: '.$type); + } + if (!is_array($log)) { + $log = array($log); + } elseif (!array_key_exists(0, $log)) { + throw new \InvalidArgumentException('No logger provided'); + } + if (null === $log[0]) { + $this->loggedErrors &= ~$type; + } elseif ($log[0] instanceof LoggerInterface) { + $this->loggedErrors |= $type; + } else { + throw new \InvalidArgumentException('Invalid logger provided'); + } + $this->loggers[$type] = $log + $prev[$type]; + } + $this->reRegister($prevLogged | $this->thrownErrors); + + return $prev; + } + + /** + * Sets a user exception handler. + * + * @param callable $handler A handler that will be called on Exception + * + * @return callable|null The previous exception handler + * + * @throws \InvalidArgumentException + */ + public function setExceptionHandler($handler) + { + if (null !== $handler && !is_callable($handler)) { + throw new \LogicException('The exception handler must be a valid PHP callable.'); + } + $prev = $this->exceptionHandler; + $this->exceptionHandler = $handler; + + return $prev; + } + + /** + * Sets the PHP error levels that throw an exception when a PHP error occurs. + * + * @param int $levels A bit field of E_* constants for thrown errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function throwAt($levels, $replace = false) + { + $prev = $this->thrownErrors; + $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; + if (!$replace) { + $this->thrownErrors |= $prev; + } + $this->reRegister($prev | $this->loggedErrors); + + // $this->displayErrors is @deprecated since version 2.6 + $this->displayErrors = $this->thrownErrors; + + return $prev; + } + + /** + * Sets the PHP error levels for which local variables are preserved. + * + * @param int $levels A bit field of E_* constants for scoped errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function scopeAt($levels, $replace = false) + { + $prev = $this->scopedErrors; + $this->scopedErrors = (int) $levels; + if (!$replace) { + $this->scopedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the PHP error levels for which the stack trace is preserved. + * + * @param int $levels A bit field of E_* constants for traced errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function traceAt($levels, $replace = false) + { + $prev = $this->tracedErrors; + $this->tracedErrors = (int) $levels; + if (!$replace) { + $this->tracedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the error levels where the @-operator is ignored. + * + * @param int $levels A bit field of E_* constants for screamed errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function screamAt($levels, $replace = false) + { + $prev = $this->screamedErrors; + $this->screamedErrors = (int) $levels; + if (!$replace) { + $this->screamedErrors |= $prev; + } + + return $prev; + } + + /** + * Re-registers as a PHP error handler if levels changed. + */ + private function reRegister($prev) + { + if ($prev !== $this->thrownErrors | $this->loggedErrors) { + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler === $this) { + restore_error_handler(); + if ($this->isRoot) { + set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors); + } else { + set_error_handler(array($this, 'handleError')); + } + } + } + } + + /** + * Handles errors by filtering then logging them according to the configured bit fields. + * + * @param int $type One of the E_* constants + * @param string $message + * @param string $file + * @param int $line + * + * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself + * + * @throws \ErrorException When $this->thrownErrors requests so + * + * @internal + */ + public function handleError($type, $message, $file, $line) + { + $level = error_reporting(); + $silenced = 0 === ($level & $type); + $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; + $log = $this->loggedErrors & $type; + $throw = $this->thrownErrors & $type & $level; + $type &= $level | $this->screamedErrors; + + if (!$type || (!$log && !$throw)) { + return !$silenced && $type && $log; + } + $scope = $this->scopedErrors & $type; + + if (4 < $numArgs = func_num_args()) { + $context = $scope ? (func_get_arg(4) ?: array()) : array(); + $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM + } else { + $context = array(); + $backtrace = null; + } + + if (isset($context['GLOBALS']) && $scope) { + $e = $context; // Whatever the signature of the method, + unset($e['GLOBALS'], $context); // $context is always a reference in 5.3 + $context = $e; + } + + if (null !== $backtrace && $type & E_ERROR) { + // E_ERROR fatal errors are triggered on HHVM when + // hhvm.error_handling.call_user_handler_on_fatals=1 + // which is the way to get their backtrace. + $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace')); + + return true; + } + + if ($throw) { + if ($scope && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) { + // Checking for class existence is a work around for https://bugs.php.net/42098 + $throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context); + } else { + $throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line); + } + + if (\PHP_VERSION_ID <= 50407 && (\PHP_VERSION_ID >= 50400 || \PHP_VERSION_ID <= 50317)) { + // Exceptions thrown from error handlers are sometimes not caught by the exception + // handler and shutdown handlers are bypassed before 5.4.8/5.3.18. + // We temporarily re-enable display_errors to prevent any blank page related to this bug. + + $throw->errorHandlerCanary = new ErrorHandlerCanary(); + } + + throw $throw; + } + + // For duplicated errors, log the trace only once + $e = md5("{$type}/{$line}/{$file}\x00{$message}", true); + $trace = true; + + if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) { + $trace = false; + } else { + $this->loggedTraces[$e] = 1; + } + + $e = compact('type', 'file', 'line', 'level'); + + if ($type & $level) { + if ($scope) { + $e['scope_vars'] = $context; + if ($trace) { + $e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); + } + } elseif ($trace) { + if (null === $backtrace) { + $e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } else { + foreach ($backtrace as &$frame) { + unset($frame['args'], $frame); + } + $e['stack'] = $backtrace; + } + } + } + + if ($this->isRecursive) { + $log = 0; + } elseif (self::$stackedErrorLevels) { + self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e); + } else { + try { + $this->isRecursive = true; + $this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e); + $this->isRecursive = false; + } catch (\Exception $e) { + $this->isRecursive = false; + + throw $e; + } catch (\Throwable $e) { + $this->isRecursive = false; + + throw $e; + } + } + + return !$silenced && $type && $log; + } + + /** + * Handles an exception by logging then forwarding it to another handler. + * + * @param \Exception|\Throwable $exception An exception to handle + * @param array $error An array as returned by error_get_last() + * + * @internal + */ + public function handleException($exception, array $error = null) + { + if (null === $error) { + self::$exitCode = 255; + } + if (!$exception instanceof \Exception) { + $exception = new FatalThrowableError($exception); + } + $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; + $handlerException = null; + + if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { + $e = array( + 'type' => $type, + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'level' => error_reporting(), + 'stack' => $exception->getTrace(), + ); + if ($exception instanceof FatalErrorException) { + if ($exception instanceof FatalThrowableError) { + $error = array( + 'type' => $type, + 'message' => $message = $exception->getMessage(), + 'file' => $e['file'], + 'line' => $e['line'], + ); + } else { + $message = 'Fatal '.$exception->getMessage(); + } + } elseif ($exception instanceof \ErrorException) { + $message = 'Uncaught '.$exception->getMessage(); + if ($exception instanceof ContextErrorException) { + $e['context'] = $exception->getContext(); + } + } else { + $message = 'Uncaught Exception: '.$exception->getMessage(); + } + } + if ($this->loggedErrors & $type) { + try { + $this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e); + } catch (\Exception $handlerException) { + } catch (\Throwable $handlerException) { + } + } + if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { + foreach ($this->getFatalErrorHandlers() as $handler) { + if ($e = $handler->handleError($error, $exception)) { + $exception = $e; + break; + } + } + } + $exceptionHandler = $this->exceptionHandler; + $this->exceptionHandler = null; + try { + if (null !== $exceptionHandler) { + return \call_user_func($exceptionHandler, $exception); + } + $handlerException = $handlerException ?: $exception; + } catch (\Exception $handlerException) { + } catch (\Throwable $handlerException) { + } + if ($exception === $handlerException) { + self::$reservedMemory = null; // Disable the fatal error handler + throw $exception; // Give back $exception to the native handler + } + $this->handleException($handlerException); + } + + /** + * Shutdown registered function for handling PHP fatal errors. + * + * @param array $error An array as returned by error_get_last() + * + * @internal + */ + public static function handleFatalError(array $error = null) + { + if (null === self::$reservedMemory) { + return; + } + + $handler = self::$reservedMemory = null; + $handlers = array(); + $previousHandler = null; + $sameHandlerLimit = 10; + + while (!is_array($handler) || !$handler[0] instanceof self) { + $handler = set_exception_handler('var_dump'); + restore_exception_handler(); + + if (!$handler) { + break; + } + restore_exception_handler(); + + if ($handler !== $previousHandler) { + array_unshift($handlers, $handler); + $previousHandler = $handler; + } elseif (0 === --$sameHandlerLimit) { + $handler = null; + break; + } + } + foreach ($handlers as $h) { + set_exception_handler($h); + } + if (!$handler) { + return; + } + if ($handler !== $h) { + $handler[0]->setExceptionHandler($h); + } + $handler = $handler[0]; + $handlers = array(); + + if ($exit = null === $error) { + $error = error_get_last(); + } + + try { + while (self::$stackedErrorLevels) { + static::unstackErrors(); + } + } catch (\Exception $exception) { + // Handled below + } catch (\Throwable $exception) { + // Handled below + } + + if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { + // Let's not throw anymore but keep logging + $handler->throwAt(0, true); + $trace = isset($error['backtrace']) ? $error['backtrace'] : null; + + if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { + $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); + } else { + $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); + } + } + + try { + if (isset($exception)) { + self::$exitCode = 255; + $handler->handleException($exception, $error); + } + } catch (FatalErrorException $e) { + // Ignore this re-throw + } + + if ($exit && self::$exitCode) { + $exitCode = self::$exitCode; + register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); + } + } + + /** + * Configures the error handler for delayed handling. + * Ensures also that non-catchable fatal errors are never silenced. + * + * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724 + * PHP has a compile stage where it behaves unusually. To workaround it, + * we plug an error handler that only stacks errors for later. + * + * The most important feature of this is to prevent + * autoloading until unstackErrors() is called. + */ + public static function stackErrors() + { + self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); + } + + /** + * Unstacks stacked errors and forwards to the logger. + */ + public static function unstackErrors() + { + $level = array_pop(self::$stackedErrorLevels); + + if (null !== $level) { + $e = error_reporting($level); + if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) { + // If the user changed the error level, do not overwrite it + error_reporting($e); + } + } + + if (empty(self::$stackedErrorLevels)) { + $errors = self::$stackedErrors; + self::$stackedErrors = array(); + + foreach ($errors as $e) { + $e[0]->log($e[1], $e[2], $e[3]); + } + } + } + + /** + * Gets the fatal error handlers. + * + * Override this method if you want to define more fatal error handlers. + * + * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface + */ + protected function getFatalErrorHandlers() + { + return array( + new UndefinedFunctionFatalErrorHandler(), + new UndefinedMethodFatalErrorHandler(), + new ClassNotFoundFatalErrorHandler(), + ); + } + + /** + * Sets the level at which the conversion to Exception is done. + * + * @param int|null $level The level (null to use the error_reporting() value and 0 to disable) + * + * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead. + */ + public function setLevel($level) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED); + + $level = null === $level ? error_reporting() : $level; + $this->throwAt($level, true); + } + + /** + * Sets the display_errors flag value. + * + * @param int $displayErrors The display_errors flag value + * + * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead. + */ + public function setDisplayErrors($displayErrors) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED); + + if ($displayErrors) { + $this->throwAt($this->displayErrors, true); + } else { + $displayErrors = $this->displayErrors; + $this->throwAt(0, true); + $this->displayErrors = $displayErrors; + } + } + + /** + * Sets a logger for the given channel. + * + * @param LoggerInterface $logger A logger interface + * @param string $channel The channel associated with the logger (deprecation, emergency or scream) + * + * @deprecated since version 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead. + */ + public static function setLogger(LoggerInterface $logger, $channel = 'deprecation') + { + @trigger_error('The '.__METHOD__.' static method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the setLoggers() or setDefaultLogger() methods instead.', E_USER_DEPRECATED); + + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if (!$handler instanceof self) { + return; + } + if ('deprecation' === $channel) { + $handler->setDefaultLogger($logger, E_DEPRECATED | E_USER_DEPRECATED, true); + $handler->screamAt(E_DEPRECATED | E_USER_DEPRECATED); + } elseif ('scream' === $channel) { + $handler->setDefaultLogger($logger, E_ALL | E_STRICT, false); + $handler->screamAt(E_ALL | E_STRICT); + } elseif ('emergency' === $channel) { + $handler->setDefaultLogger($logger, E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR, true); + $handler->screamAt(E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); + } + } + + /** + * @deprecated since version 2.6, to be removed in 3.0. Use handleError() instead. + */ + public function handle($level, $message, $file = 'unknown', $line = 0, $context = array()) + { + $this->handleError(E_USER_DEPRECATED, 'The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleError() method instead.', __FILE__, __LINE__, array()); + + return $this->handleError($level, $message, $file, $line, (array) $context); + } + + /** + * Handles PHP fatal errors. + * + * @deprecated since version 2.6, to be removed in 3.0. Use handleFatalError() instead. + */ + public function handleFatal() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleFatalError() method instead.', E_USER_DEPRECATED); + + static::handleFatalError(); + } +} + +/** + * Private class used to work around https://bugs.php.net/54275. + * + * @author Nicolas Grekas + * + * @internal + */ +class ErrorHandlerCanary +{ + private static $displayErrors = null; + + public function __construct() + { + if (null === self::$displayErrors) { + self::$displayErrors = ini_set('display_errors', 1); + } + } + + public function __destruct() + { + if (null !== self::$displayErrors) { + ini_set('display_errors', self::$displayErrors); + self::$displayErrors = null; + } + } +} diff --git a/core/vendor/symfony/debug/Exception/ClassNotFoundException.php b/core/vendor/symfony/debug/Exception/ClassNotFoundException.php new file mode 100644 index 0000000..b91bf46 --- /dev/null +++ b/core/vendor/symfony/debug/Exception/ClassNotFoundException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Class (or Trait or Interface) Not Found Exception. + * + * @author Konstanton Myakshin + */ +class ClassNotFoundException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/core/vendor/symfony/debug/Exception/ContextErrorException.php b/core/vendor/symfony/debug/Exception/ContextErrorException.php new file mode 100644 index 0000000..54f0198 --- /dev/null +++ b/core/vendor/symfony/debug/Exception/ContextErrorException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Error Exception with Variable Context. + * + * @author Christian Sciberras + */ +class ContextErrorException extends \ErrorException +{ + private $context = array(); + + public function __construct($message, $code, $severity, $filename, $lineno, $context = array()) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + $this->context = $context; + } + + /** + * @return array Array of variables that existed when the exception occurred + */ + public function getContext() + { + return $this->context; + } +} diff --git a/core/vendor/symfony/debug/Exception/DummyException.php b/core/vendor/symfony/debug/Exception/DummyException.php new file mode 100644 index 0000000..1b9082b --- /dev/null +++ b/core/vendor/symfony/debug/Exception/DummyException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +@trigger_error('The '.__NAMESPACE__.'\DummyException class is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); + +/** + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0. + */ +class DummyException extends \ErrorException +{ +} diff --git a/core/vendor/symfony/debug/Exception/FatalErrorException.php b/core/vendor/symfony/debug/Exception/FatalErrorException.php new file mode 100644 index 0000000..db2fb43 --- /dev/null +++ b/core/vendor/symfony/debug/Exception/FatalErrorException.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Fatal Error Exception. + * + * @author Fabien Potencier + * @author Konstanton Myakshin + * @author Nicolas Grekas + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class FatalErrorException extends \ErrorException +{ +} + +namespace Symfony\Component\Debug\Exception; + +use Symfony\Component\HttpKernel\Exception\FatalErrorException as LegacyFatalErrorException; + +/** + * Fatal Error Exception. + * + * @author Konstanton Myakshin + */ +class FatalErrorException extends LegacyFatalErrorException +{ + public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + + if (null !== $trace) { + if (!$traceArgs) { + foreach ($trace as &$frame) { + unset($frame['args'], $frame['this'], $frame); + } + } + + $this->setTrace($trace); + } elseif (null !== $traceOffset) { + if (function_exists('xdebug_get_function_stack')) { + $trace = xdebug_get_function_stack(); + if (0 < $traceOffset) { + array_splice($trace, -$traceOffset); + } + + foreach ($trace as &$frame) { + if (!isset($frame['type'])) { + // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 + if (isset($frame['class'])) { + $frame['type'] = '::'; + } + } elseif ('dynamic' === $frame['type']) { + $frame['type'] = '->'; + } elseif ('static' === $frame['type']) { + $frame['type'] = '::'; + } + + // XDebug also has a different name for the parameters array + if (!$traceArgs) { + unset($frame['params'], $frame['args']); + } elseif (isset($frame['params']) && !isset($frame['args'])) { + $frame['args'] = $frame['params']; + unset($frame['params']); + } + } + + unset($frame); + $trace = array_reverse($trace); + } elseif (function_exists('symfony_debug_backtrace')) { + $trace = symfony_debug_backtrace(); + if (0 < $traceOffset) { + array_splice($trace, 0, $traceOffset); + } + } else { + $trace = array(); + } + + $this->setTrace($trace); + } + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/core/vendor/symfony/debug/Exception/FatalThrowableError.php b/core/vendor/symfony/debug/Exception/FatalThrowableError.php new file mode 100644 index 0000000..fafc922 --- /dev/null +++ b/core/vendor/symfony/debug/Exception/FatalThrowableError.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Fatal Throwable Error. + * + * @author Nicolas Grekas + */ +class FatalThrowableError extends FatalErrorException +{ + public function __construct(\Throwable $e) + { + if ($e instanceof \ParseError) { + $message = 'Parse error: '.$e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: '.$e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = $e->getMessage(); + $severity = E_ERROR; + } + + \ErrorException::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine(), + $e->getPrevious() + ); + + $this->setTrace($e->getTrace()); + } +} diff --git a/core/vendor/symfony/debug/Exception/FlattenException.php b/core/vendor/symfony/debug/Exception/FlattenException.php new file mode 100644 index 0000000..b3a98ac --- /dev/null +++ b/core/vendor/symfony/debug/Exception/FlattenException.php @@ -0,0 +1,297 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +use Symfony\Component\Debug\Exception\FlattenException as DebugFlattenException; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class FlattenException +{ + private $handler; + + public static function __callStatic($method, $args) + { + if (!method_exists('Symfony\Component\Debug\Exception\FlattenException', $method)) { + throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_called_class(), $method)); + } + + return call_user_func_array(array('Symfony\Component\Debug\Exception\FlattenException', $method), $args); + } + + public function __call($method, $args) + { + if (!isset($this->handler)) { + $this->handler = new DebugFlattenException(); + } + + if (!method_exists($this->handler, $method)) { + throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_class($this), $method)); + } + + return call_user_func_array(array($this->handler, $method), $args); + } +} + +namespace Symfony\Component\Debug\Exception; + +use Symfony\Component\HttpKernel\Exception\FlattenException as LegacyFlattenException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + */ +class FlattenException extends LegacyFlattenException +{ + private $message; + private $code; + private $previous; + private $trace; + private $class; + private $statusCode; + private $headers; + private $file; + private $line; + + public static function create(\Exception $exception, $statusCode = null, array $headers = array()) + { + $e = new static(); + $e->setMessage($exception->getMessage()); + $e->setCode($exception->getCode()); + + if ($exception instanceof HttpExceptionInterface) { + $statusCode = $exception->getStatusCode(); + $headers = array_merge($headers, $exception->getHeaders()); + } + + if (null === $statusCode) { + $statusCode = 500; + } + + $e->setStatusCode($statusCode); + $e->setHeaders($headers); + $e->setTraceFromException($exception); + $e->setClass(get_class($exception)); + $e->setFile($exception->getFile()); + $e->setLine($exception->getLine()); + + $previous = $exception->getPrevious(); + + if ($previous instanceof \Exception) { + $e->setPrevious(static::create($previous)); + } elseif ($previous instanceof \Throwable) { + $e->setPrevious(static::create(new FatalThrowableError($previous))); + } + + return $e; + } + + public function toArray() + { + $exceptions = array(); + foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { + $exceptions[] = array( + 'message' => $exception->getMessage(), + 'class' => $exception->getClass(), + 'trace' => $exception->getTrace(), + ); + } + + return $exceptions; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($code) + { + $this->statusCode = $code; + } + + public function getHeaders() + { + return $this->headers; + } + + public function setHeaders(array $headers) + { + $this->headers = $headers; + } + + public function getClass() + { + return $this->class; + } + + public function setClass($class) + { + $this->class = $class; + } + + public function getFile() + { + return $this->file; + } + + public function setFile($file) + { + $this->file = $file; + } + + public function getLine() + { + return $this->line; + } + + public function setLine($line) + { + $this->line = $line; + } + + public function getMessage() + { + return $this->message; + } + + public function setMessage($message) + { + $this->message = $message; + } + + public function getCode() + { + return $this->code; + } + + public function setCode($code) + { + $this->code = $code; + } + + public function getPrevious() + { + return $this->previous; + } + + public function setPrevious(FlattenException $previous) + { + $this->previous = $previous; + } + + public function getAllPrevious() + { + $exceptions = array(); + $e = $this; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + return $exceptions; + } + + public function getTrace() + { + return $this->trace; + } + + public function setTraceFromException(\Exception $exception) + { + $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); + } + + public function setTrace($trace, $file, $line) + { + $this->trace = array(); + $this->trace[] = array( + 'namespace' => '', + 'short_class' => '', + 'class' => '', + 'type' => '', + 'function' => '', + 'file' => $file, + 'line' => $line, + 'args' => array(), + ); + foreach ($trace as $entry) { + $class = ''; + $namespace = ''; + if (isset($entry['class'])) { + $parts = explode('\\', $entry['class']); + $class = array_pop($parts); + $namespace = implode('\\', $parts); + } + + $this->trace[] = array( + 'namespace' => $namespace, + 'short_class' => $class, + 'class' => isset($entry['class']) ? $entry['class'] : '', + 'type' => isset($entry['type']) ? $entry['type'] : '', + 'function' => isset($entry['function']) ? $entry['function'] : null, + 'file' => isset($entry['file']) ? $entry['file'] : null, + 'line' => isset($entry['line']) ? $entry['line'] : null, + 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), + ); + } + } + + private function flattenArgs($args, $level = 0, &$count = 0) + { + $result = array(); + foreach ($args as $key => $value) { + if (++$count > 1e4) { + return array('array', '*SKIPPED over 10000 entries*'); + } + if ($value instanceof \__PHP_Incomplete_Class) { + // is_object() returns false on PHP<=7.1 + $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); + } elseif (is_object($value)) { + $result[$key] = array('object', get_class($value)); + } elseif (is_array($value)) { + if ($level > 10) { + $result[$key] = array('array', '*DEEP NESTED ARRAY*'); + } else { + $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count)); + } + } elseif (null === $value) { + $result[$key] = array('null', null); + } elseif (is_bool($value)) { + $result[$key] = array('boolean', $value); + } elseif (is_resource($value)) { + $result[$key] = array('resource', get_resource_type($value)); + } else { + $result[$key] = array('string', (string) $value); + } + } + + return $result; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/core/vendor/symfony/debug/Exception/OutOfMemoryException.php b/core/vendor/symfony/debug/Exception/OutOfMemoryException.php new file mode 100644 index 0000000..fec1979 --- /dev/null +++ b/core/vendor/symfony/debug/Exception/OutOfMemoryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Out of memory exception. + * + * @author Nicolas Grekas + */ +class OutOfMemoryException extends FatalErrorException +{ +} diff --git a/core/vendor/symfony/debug/Exception/UndefinedFunctionException.php b/core/vendor/symfony/debug/Exception/UndefinedFunctionException.php new file mode 100644 index 0000000..a66ae2a --- /dev/null +++ b/core/vendor/symfony/debug/Exception/UndefinedFunctionException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Undefined Function Exception. + * + * @author Konstanton Myakshin + */ +class UndefinedFunctionException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/core/vendor/symfony/debug/Exception/UndefinedMethodException.php b/core/vendor/symfony/debug/Exception/UndefinedMethodException.php new file mode 100644 index 0000000..350dc31 --- /dev/null +++ b/core/vendor/symfony/debug/Exception/UndefinedMethodException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Undefined Method Exception. + * + * @author Grégoire Pineau + */ +class UndefinedMethodException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/core/vendor/symfony/debug/ExceptionHandler.php b/core/vendor/symfony/debug/ExceptionHandler.php new file mode 100644 index 0000000..472073c --- /dev/null +++ b/core/vendor/symfony/debug/ExceptionHandler.php @@ -0,0 +1,467 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\Debug\Exception\OutOfMemoryException; + +/** + * ExceptionHandler converts an exception to a Response object. + * + * It is mostly useful in debug mode to replace the default PHP/XDebug + * output with something prettier and more useful. + * + * As this class is mainly used during Kernel boot, where nothing is yet + * available, the Response content is always HTML. + * + * @author Fabien Potencier + * @author Nicolas Grekas + */ +class ExceptionHandler +{ + private $debug; + private $charset; + private $handler; + private $caughtBuffer; + private $caughtLength; + private $fileLinkFormat; + + public function __construct($debug = true, $charset = null, $fileLinkFormat = null) + { + if (false !== strpos($charset, '%')) { + // Swap $charset and $fileLinkFormat for BC reasons + $pivot = $fileLinkFormat; + $fileLinkFormat = $charset; + $charset = $pivot; + } + $this->debug = $debug; + $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + } + + /** + * Registers the exception handler. + * + * @param bool $debug Enable/disable debug mode, where the stack trace is displayed + * @param string|null $charset The charset used by exception messages + * @param string|null $fileLinkFormat The IDE link template + * + * @return static + */ + public static function register($debug = true, $charset = null, $fileLinkFormat = null) + { + $handler = new static($debug, $charset, $fileLinkFormat); + + $prev = set_exception_handler(array($handler, 'handle')); + if (is_array($prev) && $prev[0] instanceof ErrorHandler) { + restore_exception_handler(); + $prev[0]->setExceptionHandler(array($handler, 'handle')); + } + + return $handler; + } + + /** + * Sets a user exception handler. + * + * @param callable $handler An handler that will be called on Exception + * + * @return callable|null The previous exception handler if any + */ + public function setHandler($handler) + { + if (null !== $handler && !is_callable($handler)) { + throw new \LogicException('The exception handler must be a valid PHP callable.'); + } + $old = $this->handler; + $this->handler = $handler; + + return $old; + } + + /** + * Sets the format for links to source files. + * + * @param string $format The format for links to source files + * + * @return string The previous file link format + */ + public function setFileLinkFormat($format) + { + $old = $this->fileLinkFormat; + $this->fileLinkFormat = $format; + + return $old; + } + + /** + * Sends a response for the given Exception. + * + * To be as fail-safe as possible, the exception is first handled + * by our simple exception handler, then by the user exception handler. + * The latter takes precedence and any output from the former is cancelled, + * if and only if nothing bad happens in this handling path. + */ + public function handle(\Exception $exception) + { + if (null === $this->handler || $exception instanceof OutOfMemoryException) { + $this->failSafeHandle($exception); + + return; + } + + $caughtLength = $this->caughtLength = 0; + + ob_start(array($this, 'catchOutput')); + $this->failSafeHandle($exception); + while (null === $this->caughtBuffer && ob_end_flush()) { + // Empty loop, everything is in the condition + } + if (isset($this->caughtBuffer[0])) { + ob_start(array($this, 'cleanOutput')); + echo $this->caughtBuffer; + $caughtLength = ob_get_length(); + } + $this->caughtBuffer = null; + + try { + call_user_func($this->handler, $exception); + $this->caughtLength = $caughtLength; + } catch (\Exception $e) { + if (!$caughtLength) { + // All handlers failed. Let PHP handle that now. + throw $exception; + } + } + } + + /** + * Sends a response for the given Exception. + * + * If you have the Symfony HttpFoundation component installed, + * this method will use it to create and send the response. If not, + * it will fallback to plain PHP functions. + */ + private function failSafeHandle(\Exception $exception) + { + if (class_exists('Symfony\Component\HttpFoundation\Response', false) + && __CLASS__ !== get_class($this) + && ($reflector = new \ReflectionMethod($this, 'createResponse')) + && __CLASS__ !== $reflector->class + ) { + $response = $this->createResponse($exception); + $response->sendHeaders(); + $response->sendContent(); + + return; + } + + $this->sendPhpResponse($exception); + } + + /** + * Sends the error associated with the given Exception as a plain PHP response. + * + * This method uses plain PHP functions like header() and echo to output + * the response. + * + * @param \Exception|FlattenException $exception An \Exception instance + */ + public function sendPhpResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + if (!headers_sent()) { + header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); + foreach ($exception->getHeaders() as $name => $value) { + header($name.': '.$value, false); + } + header('Content-Type: text/html; charset='.$this->charset); + } + + echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + + /** + * Creates the error Response associated with the given Exception. + * + * @param \Exception|FlattenException $exception An \Exception instance + * + * @return Response A Response instance + */ + public function createResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + return Response::create($this->decorate($this->getContent($exception), $this->getStylesheet($exception)), $exception->getStatusCode(), $exception->getHeaders())->setCharset($this->charset); + } + + /** + * Gets the HTML content associated with the given exception. + * + * @return string The content as a string + */ + public function getContent(FlattenException $exception) + { + switch ($exception->getStatusCode()) { + case 404: + $title = 'Sorry, the page you are looking for could not be found.'; + break; + default: + $title = 'Whoops, looks like something went wrong.'; + } + + $content = ''; + if ($this->debug) { + try { + $count = count($exception->getAllPrevious()); + $total = $count + 1; + foreach ($exception->toArray() as $position => $e) { + $ind = $count - $position + 1; + $class = $this->formatClass($e['class']); + $message = nl2br($this->escapeHtml($e['message'])); + $content .= sprintf(<<<'EOF' +

    + %d/%d + %s%s: + %s +

    +
    +
      + +EOF + , $ind, $total, $class, $this->formatPath($e['trace'][0]['file'], $e['trace'][0]['line']), $message); + foreach ($e['trace'] as $trace) { + $content .= '
    1. '; + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + } + if (isset($trace['file']) && isset($trace['line'])) { + $content .= $this->formatPath($trace['file'], $trace['line']); + } + $content .= "
    2. \n"; + } + + $content .= "
    \n
    \n"; + } + } catch (\Exception $e) { + // something nasty happened and we cannot throw an exception anymore + if ($this->debug) { + $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage())); + } else { + $title = 'Whoops, looks like something went wrong.'; + } + } + } + + return << +

    $title

    + $content + +EOF; + } + + /** + * Gets the stylesheet associated with the given exception. + * + * @return string The stylesheet as a string + */ + public function getStylesheet(FlattenException $exception) + { + return <<<'EOF' + .sf-reset { font: 11px Verdana, Arial, sans-serif; color: #333 } + .sf-reset .clear { clear:both; height:0; font-size:0; line-height:0; } + .sf-reset .clear_fix:after { display:block; height:0; clear:both; visibility:hidden; } + .sf-reset .clear_fix { display:inline-block; } + .sf-reset * html .clear_fix { height:1%; } + .sf-reset .clear_fix { display:block; } + .sf-reset, .sf-reset .block { margin: auto } + .sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; } + .sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px } + .sf-reset strong { font-weight:bold; } + .sf-reset a { color:#6c6159; cursor: default; } + .sf-reset a img { border:none; } + .sf-reset a:hover { text-decoration:underline; } + .sf-reset em { font-style:italic; } + .sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif } + .sf-reset .exception_counter { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; float: left; display: block; } + .sf-reset .exception_title { margin-left: 3em; margin-bottom: 0.7em; display: block; } + .sf-reset .exception_message { margin-left: 3em; display: block; } + .sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; } + .sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px; + -webkit-border-bottom-right-radius: 16px; + -webkit-border-bottom-left-radius: 16px; + -moz-border-radius-bottomright: 16px; + -moz-border-radius-bottomleft: 16px; + border-bottom-right-radius: 16px; + border-bottom-left-radius: 16px; + border-bottom:1px solid #ccc; + border-right:1px solid #ccc; + border-left:1px solid #ccc; + word-wrap: break-word; + } + .sf-reset .block_exception { background-color:#ddd; color: #333; padding:20px; + -webkit-border-top-left-radius: 16px; + -webkit-border-top-right-radius: 16px; + -moz-border-radius-topleft: 16px; + -moz-border-radius-topright: 16px; + border-top-left-radius: 16px; + border-top-right-radius: 16px; + border-top:1px solid #ccc; + border-right:1px solid #ccc; + border-left:1px solid #ccc; + overflow: hidden; + word-wrap: break-word; + } + .sf-reset a { background:none; color:#868686; text-decoration:none; } + .sf-reset a:hover { background:none; color:#313131; text-decoration:underline; } + .sf-reset ol { padding: 10px 0; } + .sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + border: 1px solid #ccc; + } +EOF; + } + + private function decorate($content, $css) + { + return << + + + + + + + + $content + + +EOF; + } + + private function formatClass($class) + { + $parts = explode('\\', $class); + + return sprintf('%s', $class, array_pop($parts)); + } + + private function formatPath($path, $line) + { + $path = $this->escapeHtml($path); + $file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path; + + if ($linkFormat = $this->fileLinkFormat) { + $link = strtr($this->escapeHtml($linkFormat), array('%f' => $path, '%l' => (int) $line)); + + return sprintf(' in %s line %d', $link, $file, $line); + } + + return sprintf(' in %s line %d', $path, $file, $line); + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + private function formatArgs(array $args) + { + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('string' === $item[0]) { + $formattedValue = sprintf("'%s'", $this->escapeHtml($item[1])); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', var_export($this->escapeHtml((string) $item[1]), true)); + } + + $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue); + } + + return implode(', ', $result); + } + + /** + * Returns an UTF-8 and HTML encoded string. + * + * @deprecated since version 2.7, to be removed in 3.0. + */ + protected static function utf8Htmlize($str) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + + return htmlspecialchars($str, ENT_QUOTES | (\PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), 'UTF-8'); + } + + /** + * HTML-encodes a string. + */ + private function escapeHtml($str) + { + return htmlspecialchars($str, ENT_QUOTES | (\PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), $this->charset); + } + + /** + * @internal + */ + public function catchOutput($buffer) + { + $this->caughtBuffer = $buffer; + + return ''; + } + + /** + * @internal + */ + public function cleanOutput($buffer) + { + if ($this->caughtLength) { + // use substr_replace() instead of substr() for mbstring overloading resistance + $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); + if (isset($cleanBuffer[0])) { + $buffer = $cleanBuffer; + } + } + + return $buffer; + } +} diff --git a/core/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/core/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php new file mode 100644 index 0000000..612bfca --- /dev/null +++ b/core/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\ClassNotFoundException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\DebugClassLoader; +use Composer\Autoload\ClassLoader as ComposerClassLoader; +use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader; +use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader; + +/** + * ErrorHandler for classes that do not exist. + * + * @author Fabien Potencier + */ +class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + $messageLen = strlen($error['message']); + $notFoundSuffix = '\' not found'; + $notFoundSuffixLen = strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return; + } + + if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { + return; + } + + foreach (array('class', 'interface', 'trait') as $typeName) { + $prefix = ucfirst($typeName).' \''; + $prefixLen = strlen($prefix); + if (0 !== strpos($error['message'], $prefix)) { + continue; + } + + $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { + $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); + $tail = ' for another namespace?'; + } else { + $className = $fullyQualifiedClassName; + $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); + $tail = '?'; + } + + if ($candidates = $this->getClassCandidates($className)) { + $tail = array_pop($candidates).'"?'; + if ($candidates) { + $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; + } else { + $tail = ' for "'.$tail; + } + } + $message .= "\nDid you forget a \"use\" statement".$tail; + + return new ClassNotFoundException($message, $exception); + } + } + + /** + * Tries to guess the full namespace for a given class name. + * + * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer + * autoloader (that should cover all common cases). + * + * @param string $class A class name (without its namespace) + * + * @return array An array of possible fully qualified class names + */ + private function getClassCandidates($class) + { + if (!is_array($functions = spl_autoload_functions())) { + return array(); + } + + // find Symfony and Composer autoloaders + $classes = array(); + + foreach ($functions as $function) { + if (!is_array($function)) { + continue; + } + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + + // @deprecated since version 2.5. Returning an object from DebugClassLoader::getClassLoader() is deprecated. + if (is_object($function)) { + $function = array($function); + } + + if (!is_array($function)) { + continue; + } + } + + if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader || $function[0] instanceof SymfonyUniversalClassLoader) { + foreach ($function[0]->getPrefixes() as $prefix => $paths) { + foreach ($paths as $path) { + $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); + } + } + } + if ($function[0] instanceof ComposerClassLoader) { + foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { + foreach ($paths as $path) { + $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); + } + } + } + } + + return array_unique($classes); + } + + /** + * @param string $path + * @param string $class + * @param string $prefix + * + * @return array + */ + private function findClassInPath($path, $class, $prefix) + { + if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { + return array(); + } + + $classes = array(); + $filename = $class.'.php'; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { + $classes[] = $class; + } + } + + return $classes; + } + + /** + * @param string $path + * @param string $file + * @param string $prefix + * + * @return string|null + */ + private function convertFileToClass($path, $file, $prefix) + { + $candidates = array( + // namespaced class + $namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file), + // namespaced class (with target dir) + $prefix.$namespacedClass, + // namespaced class (with target dir and separator) + $prefix.'\\'.$namespacedClass, + // PEAR class + str_replace('\\', '_', $namespacedClass), + // PEAR class (with target dir) + str_replace('\\', '_', $prefix.$namespacedClass), + // PEAR class (with target dir and separator) + str_replace('\\', '_', $prefix.'\\'.$namespacedClass), + ); + + if ($prefix) { + $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); + } + + // We cannot use the autoloader here as most of them use require; but if the class + // is not found, the new autoloader call will require the file again leading to a + // "cannot redeclare class" error. + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + + require_once $file; + + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + } + + /** + * @param string $class + * + * @return bool + */ + private function classExists($class) + { + return class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false)); + } +} diff --git a/core/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php b/core/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php new file mode 100644 index 0000000..6b87eb3 --- /dev/null +++ b/core/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; + +/** + * Attempts to convert fatal errors to exceptions. + * + * @author Fabien Potencier + */ +interface FatalErrorHandlerInterface +{ + /** + * Attempts to convert an error into an exception. + * + * @param array $error An array as returned by error_get_last() + * @param FatalErrorException $exception A FatalErrorException instance + * + * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise + */ + public function handleError(array $error, FatalErrorException $exception); +} diff --git a/core/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/core/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php new file mode 100644 index 0000000..c6f391a --- /dev/null +++ b/core/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\UndefinedFunctionException; +use Symfony\Component\Debug\Exception\FatalErrorException; + +/** + * ErrorHandler for undefined functions. + * + * @author Fabien Potencier + */ +class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + $messageLen = strlen($error['message']); + $notFoundSuffix = '()'; + $notFoundSuffixLen = strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return; + } + + if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { + return; + } + + $prefix = 'Call to undefined function '; + $prefixLen = strlen($prefix); + if (0 !== strpos($error['message'], $prefix)) { + return; + } + + $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { + $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); + } else { + $functionName = $fullyQualifiedFunctionName; + $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); + } + + $candidates = array(); + foreach (get_defined_functions() as $type => $definedFunctionNames) { + foreach ($definedFunctionNames as $definedFunctionName) { + if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { + $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); + } else { + $definedFunctionNameBasename = $definedFunctionName; + } + + if ($definedFunctionNameBasename === $functionName) { + $candidates[] = '\\'.$definedFunctionName; + } + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedFunctionException($message, $exception); + } +} diff --git a/core/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/core/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php new file mode 100644 index 0000000..6fa62b6 --- /dev/null +++ b/core/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\UndefinedMethodException; + +/** + * ErrorHandler for undefined methods. + * + * @author Grégoire Pineau + */ +class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches); + if (!$matches) { + return; + } + + $className = $matches[1]; + $methodName = $matches[2]; + + $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); + + if (!class_exists($className) || null === $methods = get_class_methods($className)) { + // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) + return new UndefinedMethodException($message, $exception); + } + + $candidates = array(); + foreach ($methods as $definedMethodName) { + $lev = levenshtein($methodName, $definedMethodName); + if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) { + $candidates[] = $definedMethodName; + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedMethodException($message, $exception); + } +} diff --git a/core/vendor/symfony/debug/LICENSE b/core/vendor/symfony/debug/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/core/vendor/symfony/debug/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 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/core/vendor/symfony/debug/README.md b/core/vendor/symfony/debug/README.md new file mode 100644 index 0000000..a1d1617 --- /dev/null +++ b/core/vendor/symfony/debug/README.md @@ -0,0 +1,13 @@ +Debug Component +=============== + +The Debug component provides tools to ease debugging PHP code. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/debug/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/core/vendor/symfony/debug/Resources/ext/README.md b/core/vendor/symfony/debug/Resources/ext/README.md new file mode 100644 index 0000000..25dccf0 --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/README.md @@ -0,0 +1,134 @@ +Symfony Debug Extension for PHP 5 +================================= + +This extension publishes several functions to help building powerful debugging tools. +It is compatible with PHP 5.3, 5.4, 5.5 and 5.6; with ZTS and non-ZTS modes. +It is not required thus not provided for PHP 7. + +symfony_zval_info() +------------------- + +- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP, +- does work with references, preventing memory copying. + +Its behavior is about the same as: + +```php + gettype($array[$key]), + 'zval_hash' => /* hashed memory address of $array[$key] */, + 'zval_refcount' => /* internal zval refcount of $array[$key] */, + 'zval_isref' => /* is_ref status of $array[$key] */, + ); + + switch ($info['type']) { + case 'object': + $info += array( + 'object_class' => get_class($array[$key]), + 'object_refcount' => /* internal object refcount of $array[$key] */, + 'object_hash' => spl_object_hash($array[$key]), + 'object_handle' => /* internal object handle $array[$key] */, + ); + break; + + case 'resource': + $info += array( + 'resource_handle' => (int) $array[$key], + 'resource_type' => get_resource_type($array[$key]), + 'resource_refcount' => /* internal resource refcount of $array[$key] */, + ); + break; + + case 'array': + $info += array( + 'array_count' => count($array[$key]), + ); + break; + + case 'string': + $info += array( + 'strlen' => strlen($array[$key]), + ); + break; + } + + return $info; +} +``` + +symfony_debug_backtrace() +------------------------- + +This function works like debug_backtrace(), except that it can fetch the full backtrace in case of fatal errors: + +```php +function foo() { fatal(); } +function bar() { foo(); } + +function sd() { var_dump(symfony_debug_backtrace()); } + +register_shutdown_function('sd'); + +bar(); + +/* Will output +Fatal error: Call to undefined function fatal() in foo.php on line 42 +array(3) { + [0]=> + array(2) { + ["function"]=> + string(2) "sd" + ["args"]=> + array(0) { + } + } + [1]=> + array(4) { + ["file"]=> + string(7) "foo.php" + ["line"]=> + int(1) + ["function"]=> + string(3) "foo" + ["args"]=> + array(0) { + } + } + [2]=> + array(4) { + ["file"]=> + string(102) "foo.php" + ["line"]=> + int(2) + ["function"]=> + string(3) "bar" + ["args"]=> + array(0) { + } + } +} +*/ +``` + +Usage +----- + +To enable the extension from source, run: + +``` + phpize + ./configure + make + sudo make install +``` diff --git a/core/vendor/symfony/debug/Resources/ext/config.m4 b/core/vendor/symfony/debug/Resources/ext/config.m4 new file mode 100644 index 0000000..3c56047 --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension symfony_debug + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(symfony_debug, for symfony_debug support, +dnl Make sure that the comment is aligned: +dnl [ --with-symfony_debug Include symfony_debug support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(symfony_debug, whether to enable symfony_debug support, +dnl Make sure that the comment is aligned: +[ --enable-symfony_debug Enable symfony_debug support]) + +if test "$PHP_SYMFONY_DEBUG" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-symfony_debug -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/symfony_debug.h" # you most likely want to change this + dnl if test -r $PHP_SYMFONY_DEBUG/$SEARCH_FOR; then # path given as parameter + dnl SYMFONY_DEBUG_DIR=$PHP_SYMFONY_DEBUG + dnl else # search default path list + dnl AC_MSG_CHECKING([for symfony_debug files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl SYMFONY_DEBUG_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$SYMFONY_DEBUG_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the symfony_debug distribution]) + dnl fi + + dnl # --with-symfony_debug -> add include path + dnl PHP_ADD_INCLUDE($SYMFONY_DEBUG_DIR/include) + + dnl # --with-symfony_debug -> check for lib and symbol presence + dnl LIBNAME=symfony_debug # you may want to change this + dnl LIBSYMBOL=symfony_debug # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SYMFONY_DEBUG_DIR/lib, SYMFONY_DEBUG_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_SYMFONY_DEBUGLIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong symfony_debug lib version or lib not found]) + dnl ],[ + dnl -L$SYMFONY_DEBUG_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(SYMFONY_DEBUG_SHARED_LIBADD) + + PHP_NEW_EXTENSION(symfony_debug, symfony_debug.c, $ext_shared) +fi diff --git a/core/vendor/symfony/debug/Resources/ext/config.w32 b/core/vendor/symfony/debug/Resources/ext/config.w32 new file mode 100644 index 0000000..487e691 --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("symfony_debug", "for symfony_debug support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("symfony_debug", "enable symfony_debug support", "no"); + +if (PHP_SYMFONY_DEBUG != "no") { + EXTENSION("symfony_debug", "symfony_debug.c"); +} + diff --git a/core/vendor/symfony/debug/Resources/ext/php_symfony_debug.h b/core/vendor/symfony/debug/Resources/ext/php_symfony_debug.h new file mode 100644 index 0000000..26d0e8c --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/php_symfony_debug.h @@ -0,0 +1,60 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifndef PHP_SYMFONY_DEBUG_H +#define PHP_SYMFONY_DEBUG_H + +extern zend_module_entry symfony_debug_module_entry; +#define phpext_symfony_debug_ptr &symfony_debug_module_entry + +#define PHP_SYMFONY_DEBUG_VERSION "2.7" + +#ifdef PHP_WIN32 +# define PHP_SYMFONY_DEBUG_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_SYMFONY_DEBUG_API __attribute__ ((visibility("default"))) +#else +# define PHP_SYMFONY_DEBUG_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +ZEND_BEGIN_MODULE_GLOBALS(symfony_debug) + intptr_t req_rand_init; + void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + zval *debug_bt; +ZEND_END_MODULE_GLOBALS(symfony_debug) + +PHP_MINIT_FUNCTION(symfony_debug); +PHP_MSHUTDOWN_FUNCTION(symfony_debug); +PHP_RINIT_FUNCTION(symfony_debug); +PHP_RSHUTDOWN_FUNCTION(symfony_debug); +PHP_MINFO_FUNCTION(symfony_debug); +PHP_GINIT_FUNCTION(symfony_debug); +PHP_GSHUTDOWN_FUNCTION(symfony_debug); + +PHP_FUNCTION(symfony_zval_info); +PHP_FUNCTION(symfony_debug_backtrace); + +static char *_symfony_debug_memory_address_hash(void * TSRMLS_DC); +static const char *_symfony_debug_zval_type(zval *); +static const char* _symfony_debug_get_resource_type(long TSRMLS_DC); +static int _symfony_debug_get_resource_refcount(long TSRMLS_DC); + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + +#ifdef ZTS +#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v) +#else +#define SYMFONY_DEBUG_G(v) (symfony_debug_globals.v) +#endif + +#endif /* PHP_SYMFONY_DEBUG_H */ diff --git a/core/vendor/symfony/debug/Resources/ext/symfony_debug.c b/core/vendor/symfony/debug/Resources/ext/symfony_debug.c new file mode 100644 index 0000000..0d7cb60 --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/symfony_debug.c @@ -0,0 +1,283 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#ifdef ZTS +#include "TSRM.h" +#endif +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_symfony_debug.h" +#include "ext/standard/php_rand.h" +#include "ext/standard/php_lcg.h" +#include "ext/spl/php_spl.h" +#include "Zend/zend_gc.h" +#include "Zend/zend_builtin_functions.h" +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ +#include "ext/standard/php_array.h" +#include "Zend/zend_interfaces.h" +#include "SAPI.h" + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == 220090626 + +ZEND_DECLARE_MODULE_GLOBALS(symfony_debug) + +ZEND_BEGIN_ARG_INFO_EX(symfony_zval_arginfo, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_ARRAY_INFO(0, array, 0) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +const zend_function_entry symfony_debug_functions[] = { + PHP_FE(symfony_zval_info, symfony_zval_arginfo) + PHP_FE(symfony_debug_backtrace, NULL) + PHP_FE_END +}; + +PHP_FUNCTION(symfony_debug_backtrace) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } +#if IS_PHP_53 + zend_fetch_debug_backtrace(return_value, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(return_value, 1, 0, 0 TSRMLS_CC); +#endif + + if (!SYMFONY_DEBUG_G(debug_bt)) { + return; + } + + php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(SYMFONY_DEBUG_G(debug_bt)), 0 TSRMLS_CC); +} + +PHP_FUNCTION(symfony_zval_info) +{ + zval *key = NULL, *arg = NULL; + zval **data = NULL; + HashTable *array = NULL; + long options = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zh|l", &key, &array, &options) == FAILURE) { + return; + } + + switch (Z_TYPE_P(key)) { + case IS_STRING: + if (zend_symtable_find(array, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&data) == FAILURE) { + return; + } + break; + case IS_LONG: + if (zend_hash_index_find(array, Z_LVAL_P(key), (void **)&data)) { + return; + } + break; + } + + arg = *data; + + array_init(return_value); + + add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1); + add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg TSRMLS_CC), 16, 0); + add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg)); + add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg)); + + if (Z_TYPE_P(arg) == IS_OBJECT) { + char hash[33] = {0}; + + php_spl_object_hash(arg, (char *)hash TSRMLS_CC); + add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1); + add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount); + add_assoc_string(return_value, "object_hash", hash, 1); + add_assoc_long(return_value, "object_handle", Z_OBJ_HANDLE_P(arg)); + } else if (Z_TYPE_P(arg) == IS_ARRAY) { + add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg))); + } else if(Z_TYPE_P(arg) == IS_RESOURCE) { + add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg)); + add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg) TSRMLS_CC), 1); + add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg) TSRMLS_CC)); + } else if (Z_TYPE_P(arg) == IS_STRING) { + add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg)); + } +} + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) +{ + TSRMLS_FETCH(); + zval *retval; + + switch (type) { + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + ALLOC_INIT_ZVAL(retval); +#if IS_PHP_53 + zend_fetch_debug_backtrace(retval, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(retval, 1, 0, 0 TSRMLS_CC); +#endif + SYMFONY_DEBUG_G(debug_bt) = retval; + } + + SYMFONY_DEBUG_G(old_error_cb)(type, error_filename, error_lineno, format, args); +} + +static const char* _symfony_debug_get_resource_type(long rsid TSRMLS_DC) +{ + const char *res_type; + res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC); + + if (!res_type) { + return "Unknown"; + } + + return res_type; +} + +static int _symfony_debug_get_resource_refcount(long rsid TSRMLS_DC) +{ + zend_rsrc_list_entry *le; + + if (zend_hash_index_find(&EG(regular_list), rsid, (void **) &le)==SUCCESS) { + return le->refcount; + } + + return 0; +} + +static char *_symfony_debug_memory_address_hash(void *address TSRMLS_DC) +{ + char *result = NULL; + intptr_t address_rand; + + if (!SYMFONY_DEBUG_G(req_rand_init)) { + if (!BG(mt_rand_is_seeded)) { + php_mt_srand(GENERATE_SEED() TSRMLS_CC); + } + SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(TSRMLS_C); + } + + address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init); + + spprintf(&result, 17, "%016zx", address_rand); + + return result; +} + +static const char *_symfony_debug_zval_type(zval *zv) +{ + switch (Z_TYPE_P(zv)) { + case IS_NULL: + return "NULL"; + break; + + case IS_BOOL: + return "boolean"; + break; + + case IS_LONG: + return "integer"; + break; + + case IS_DOUBLE: + return "double"; + break; + + case IS_STRING: + return "string"; + break; + + case IS_ARRAY: + return "array"; + break; + + case IS_OBJECT: + return "object"; + + case IS_RESOURCE: + return "resource"; + + default: + return "unknown type"; + } +} + +zend_module_entry symfony_debug_module_entry = { + STANDARD_MODULE_HEADER, + "symfony_debug", + symfony_debug_functions, + PHP_MINIT(symfony_debug), + PHP_MSHUTDOWN(symfony_debug), + PHP_RINIT(symfony_debug), + PHP_RSHUTDOWN(symfony_debug), + PHP_MINFO(symfony_debug), + PHP_SYMFONY_DEBUG_VERSION, + PHP_MODULE_GLOBALS(symfony_debug), + PHP_GINIT(symfony_debug), + PHP_GSHUTDOWN(symfony_debug), + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +#ifdef COMPILE_DL_SYMFONY_DEBUG +ZEND_GET_MODULE(symfony_debug) +#endif + +PHP_GINIT_FUNCTION(symfony_debug) +{ + memset(symfony_debug_globals, 0 , sizeof(*symfony_debug_globals)); +} + +PHP_GSHUTDOWN_FUNCTION(symfony_debug) +{ + +} + +PHP_MINIT_FUNCTION(symfony_debug) +{ + SYMFONY_DEBUG_G(old_error_cb) = zend_error_cb; + zend_error_cb = symfony_debug_error_cb; + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(symfony_debug) +{ + zend_error_cb = SYMFONY_DEBUG_G(old_error_cb); + + return SUCCESS; +} + +PHP_RINIT_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_RSHUTDOWN_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_MINFO_FUNCTION(symfony_debug) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Symfony Debug support", "enabled"); + php_info_print_table_header(2, "Symfony Debug version", PHP_SYMFONY_DEBUG_VERSION); + php_info_print_table_end(); +} diff --git a/core/vendor/symfony/debug/Resources/ext/tests/001.phpt b/core/vendor/symfony/debug/Resources/ext/tests/001.phpt new file mode 100644 index 0000000..4a87cd3 --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/tests/001.phpt @@ -0,0 +1,155 @@ +--TEST-- +Test symfony_zval_info API +--SKIPIF-- + +--FILE-- + $int, + 'float' => $float, + 'str' => $str, + 'object' => $object, + 'array' => $array, + 'resource' => $resource, + 'null' => $null, + 'bool' => $bool, + 'refcount' => &$refcount2, +); + +var_dump(symfony_zval_info('int', $var)); +var_dump(symfony_zval_info('float', $var)); +var_dump(symfony_zval_info('str', $var)); +var_dump(symfony_zval_info('object', $var)); +var_dump(symfony_zval_info('array', $var)); +var_dump(symfony_zval_info('resource', $var)); +var_dump(symfony_zval_info('null', $var)); +var_dump(symfony_zval_info('bool', $var)); + +var_dump(symfony_zval_info('refcount', $var)); +var_dump(symfony_zval_info('not-exist', $var)); +?> +--EXPECTF-- +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(6) "double" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(5) { + ["type"]=> + string(6) "string" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["strlen"]=> + int(6) +} +array(8) { + ["type"]=> + string(6) "object" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["object_class"]=> + string(8) "stdClass" + ["object_refcount"]=> + int(1) + ["object_hash"]=> + string(32) "%s" + ["object_handle"]=> + int(%d) +} +array(5) { + ["type"]=> + string(5) "array" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["array_count"]=> + int(2) +} +array(7) { + ["type"]=> + string(8) "resource" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["resource_handle"]=> + int(%d) + ["resource_type"]=> + string(6) "stream" + ["resource_refcount"]=> + int(1) +} +array(4) { + ["type"]=> + string(4) "NULL" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "boolean" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(3) + ["zval_isref"]=> + bool(true) +} +NULL diff --git a/core/vendor/symfony/debug/Resources/ext/tests/002.phpt b/core/vendor/symfony/debug/Resources/ext/tests/002.phpt new file mode 100644 index 0000000..afc7bb4 --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/tests/002.phpt @@ -0,0 +1,65 @@ +--TEST-- +Test symfony_debug_backtrace in case of fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Call to undefined function notexist() in %s on line %d +Array +( + [0] => Array + ( + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => foo + [args] => Array + ( + ) + + ) + + [2] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/core/vendor/symfony/debug/Resources/ext/tests/002_1.phpt b/core/vendor/symfony/debug/Resources/ext/tests/002_1.phpt new file mode 100644 index 0000000..86de3e1 --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/tests/002_1.phpt @@ -0,0 +1,48 @@ +--TEST-- +Test symfony_debug_backtrace in case of non fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Array +( + [0] => Array + ( + [file] => %s + [line] => %d + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/core/vendor/symfony/debug/Resources/ext/tests/003.phpt b/core/vendor/symfony/debug/Resources/ext/tests/003.phpt new file mode 100644 index 0000000..ce3c4e0 --- /dev/null +++ b/core/vendor/symfony/debug/Resources/ext/tests/003.phpt @@ -0,0 +1,87 @@ +--TEST-- +Test ErrorHandler in case of fatal error +--SKIPIF-- + +--FILE-- +setExceptionHandler('print_r'); + +if (function_exists('xdebug_disable')) { + xdebug_disable(); +} + +bar(); +?> +--EXPECTF-- +Fatal error: Call to undefined function Symfony\Component\Debug\notexist() in %s on line %d +Symfony\Component\Debug\Exception\UndefinedFunctionException Object +( + [message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug". + [string:Exception:private] => + [code:protected] => 0 + [file:protected] => %s + [line:protected] => %d + [trace:Exception:private] => Array + ( + [0] => Array + ( +%A [function] => Symfony\Component\Debug\foo +%A [args] => Array + ( + ) + + ) + + [1] => Array + ( +%A [function] => Symfony\Component\Debug\bar +%A [args] => Array + ( + ) + + ) +%A + ) + + [previous:Exception:private] => + [severity:protected] => 1 +) diff --git a/core/vendor/symfony/debug/Tests/DebugClassLoaderTest.php b/core/vendor/symfony/debug/Tests/DebugClassLoaderTest.php new file mode 100644 index 0000000..437f1e2 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/DebugClassLoaderTest.php @@ -0,0 +1,315 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\Debug\ErrorHandler; + +class DebugClassLoaderTest extends TestCase +{ + /** + * @var int Error reporting level before running tests + */ + private $errorReporting; + + private $loader; + + protected function setUp() + { + $this->errorReporting = error_reporting(E_ALL | E_STRICT); + $this->loader = new ClassLoader(); + spl_autoload_register(array($this->loader, 'loadClass'), true, true); + DebugClassLoader::enable(); + } + + protected function tearDown() + { + DebugClassLoader::disable(); + spl_autoload_unregister(array($this->loader, 'loadClass')); + error_reporting($this->errorReporting); + } + + public function testIdempotence() + { + DebugClassLoader::enable(); + + $functions = spl_autoload_functions(); + foreach ($functions as $function) { + if (is_array($function) && $function[0] instanceof DebugClassLoader) { + $reflClass = new \ReflectionClass($function[0]); + $reflProp = $reflClass->getProperty('classLoader'); + $reflProp->setAccessible(true); + + $this->assertNotInstanceOf('Symfony\Component\Debug\DebugClassLoader', $reflProp->getValue($function[0])); + + return; + } + } + + $this->fail('DebugClassLoader did not register'); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage boo + */ + public function testThrowingClass() + { + try { + class_exists(__NAMESPACE__.'\Fixtures\Throwing'); + $this->fail('Exception expected'); + } catch (\Exception $e) { + $this->assertSame('boo', $e->getMessage()); + } + + // the second call also should throw + class_exists(__NAMESPACE__.'\Fixtures\Throwing'); + } + + public function testUnsilencing() + { + if (\PHP_VERSION_ID >= 70000) { + $this->markTestSkipped('PHP7 throws exceptions, unsilencing is not required anymore.'); + } + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM is not handled in this test case.'); + } + + ob_start(); + + $this->iniSet('log_errors', 0); + $this->iniSet('display_errors', 1); + + // See below: this will fail with parse error + // but this should not be @-silenced. + @class_exists(__NAMESPACE__.'\TestingUnsilencing', true); + + $output = ob_get_clean(); + + $this->assertStringMatchesFormat('%aParse error%a', $output); + } + + public function testStacking() + { + // the ContextErrorException must not be loaded to test the workaround + // for https://bugs.php.net/65322. + if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) { + $this->markTestSkipped('The ContextErrorException class is already loaded.'); + } + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM is not handled in this test case.'); + } + + ErrorHandler::register(); + + try { + // Trigger autoloading + E_STRICT at compile time + // which in turn triggers $errorHandler->handle() + // that again triggers autoloading for ContextErrorException. + // Error stacking works around the bug above and everything is fine. + + eval(' + namespace '.__NAMESPACE__.'; + class ChildTestingStacking extends TestingStacking { function foo($bar) {} } + '); + $this->fail('ContextErrorException expected'); + } catch (\ErrorException $exception) { + // if an exception is thrown, the test passed + restore_error_handler(); + restore_exception_handler(); + $this->assertStringStartsWith(__FILE__, $exception->getFile()); + if (\PHP_VERSION_ID < 70000) { + $this->assertRegExp('/^Runtime Notice: Declaration/', $exception->getMessage()); + $this->assertEquals(E_STRICT, $exception->getSeverity()); + } else { + $this->assertRegExp('/^Warning: Declaration/', $exception->getMessage()); + $this->assertEquals(E_WARNING, $exception->getSeverity()); + } + } catch (\Exception $exception) { + restore_error_handler(); + restore_exception_handler(); + + throw $exception; + } + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between loaded and declared class names + */ + public function testNameCaseMismatch() + { + class_exists(__NAMESPACE__.'\TestingCaseMismatch', true); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between class and real file names + */ + public function testFileCaseMismatch() + { + if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) { + $this->markTestSkipped('Can only be run on case insensitive filesystems'); + } + + class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between loaded and declared class names + */ + public function testPsr4CaseMismatch() + { + class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true); + } + + public function testNotPsr0() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true)); + } + + public function testNotPsr0Bis() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true)); + } + + public function testClassAlias() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true)); + } + + /** + * @dataProvider provideDeprecatedSuper + */ + public function testDeprecatedSuper($class, $super, $type) + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_DEPRECATED); + + class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The Test\Symfony\Component\Debug\Tests\\'.$class.' class '.$type.' Symfony\Component\Debug\Tests\Fixtures\\'.$super.' that is deprecated but this is a test deprecation notice', + ); + + $this->assertSame($xError, $lastError); + } + + public function provideDeprecatedSuper() + { + return array( + array('DeprecatedInterfaceClass', 'DeprecatedInterface', 'implements'), + array('DeprecatedParentClass', 'DeprecatedClass', 'extends'), + ); + } + + public function testDeprecatedSuperInSameNamespace() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_NOTICE, + 'message' => '', + ); + + $this->assertSame($xError, $lastError); + } + + public function testReservedForPhp7() + { + if (\PHP_VERSION_ID >= 70000) { + $this->markTestSkipped('PHP7 already prevents using reserved names.'); + } + + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Test\\'.__NAMESPACE__.'\\Float', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'Test\Symfony\Component\Debug\Tests\Float uses a reserved class name (Float) that will break on PHP 7 and higher', + ); + + $this->assertSame($xError, $lastError); + } +} + +class ClassLoader +{ + public function loadClass($class) + { + } + + public function getClassMap() + { + return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php'); + } + + public function findFile($class) + { + $fixtureDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR; + + if (__NAMESPACE__.'\TestingUnsilencing' === $class) { + eval('-- parse error --'); + } elseif (__NAMESPACE__.'\TestingStacking' === $class) { + eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }'); + } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) { + eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}'); + } elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) { + return $fixtureDir.'CaseMismatch.php'; + } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) { + return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php'; + } elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) { + return $fixtureDir.'reallyNotPsr0.php'; + } elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) { + return $fixtureDir.'notPsr0Bis.php'; + } elseif (__NAMESPACE__.'\Fixtures\DeprecatedInterface' === $class) { + return $fixtureDir.'DeprecatedInterface.php'; + } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) { + eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); + } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); + } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}'); + } elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class Float {}'); + } + } +} diff --git a/core/vendor/symfony/debug/Tests/ErrorHandlerTest.php b/core/vendor/symfony/debug/Tests/ErrorHandlerTest.php new file mode 100644 index 0000000..d8f4a74 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/ErrorHandlerTest.php @@ -0,0 +1,571 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\Exception\ContextErrorException; + +/** + * ErrorHandlerTest. + * + * @author Robert Schönthal + * @author Nicolas Grekas + */ +class ErrorHandlerTest extends TestCase +{ + public function testRegister() + { + $handler = ErrorHandler::register(); + + try { + $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler); + $this->assertSame($handler, ErrorHandler::register()); + + $newHandler = new ErrorHandler(); + + $this->assertSame($handler, ErrorHandler::register($newHandler, false)); + $h = set_error_handler('var_dump'); + restore_error_handler(); + $this->assertSame(array($handler, 'handleError'), $h); + + try { + $this->assertSame($newHandler, ErrorHandler::register($newHandler, true)); + $h = set_error_handler('var_dump'); + restore_error_handler(); + $this->assertSame(array($newHandler, 'handleError'), $h); + } catch (\Exception $e) { + } + + restore_error_handler(); + restore_exception_handler(); + + if (isset($e)) { + throw $e; + } + } catch (\Exception $e) { + } + + restore_error_handler(); + restore_exception_handler(); + + if (isset($e)) { + throw $e; + } + } + + public function testErrorGetLast() + { + $handler = ErrorHandler::register(); + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $handler->setDefaultLogger($logger); + $handler->screamAt(E_ALL); + + try { + @trigger_error('Hello', E_USER_WARNING); + $expected = array( + 'type' => E_USER_WARNING, + 'message' => 'Hello', + 'file' => __FILE__, + 'line' => __LINE__ - 5, + ); + $this->assertSame($expected, error_get_last()); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + public function testNotice() + { + ErrorHandler::register(); + + try { + self::triggerNotice($this); + $this->fail('ContextErrorException expected'); + } catch (ContextErrorException $exception) { + // if an exception is thrown, the test passed + restore_error_handler(); + restore_exception_handler(); + + $this->assertEquals(E_NOTICE, $exception->getSeverity()); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage()); + if (\PHP_VERSION_ID < 70200) { + $this->assertArrayHasKey('foobar', $exception->getContext()); + } + + $trace = $exception->getTrace(); + $this->assertEquals(__FILE__, $trace[0]['file']); + $this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']); + $this->assertEquals('handleError', $trace[0]['function']); + $this->assertEquals('->', $trace[0]['type']); + + $this->assertEquals(__FILE__, $trace[1]['file']); + $this->assertEquals(__CLASS__, $trace[1]['class']); + $this->assertEquals('triggerNotice', $trace[1]['function']); + $this->assertEquals('::', $trace[1]['type']); + + $this->assertEquals(__FILE__, $trace[1]['file']); + $this->assertEquals(__CLASS__, $trace[2]['class']); + $this->assertEquals(__FUNCTION__, $trace[2]['function']); + $this->assertEquals('->', $trace[2]['type']); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + // dummy function to test trace in error handler. + private static function triggerNotice($that) + { + // dummy variable to check for in error handler. + $foobar = 123; + $that->assertSame('', $foo.$foo.$bar); + } + + public function testConstruct() + { + try { + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + $this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0)); + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + public function testDefaultLogger() + { + try { + $handler = ErrorHandler::register(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $handler->setDefaultLogger($logger, E_NOTICE); + $handler->setDefaultLogger($logger, array(E_USER_NOTICE => LogLevel::CRITICAL)); + + $loggers = array( + E_DEPRECATED => array(null, LogLevel::INFO), + E_USER_DEPRECATED => array(null, LogLevel::INFO), + E_NOTICE => array($logger, LogLevel::WARNING), + E_USER_NOTICE => array($logger, LogLevel::CRITICAL), + E_STRICT => array(null, LogLevel::WARNING), + E_WARNING => array(null, LogLevel::WARNING), + E_USER_WARNING => array(null, LogLevel::WARNING), + E_COMPILE_WARNING => array(null, LogLevel::WARNING), + E_CORE_WARNING => array(null, LogLevel::WARNING), + E_USER_ERROR => array(null, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), + E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), + E_PARSE => array(null, LogLevel::CRITICAL), + E_ERROR => array(null, LogLevel::CRITICAL), + E_CORE_ERROR => array(null, LogLevel::CRITICAL), + ); + $this->assertSame($loggers, $handler->setLoggers(array())); + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + public function testHandleError() + { + try { + $handler = ErrorHandler::register(); + $handler->throwAt(0, true); + $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + try { + $handler->handleError(4, 'foo', 'foo.php', 12, array()); + } catch (\ErrorException $e) { + $this->assertSame('Parse Error: foo', $e->getMessage()); + $this->assertSame(4, $e->getSeverity()); + $this->assertSame('foo.php', $e->getFile()); + $this->assertSame(12, $e->getLine()); + } + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(E_USER_DEPRECATED, true); + $this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(E_DEPRECATED, true); + $this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $that = $this; + $warnArgCheck = function ($logLevel, $message, $context) use ($that) { + $that->assertEquals('info', $logLevel); + $that->assertEquals('foo', $message); + $that->assertArrayHasKey('type', $context); + $that->assertEquals($context['type'], E_USER_DEPRECATED); + $that->assertArrayHasKey('stack', $context); + $that->assertInternalType('array', $context['stack']); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($warnArgCheck)) + ; + + $handler = ErrorHandler::register(); + $handler->setDefaultLogger($logger, E_USER_DEPRECATED); + $this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $that = $this; + $logArgCheck = function ($level, $message, $context) use ($that) { + $that->assertEquals('Undefined variable: undefVar', $message); + $that->assertArrayHasKey('type', $context); + $that->assertEquals($context['type'], E_NOTICE); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler = ErrorHandler::register(); + $handler->setDefaultLogger($logger, E_NOTICE); + $handler->screamAt(E_NOTICE); + unset($undefVar); + @$undefVar++; + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + public function testHandleDeprecation() + { + $that = $this; + $logArgCheck = function ($level, $message, $context) use ($that) { + $that->assertEquals(LogLevel::INFO, $level); + $that->assertArrayHasKey('level', $context); + $that->assertEquals(E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED, $context['level']); + $that->assertArrayHasKey('stack', $context); + }; + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler = new ErrorHandler(); + $handler->setDefaultLogger($logger); + @$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array()); + } + + /** + * @group no-hhvm + */ + public function testHandleException() + { + try { + $handler = ErrorHandler::register(); + + $exception = new \Exception('foo'); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $that = $this; + $logArgCheck = function ($level, $message, $context) use ($that) { + $that->assertEquals('Uncaught Exception: foo', $message); + $that->assertArrayHasKey('type', $context); + $that->assertEquals($context['type'], E_ERROR); + }; + + $logger + ->expects($this->exactly(2)) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler->setDefaultLogger($logger, E_ERROR); + + try { + $handler->handleException($exception); + $this->fail('Exception expected'); + } catch (\Exception $e) { + $this->assertSame($exception, $e); + } + + $that = $this; + $handler->setExceptionHandler(function ($e) use ($exception, $that) { + $that->assertSame($exception, $e); + }); + + $handler->handleException($exception); + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + public function testErrorStacking() + { + try { + $handler = ErrorHandler::register(); + $handler->screamAt(E_USER_WARNING); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $logger + ->expects($this->exactly(2)) + ->method('log') + ->withConsecutive( + array($this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')), + array($this->equalTo(LogLevel::DEBUG), $this->equalTo('Silenced warning')) + ) + ; + + $handler->setDefaultLogger($logger, array(E_USER_WARNING => LogLevel::WARNING)); + + ErrorHandler::stackErrors(); + @trigger_error('Silenced warning', E_USER_WARNING); + $logger->log(LogLevel::WARNING, 'Dummy log'); + ErrorHandler::unstackErrors(); + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + /** + * @group no-hhvm + */ + public function testHandleFatalError() + { + try { + $handler = ErrorHandler::register(); + + $error = array( + 'type' => E_PARSE, + 'message' => 'foo', + 'file' => 'bar', + 'line' => 123, + ); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $that = $this; + $logArgCheck = function ($level, $message, $context) use ($that) { + $that->assertEquals('Fatal Parse Error: foo', $message); + $that->assertArrayHasKey('type', $context); + $that->assertEquals($context['type'], E_PARSE); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler->setDefaultLogger($logger, E_PARSE); + + $handler->handleFatalError($error); + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + /** + * @requires PHP 7 + */ + public function testHandleErrorException() + { + $exception = new \Error("Class 'Foo' not found"); + + $handler = new ErrorHandler(); + $handler->setExceptionHandler(function () use (&$args) { + $args = func_get_args(); + }); + + $handler->handleException($exception); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]); + $this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage()); + } + + /** + * @group no-hhvm + */ + public function testHandleFatalErrorOnHHVM() + { + try { + $handler = ErrorHandler::register(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with( + $this->equalTo(LogLevel::CRITICAL), + $this->equalTo('Fatal Error: foo'), + $this->equalTo(array( + 'type' => 1, + 'file' => 'bar', + 'line' => 123, + 'level' => -1, + 'stack' => array(456), + )) + ) + ; + + $handler->setDefaultLogger($logger, E_ERROR); + + $error = array( + 'type' => E_ERROR + 0x1000000, // This error level is used by HHVM for fatal errors + 'message' => 'foo', + 'file' => 'bar', + 'line' => 123, + 'context' => array(123), + 'backtrace' => array(456), + ); + + call_user_func_array(array($handler, 'handleError'), $error); + $handler->handleFatalError($error); + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + /** + * @group legacy + */ + public function testLegacyInterface() + { + try { + $handler = ErrorHandler::register(0); + $this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $that = $this; + $logArgCheck = function ($level, $message, $context) use ($that) { + $that->assertEquals('Undefined variable: undefVar', $message); + $that->assertArrayHasKey('type', $context); + $that->assertEquals($context['type'], E_NOTICE); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler = ErrorHandler::register(E_NOTICE); + @$handler->setLogger($logger, 'scream'); + unset($undefVar); + @$undefVar++; + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + /** + * @expectedException \Exception + * @group no-hhvm + */ + public function testCustomExceptionHandler() + { + $handler = new ErrorHandler(); + $handler->setExceptionHandler(function ($e) use ($handler) { + $handler->handleException($e); + }); + + $handler->handleException(new \Exception()); + } +} diff --git a/core/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php b/core/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php new file mode 100644 index 0000000..269abf0 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\GoneHttpException; +use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException; +use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; +use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; +use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; + +class FlattenExceptionTest extends TestCase +{ + public function testStatusCode() + { + $flattened = FlattenException::create(new \RuntimeException(), 403); + $this->assertEquals('403', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new \RuntimeException()); + $this->assertEquals('500', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new NotFoundHttpException()); + $this->assertEquals('404', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"')); + $this->assertEquals('401', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new BadRequestHttpException()); + $this->assertEquals('400', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new NotAcceptableHttpException()); + $this->assertEquals('406', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new ConflictHttpException()); + $this->assertEquals('409', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals('405', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new AccessDeniedHttpException()); + $this->assertEquals('403', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new GoneHttpException()); + $this->assertEquals('410', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new LengthRequiredHttpException()); + $this->assertEquals('411', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new PreconditionFailedHttpException()); + $this->assertEquals('412', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new PreconditionRequiredHttpException()); + $this->assertEquals('428', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException()); + $this->assertEquals('503', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException()); + $this->assertEquals('429', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new UnsupportedMediaTypeHttpException()); + $this->assertEquals('415', $flattened->getStatusCode()); + } + + public function testHeadersForHttpException() + { + $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals(array('Allow' => 'POST'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"')); + $this->assertEquals(array('WWW-Authenticate' => 'Basic realm="My Realm"'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException('Fri, 31 Dec 1999 23:59:59 GMT')); + $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException(120)); + $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException('Fri, 31 Dec 1999 23:59:59 GMT')); + $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException(120)); + $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testFlattenHttpException(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $flattened2 = FlattenException::create($exception); + + $flattened->setPrevious($flattened2); + + $this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.'); + $this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.'); + $this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception'); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testPrevious(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $flattened2 = FlattenException::create($exception); + + $flattened->setPrevious($flattened2); + + $this->assertSame($flattened2, $flattened->getPrevious()); + + $this->assertSame(array($flattened2), $flattened->getAllPrevious()); + } + + /** + * @requires PHP 7.0 + */ + public function testPreviousError() + { + $exception = new \Exception('test', 123, new \ParseError('Oh noes!', 42)); + + $flattened = FlattenException::create($exception)->getPrevious(); + + $this->assertEquals($flattened->getMessage(), 'Parse error: Oh noes!', 'The message is copied from the original exception.'); + $this->assertEquals($flattened->getCode(), 42, 'The code is copied from the original exception.'); + $this->assertEquals($flattened->getClass(), 'Symfony\Component\Debug\Exception\FatalThrowableError', 'The class is set to the class of the original exception'); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testLine(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $this->assertSame($exception->getLine(), $flattened->getLine()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testFile(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $this->assertSame($exception->getFile(), $flattened->getFile()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testToArray(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $flattened->setTrace(array(), 'foo.php', 123); + + $this->assertEquals(array( + array( + 'message' => 'test', + 'class' => 'Exception', + 'trace' => array(array( + 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, + 'args' => array(), + )), + ), + ), $flattened->toArray()); + } + + public function flattenDataProvider() + { + return array( + array(new \Exception('test', 123)), + ); + } + + public function testRecursionInArguments() + { + $a = null; + $a = array('foo', array(2, &$a)); + $exception = $this->createException($a); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + $this->assertContains('*DEEP NESTED ARRAY*', serialize($trace)); + } + + public function testTooBigArray() + { + $a = array(); + for ($i = 0; $i < 20; ++$i) { + for ($j = 0; $j < 50; ++$j) { + for ($k = 0; $k < 10; ++$k) { + $a[$i][$j][$k] = 'value'; + } + } + } + $a[20] = 'value'; + $a[21] = 'value1'; + $exception = $this->createException($a); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + $serializeTrace = serialize($trace); + + $this->assertContains('*SKIPPED over 10000 entries*', $serializeTrace); + $this->assertNotContains('*value1*', $serializeTrace); + } + + private function createException($foo) + { + return new \Exception(); + } + + public function testSetTraceIncompleteClass() + { + $flattened = FlattenException::create(new \Exception('test', 123)); + $flattened->setTrace( + array( + array( + 'file' => __FILE__, + 'line' => 123, + 'function' => 'test', + 'args' => array( + unserialize('O:14:"BogusTestClass":0:{}'), + ), + ), + ), + 'foo.php', 123 + ); + + $this->assertEquals(array( + array( + 'message' => 'test', + 'class' => 'Exception', + 'trace' => array( + array( + 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', + 'file' => 'foo.php', 'line' => 123, + 'args' => array(), + ), + array( + 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => 'test', + 'file' => __FILE__, 'line' => 123, + 'args' => array( + array( + 'incomplete-object', 'BogusTestClass', + ), + ), + ), + ), + ), + ), $flattened->toArray()); + } +} diff --git a/core/vendor/symfony/debug/Tests/ExceptionHandlerTest.php b/core/vendor/symfony/debug/Tests/ExceptionHandlerTest.php new file mode 100644 index 0000000..77cc0b5 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/ExceptionHandlerTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; + +require_once __DIR__.'/HeaderMock.php'; + +class ExceptionHandlerTest extends TestCase +{ + protected function setUp() + { + testHeader(); + } + + protected function tearDown() + { + testHeader(); + } + + public function testDebug() + { + $handler = new ExceptionHandler(false); + + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('

    Whoops, looks like something went wrong.

    ', $response); + $this->assertNotContains('

    ', $response); + + $handler = new ExceptionHandler(true); + + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('

    Whoops, looks like something went wrong.

    ', $response); + $this->assertContains('

    ', $response); + } + + public function testStatusCode() + { + $handler = new ExceptionHandler(false, 'iso8859-1'); + + ob_start(); + $handler->sendPhpResponse(new NotFoundHttpException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('Sorry, the page you are looking for could not be found.', $response); + + $expectedHeaders = array( + array('HTTP/1.0 404', true, null), + array('Content-Type: text/html; charset=iso8859-1', true, null), + ); + + $this->assertSame($expectedHeaders, testHeader()); + } + + public function testHeaders() + { + $handler = new ExceptionHandler(false, 'iso8859-1'); + + ob_start(); + $handler->sendPhpResponse(new MethodNotAllowedHttpException(array('POST'))); + $response = ob_get_clean(); + + $expectedHeaders = array( + array('HTTP/1.0 405', true, null), + array('Allow: POST', false, null), + array('Content-Type: text/html; charset=iso8859-1', true, null), + ); + + $this->assertSame($expectedHeaders, testHeader()); + } + + public function testNestedExceptions() + { + $handler = new ExceptionHandler(true); + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar'))); + $response = ob_get_clean(); + + $this->assertStringMatchesFormat('%AFoo%ABar%A', $response); + } + + public function testHandle() + { + $exception = new \Exception('foo'); + + $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(array('sendPhpResponse'))->getMock(); + $handler + ->expects($this->exactly(2)) + ->method('sendPhpResponse'); + + $handler->handle($exception); + + $that = $this; + $handler->setHandler(function ($e) use ($exception, $that) { + $that->assertSame($exception, $e); + }); + + $handler->handle($exception); + } + + public function testHandleOutOfMemoryException() + { + $exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__); + + $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(array('sendPhpResponse'))->getMock(); + $handler + ->expects($this->once()) + ->method('sendPhpResponse'); + + $that = $this; + $handler->setHandler(function ($e) use ($that) { + $that->fail('OutOfMemoryException should bypass the handler'); + }); + + $handler->handle($exception); + } +} diff --git a/core/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php b/core/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php new file mode 100644 index 0000000..0611ed9 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php @@ -0,0 +1,201 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader; +use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; +use Symfony\Component\Debug\DebugClassLoader; +use Composer\Autoload\ClassLoader as ComposerClassLoader; + +class ClassNotFoundFatalErrorHandlerTest extends TestCase +{ + public static function setUpBeforeClass() + { + foreach (spl_autoload_functions() as $function) { + if (!is_array($function)) { + continue; + } + + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + } + + if ($function[0] instanceof ComposerClassLoader) { + $function[0]->add('Symfony_Component_Debug_Tests_Fixtures', dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + break; + } + } + } + + /** + * @dataProvider provideClassNotFoundData + */ + public function testHandleClassNotFound($error, $translatedMessage, $autoloader = null) + { + if ($autoloader) { + // Unregister all autoloaders to ensure the custom provided + // autoloader is the only one to be used during the test run. + $autoloaders = spl_autoload_functions(); + array_map('spl_autoload_unregister', $autoloaders); + spl_autoload_register($autoloader); + } + + $handler = new ClassNotFoundFatalErrorHandler(); + + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + if ($autoloader) { + spl_autoload_unregister($autoloader); + array_map('spl_autoload_register', $autoloaders); + } + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception); + $this->assertSame($translatedMessage, $exception->getMessage()); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + /** + * @group legacy + */ + public function testLegacyHandleClassNotFound() + { + $prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception')); + $symfonyUniversalClassLoader = new SymfonyUniversalClassLoader(); + $symfonyUniversalClassLoader->registerPrefixes($prefixes); + + $this->testHandleClassNotFound( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + array($symfonyUniversalClassLoader, 'loadClass') + ); + } + + public function provideClassNotFoundData() + { + $prefixes = array('Symfony\Component\Debug\Exception\\' => realpath(__DIR__.'/../../Exception')); + + $symfonyAutoloader = new SymfonyClassLoader(); + $symfonyAutoloader->addPrefixes($prefixes); + + $debugClassLoader = new DebugClassLoader(array($symfonyAutoloader, 'loadClass')); + + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'WhizBangFactory\' not found', + ), + "Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found', + ), + "Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'PEARClass\' not found', + ), + "Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + array($symfonyAutoloader, 'loadClass'), + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + array($debugClassLoader, 'loadClass'), + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?", + function ($className) { /* do nothing here */ }, + ), + ); + } + + public function testCannotRedeclareClass() + { + if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) { + $this->markTestSkipped('Can only be run on case insensitive filesystems'); + } + + require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP'; + + $error = array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found', + ); + + $handler = new ClassNotFoundFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception); + } +} diff --git a/core/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php b/core/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php new file mode 100644 index 0000000..1dc2120 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; + +class UndefinedFunctionFatalErrorHandlerTest extends TestCase +{ + /** + * @dataProvider provideUndefinedFunctionData + */ + public function testUndefinedFunction($error, $translatedMessage) + { + $handler = new UndefinedFunctionFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception); + // class names are case insensitive and PHP/HHVM do not return the same + $this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage())); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + public function provideUndefinedFunctionData() + { + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function test_namespaced_function()', + ), + "Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()', + ), + "Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function foo()', + ), + 'Attempted to call function "foo" from the global namespace.', + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function Foo\\Bar\\Baz\\foo()', + ), + 'Attempted to call function "foo" from namespace "Foo\Bar\Baz".', + ), + ); + } +} + +function test_namespaced_function() +{ +} diff --git a/core/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php b/core/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php new file mode 100644 index 0000000..739e5b2 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; + +class UndefinedMethodFatalErrorHandlerTest extends TestCase +{ + /** + * @dataProvider provideUndefinedMethodData + */ + public function testUndefinedMethod($error, $translatedMessage) + { + $handler = new UndefinedMethodFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedMethodException', $exception); + $this->assertSame($translatedMessage, $exception->getMessage()); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + public function provideUndefinedMethodData() + { + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::what()', + ), + 'Attempted to call an undefined method named "what" of class "SplObjectStorage".', + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::walid()', + ), + "Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::offsetFet()', + ), + "Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?", + ), + array( + array( + 'type' => 1, + 'message' => 'Call to undefined method class@anonymous::test()', + 'file' => '/home/possum/work/symfony/test.php', + 'line' => 11, + ), + 'Attempted to call an undefined method named "test" of class "class@anonymous".', + ), + ); + } +} diff --git a/core/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php b/core/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php new file mode 100644 index 0000000..9d6dbaa --- /dev/null +++ b/core/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php @@ -0,0 +1,3 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +function headers_sent() +{ + return false; +} + +function header($str, $replace = true, $status = null) +{ + Tests\testHeader($str, $replace, $status); +} + +namespace Symfony\Component\Debug\Tests; + +function testHeader() +{ + static $headers = array(); + + if (!$h = func_get_args()) { + $h = $headers; + $headers = array(); + + return $h; + } + + $headers[] = func_get_args(); +} diff --git a/core/vendor/symfony/debug/Tests/MockExceptionHandler.php b/core/vendor/symfony/debug/Tests/MockExceptionHandler.php new file mode 100644 index 0000000..2d6ce56 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/MockExceptionHandler.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use Symfony\Component\Debug\ExceptionHandler; + +class MockExceptionHandler extends ExceptionHandler +{ + public $e; + + public function handle(\Exception $e) + { + $this->e = $e; + } +} diff --git a/core/vendor/symfony/debug/Tests/phpt/decorate_exception_hander.phpt b/core/vendor/symfony/debug/Tests/phpt/decorate_exception_hander.phpt new file mode 100644 index 0000000..1de9b29 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/phpt/decorate_exception_hander.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test catching fatal errors when handlers are nested +--FILE-- + +--EXPECTF-- +Fatal error: Class 'Symfony\Component\Debug\missing' not found in %s on line %d +object(Symfony\Component\Debug\Exception\ClassNotFoundException)#%d (8) { + ["message":protected]=> + string(131) "Attempted to load class "missing" from namespace "Symfony\Component\Debug". +Did you forget a "use" statement for another namespace?" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(0) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(%d) + ["trace":"Exception":private]=> + array(%d) {%A} + ["previous":"Exception":private]=> + NULL + ["severity":protected]=> + int(1) +} diff --git a/core/vendor/symfony/debug/Tests/phpt/exception_rethrown.phpt b/core/vendor/symfony/debug/Tests/phpt/exception_rethrown.phpt new file mode 100644 index 0000000..9df0a65 --- /dev/null +++ b/core/vendor/symfony/debug/Tests/phpt/exception_rethrown.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test rethrowing in custom exception handler +--FILE-- +setDefaultLogger(new TestLogger()); +ini_set('display_errors', 1); + +throw new \Exception('foo'); +?> +--EXPECTF-- +Uncaught Exception: foo +123 +Fatal error: Uncaught %s:25 +Stack trace: +%a diff --git a/core/vendor/symfony/debug/Tests/phpt/fatal_with_nested_handlers.phpt b/core/vendor/symfony/debug/Tests/phpt/fatal_with_nested_handlers.phpt new file mode 100644 index 0000000..897be3e --- /dev/null +++ b/core/vendor/symfony/debug/Tests/phpt/fatal_with_nested_handlers.phpt @@ -0,0 +1,42 @@ +--TEST-- +Test catching fatal errors when handlers are nested +--FILE-- +setExceptionHandler('print_r'); + +if (true) { + class Broken implements \Serializable + { + } +} + +?> +--EXPECTF-- +array(1) { + [0]=> + string(37) "Error and exception handlers do match" +} +object(Symfony\Component\Debug\Exception\FatalErrorException)#%d (8) { + ["message":protected]=> + string(199) "Error: Class Symfony\Component\Debug\Broken contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize)" +%a +} diff --git a/core/vendor/symfony/debug/composer.json b/core/vendor/symfony/debug/composer.json new file mode 100644 index 0000000..2ae0685 --- /dev/null +++ b/core/vendor/symfony/debug/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/debug", + "type": "library", + "description": "Symfony Debug Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Debug\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + } +} diff --git a/core/vendor/symfony/debug/phpunit.xml.dist b/core/vendor/symfony/debug/phpunit.xml.dist new file mode 100644 index 0000000..12e5861 --- /dev/null +++ b/core/vendor/symfony/debug/phpunit.xml.dist @@ -0,0 +1,33 @@ + + + + + + + + + + ./Tests/ + + + ./Resources/ext/tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/core/vendor/symfony/dom-crawler/.gitignore b/core/vendor/symfony/dom-crawler/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/core/vendor/symfony/dom-crawler/CHANGELOG.md b/core/vendor/symfony/dom-crawler/CHANGELOG.md new file mode 100644 index 0000000..48fd323 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/CHANGELOG.md @@ -0,0 +1,45 @@ +CHANGELOG +========= + +2.5.0 +----- + +* [BC BREAK] The default value for checkbox and radio inputs without a value attribute have changed + from '1' to 'on' to match the HTML specification. +* [BC BREAK] The typehints on the `Link`, `Form` and `FormField` classes have been changed from + `\DOMNode` to `DOMElement`. Using any other type of `DOMNode` was triggering fatal errors in previous + versions. Code extending these classes will need to update the typehints when overwriting these methods. + +2.4.0 +----- + + * `Crawler::addXmlContent()` removes the default document namespace again if it's an only namespace. + * added support for automatic discovery and explicit registration of document + namespaces for `Crawler::filterXPath()` and `Crawler::filter()` + * improved content type guessing in `Crawler::addContent()` + * [BC BREAK] `Crawler::addXmlContent()` no longer removes the default document + namespace + +2.3.0 +----- + + * added Crawler::html() + * [BC BREAK] Crawler::each() and Crawler::reduce() now return Crawler instances instead of DomElement instances + * added schema relative URL support to links + * added support for HTML5 'form' attribute + +2.2.0 +----- + + * added a way to set raw path to the file in FileFormField - necessary for + simulating HTTP requests + +2.1.0 +----- + + * added support for the HTTP PATCH method + * refactored the Form class internals to support multi-dimensional fields + (the public API is backward compatible) + * added a way to get parsing errors for Crawler::addHtmlContent() and + Crawler::addXmlContent() via libxml functions + * added support for submitting a form without a submit button diff --git a/core/vendor/symfony/dom-crawler/Crawler.php b/core/vendor/symfony/dom-crawler/Crawler.php new file mode 100644 index 0000000..8cfd510 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Crawler.php @@ -0,0 +1,1044 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\CssSelector\CssSelector; + +/** + * Crawler eases navigation of a list of \DOMElement objects. + * + * @author Fabien Potencier + */ +class Crawler extends \SplObjectStorage +{ + protected $uri; + + /** + * @var string The default namespace prefix to be used with XPath and CSS expressions + */ + private $defaultNamespacePrefix = 'default'; + + /** + * @var array A map of manually registered namespaces + */ + private $namespaces = array(); + + /** + * @var string The base href value + */ + private $baseHref; + + /** + * @param mixed $node A Node to use as the base for the crawling + * @param string $uri The current URI + * @param string $baseHref The base href value + */ + public function __construct($node = null, $uri = null, $baseHref = null) + { + $this->uri = $uri; + $this->baseHref = $baseHref ?: $uri; + + $this->add($node); + } + + /** + * Removes all the nodes. + */ + public function clear() + { + $this->removeAll($this); + } + + /** + * Adds a node to the current list of nodes. + * + * This method uses the appropriate specialized add*() method based + * on the type of the argument. + * + * @param \DOMNodeList|\DOMNode|array|string|null $node A node + * + * @throws \InvalidArgumentException when node is not the expected type + */ + public function add($node) + { + if ($node instanceof \DOMNodeList) { + $this->addNodeList($node); + } elseif ($node instanceof \DOMNode) { + $this->addNode($node); + } elseif (is_array($node)) { + $this->addNodes($node); + } elseif (is_string($node)) { + $this->addContent($node); + } elseif (null !== $node) { + throw new \InvalidArgumentException(sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', is_object($node) ? get_class($node) : gettype($node))); + } + } + + /** + * Adds HTML/XML content. + * + * If the charset is not set via the content type, it is assumed + * to be ISO-8859-1, which is the default charset defined by the + * HTTP 1.1 specification. + * + * @param string $content A string to parse as HTML/XML + * @param null|string $type The content type of the string + */ + public function addContent($content, $type = null) + { + if (empty($type)) { + $type = 0 === strpos($content, ']+charset *= *["\']?([a-zA-Z\-0-9_:.]+)/i', $content, $matches)) { + $charset = $matches[1]; + } + + if (null === $charset) { + $charset = 'ISO-8859-1'; + } + + if ('x' === $xmlMatches[1]) { + $this->addXmlContent($content, $charset); + } else { + $this->addHtmlContent($content, $charset); + } + } + + /** + * Adds an HTML content to the list of nodes. + * + * The libxml errors are disabled when the content is parsed. + * + * If you want to get parsing errors, be sure to enable + * internal errors via libxml_use_internal_errors(true) + * and then, get the errors via libxml_get_errors(). Be + * sure to clear errors with libxml_clear_errors() afterward. + * + * @param string $content The HTML content + * @param string $charset The charset + */ + public function addHtmlContent($content, $charset = 'UTF-8') + { + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + + $dom = new \DOMDocument('1.0', $charset); + $dom->validateOnParse = true; + + set_error_handler(function () { throw new \Exception(); }); + + try { + // Convert charset to HTML-entities to work around bugs in DOMDocument::loadHTML() + + if (function_exists('mb_convert_encoding')) { + $content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset); + } elseif (function_exists('iconv')) { + $content = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { + $m = unpack('C*', $m[0]); + $i = 1; + $entities = ''; + + while (isset($m[$i])) { + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + }, + iconv($charset, 'UTF-8', $content) + ); + } + } catch (\Exception $e) { + } + + restore_error_handler(); + + if ('' !== trim($content)) { + @$dom->loadHTML($content); + } + + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + $this->addDocument($dom); + + $base = $this->filterRelativeXPath('descendant-or-self::base')->extract(array('href')); + + $baseHref = current($base); + if (count($base) && !empty($baseHref)) { + if ($this->baseHref) { + $linkNode = $dom->createElement('a'); + $linkNode->setAttribute('href', $baseHref); + $link = new Link($linkNode, $this->baseHref); + $this->baseHref = $link->getUri(); + } else { + $this->baseHref = $baseHref; + } + } + } + + /** + * Adds an XML content to the list of nodes. + * + * The libxml errors are disabled when the content is parsed. + * + * If you want to get parsing errors, be sure to enable + * internal errors via libxml_use_internal_errors(true) + * and then, get the errors via libxml_get_errors(). Be + * sure to clear errors with libxml_clear_errors() afterward. + * + * @param string $content The XML content + * @param string $charset The charset + * @param int $options Bitwise OR of the libxml option constants + * LIBXML_PARSEHUGE is dangerous, see + * http://symfony.com/blog/security-release-symfony-2-0-17-released + */ + public function addXmlContent($content, $charset = 'UTF-8', $options = LIBXML_NONET) + { + // remove the default namespace if it's the only namespace to make XPath expressions simpler + if (!preg_match('/xmlns:/', $content)) { + $content = str_replace('xmlns', 'ns', $content); + } + + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + + $dom = new \DOMDocument('1.0', $charset); + $dom->validateOnParse = true; + + if ('' !== trim($content)) { + @$dom->loadXML($content, $options); + } + + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + $this->addDocument($dom); + } + + /** + * Adds a \DOMDocument to the list of nodes. + * + * @param \DOMDocument $dom A \DOMDocument instance + */ + public function addDocument(\DOMDocument $dom) + { + if ($dom->documentElement) { + $this->addNode($dom->documentElement); + } + } + + /** + * Adds a \DOMNodeList to the list of nodes. + * + * @param \DOMNodeList $nodes A \DOMNodeList instance + */ + public function addNodeList(\DOMNodeList $nodes) + { + foreach ($nodes as $node) { + if ($node instanceof \DOMNode) { + $this->addNode($node); + } + } + } + + /** + * Adds an array of \DOMNode instances to the list of nodes. + * + * @param \DOMNode[] $nodes An array of \DOMNode instances + */ + public function addNodes(array $nodes) + { + foreach ($nodes as $node) { + $this->add($node); + } + } + + /** + * Adds a \DOMNode instance to the list of nodes. + * + * @param \DOMNode $node A \DOMNode instance + */ + public function addNode(\DOMNode $node) + { + if ($node instanceof \DOMDocument) { + $this->attach($node->documentElement); + } else { + $this->attach($node); + } + } + + // Serializing and unserializing a crawler creates DOM objects in a corrupted state. DOM elements are not properly serializable. + public function unserialize($serialized) + { + throw new \BadMethodCallException('A Crawler cannot be serialized.'); + } + + public function serialize() + { + throw new \BadMethodCallException('A Crawler cannot be serialized.'); + } + + /** + * Returns a node given its position in the node list. + * + * @param int $position The position + * + * @return self + */ + public function eq($position) + { + foreach ($this as $i => $node) { + if ($i == $position) { + return $this->createSubCrawler($node); + } + } + + return $this->createSubCrawler(null); + } + + /** + * Calls an anonymous function on each node of the list. + * + * The anonymous function receives the position and the node wrapped + * in a Crawler instance as arguments. + * + * Example: + * + * $crawler->filter('h1')->each(function ($node, $i) { + * return $node->text(); + * }); + * + * @param \Closure $closure An anonymous function + * + * @return array An array of values returned by the anonymous function + */ + public function each(\Closure $closure) + { + $data = array(); + foreach ($this as $i => $node) { + $data[] = $closure($this->createSubCrawler($node), $i); + } + + return $data; + } + + /** + * Slices the list of nodes by $offset and $length. + * + * @param int $offset + * @param int $length + * + * @return self + */ + public function slice($offset = 0, $length = -1) + { + return $this->createSubCrawler(iterator_to_array(new \LimitIterator($this, $offset, $length))); + } + + /** + * Reduces the list of nodes by calling an anonymous function. + * + * To remove a node from the list, the anonymous function must return false. + * + * @param \Closure $closure An anonymous function + * + * @return self + */ + public function reduce(\Closure $closure) + { + $nodes = array(); + foreach ($this as $i => $node) { + if (false !== $closure($this->createSubCrawler($node), $i)) { + $nodes[] = $node; + } + } + + return $this->createSubCrawler($nodes); + } + + /** + * Returns the first node of the current selection. + * + * @return self + */ + public function first() + { + return $this->eq(0); + } + + /** + * Returns the last node of the current selection. + * + * @return self + */ + public function last() + { + return $this->eq(count($this) - 1); + } + + /** + * Returns the siblings nodes of the current selection. + * + * @return self + * + * @throws \InvalidArgumentException When current node is empty + */ + public function siblings() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->createSubCrawler($this->sibling($this->getNode(0)->parentNode->firstChild)); + } + + /** + * Returns the next siblings nodes of the current selection. + * + * @return self + * + * @throws \InvalidArgumentException When current node is empty + */ + public function nextAll() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->createSubCrawler($this->sibling($this->getNode(0))); + } + + /** + * Returns the previous sibling nodes of the current selection. + * + * @return self + * + * @throws \InvalidArgumentException + */ + public function previousAll() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->createSubCrawler($this->sibling($this->getNode(0), 'previousSibling')); + } + + /** + * Returns the parents nodes of the current selection. + * + * @return self + * + * @throws \InvalidArgumentException When current node is empty + */ + public function parents() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + $nodes = array(); + + while ($node = $node->parentNode) { + if (XML_ELEMENT_NODE === $node->nodeType) { + $nodes[] = $node; + } + } + + return $this->createSubCrawler($nodes); + } + + /** + * Returns the children nodes of the current selection. + * + * @return self + * + * @throws \InvalidArgumentException When current node is empty + */ + public function children() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0)->firstChild; + + return $this->createSubCrawler($node ? $this->sibling($node) : array()); + } + + /** + * Returns the attribute value of the first node of the list. + * + * @param string $attribute The attribute name + * + * @return string|null The attribute value or null if the attribute does not exist + * + * @throws \InvalidArgumentException When current node is empty + */ + public function attr($attribute) + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + + return $node->hasAttribute($attribute) ? $node->getAttribute($attribute) : null; + } + + /** + * Returns the node name of the first node of the list. + * + * @return string The node name + * + * @throws \InvalidArgumentException When current node is empty + */ + public function nodeName() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->getNode(0)->nodeName; + } + + /** + * Returns the node value of the first node of the list. + * + * @return string The node value + * + * @throws \InvalidArgumentException When current node is empty + */ + public function text() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->getNode(0)->nodeValue; + } + + /** + * Returns the first node of the list as HTML. + * + * @return string The node html + * + * @throws \InvalidArgumentException When current node is empty + */ + public function html() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $html = ''; + foreach ($this->getNode(0)->childNodes as $child) { + $html .= $child->ownerDocument->saveHTML($child); + } + + return $html; + } + + /** + * Extracts information from the list of nodes. + * + * You can extract attributes or/and the node value (_text). + * + * Example: + * + * $crawler->filter('h1 a')->extract(array('_text', 'href')); + * + * @param array $attributes An array of attributes + * + * @return array An array of extracted values + */ + public function extract($attributes) + { + $attributes = (array) $attributes; + $count = count($attributes); + + $data = array(); + foreach ($this as $node) { + $elements = array(); + foreach ($attributes as $attribute) { + if ('_text' === $attribute) { + $elements[] = $node->nodeValue; + } else { + $elements[] = $node->getAttribute($attribute); + } + } + + $data[] = 1 === $count ? $elements[0] : $elements; + } + + return $data; + } + + /** + * Filters the list of nodes with an XPath expression. + * + * The XPath expression is evaluated in the context of the crawler, which + * is considered as a fake parent of the elements inside it. + * This means that a child selector "div" or "./div" will match only + * the div elements of the current crawler, not their children. + * + * @param string $xpath An XPath expression + * + * @return self + */ + public function filterXPath($xpath) + { + $xpath = $this->relativize($xpath); + + // If we dropped all expressions in the XPath while preparing it, there would be no match + if ('' === $xpath) { + return $this->createSubCrawler(null); + } + + return $this->filterRelativeXPath($xpath); + } + + /** + * Filters the list of nodes with a CSS selector. + * + * This method only works if you have installed the CssSelector Symfony Component. + * + * @param string $selector A CSS selector + * + * @return self + * + * @throws \RuntimeException if the CssSelector Component is not available + */ + public function filter($selector) + { + if (!class_exists('Symfony\\Component\\CssSelector\\CssSelector')) { + throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector is not installed (you can use filterXPath instead).'); + } + + // The CssSelector already prefixes the selector with descendant-or-self:: + return $this->filterRelativeXPath(CssSelector::toXPath($selector)); + } + + /** + * Selects links by name or alt value for clickable images. + * + * @param string $value The link text + * + * @return self + */ + public function selectLink($value) + { + $xpath = sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) ', static::xpathLiteral(' '.$value.' ')). + sprintf('or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)]]', static::xpathLiteral(' '.$value.' ')); + + return $this->filterRelativeXPath($xpath); + } + + /** + * Selects a button by name or alt value for images. + * + * @param string $value The button text + * + * @return self + */ + public function selectButton($value) + { + $translate = 'translate(@type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")'; + $xpath = sprintf('descendant-or-self::input[((contains(%s, "submit") or contains(%1$s, "button")) and contains(concat(\' \', normalize-space(string(@value)), \' \'), %s)) ', $translate, static::xpathLiteral(' '.$value.' ')). + sprintf('or (contains(%s, "image") and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)) or @id=%s or @name=%s] ', $translate, static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value), static::xpathLiteral($value)). + sprintf('| descendant-or-self::button[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) or @id=%s or @name=%s]', static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value), static::xpathLiteral($value)); + + return $this->filterRelativeXPath($xpath); + } + + /** + * Returns a Link object for the first node in the list. + * + * @param string $method The method for the link (get by default) + * + * @return Link A Link instance + * + * @throws \InvalidArgumentException If the current node list is empty + */ + public function link($method = 'get') + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + + return new Link($node, $this->baseHref, $method); + } + + /** + * Returns an array of Link objects for the nodes in the list. + * + * @return Link[] An array of Link instances + */ + public function links() + { + $links = array(); + foreach ($this as $node) { + $links[] = new Link($node, $this->baseHref, 'get'); + } + + return $links; + } + + /** + * Returns a Form object for the first node in the list. + * + * @param array $values An array of values for the form fields + * @param string $method The method for the form + * + * @return Form A Form instance + * + * @throws \InvalidArgumentException If the current node list is empty + */ + public function form(array $values = null, $method = null) + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $form = new Form($this->getNode(0), $this->uri, $method, $this->baseHref); + + if (null !== $values) { + $form->setValues($values); + } + + return $form; + } + + /** + * Overloads a default namespace prefix to be used with XPath and CSS expressions. + * + * @param string $prefix + */ + public function setDefaultNamespacePrefix($prefix) + { + $this->defaultNamespacePrefix = $prefix; + } + + /** + * @param string $prefix + * @param string $namespace + */ + public function registerNamespace($prefix, $namespace) + { + $this->namespaces[$prefix] = $namespace; + } + + /** + * Converts string for XPath expressions. + * + * Escaped characters are: quotes (") and apostrophe ('). + * + * Examples: + * + * echo Crawler::xpathLiteral('foo " bar'); + * //prints 'foo " bar' + * + * echo Crawler::xpathLiteral("foo ' bar"); + * //prints "foo ' bar" + * + * echo Crawler::xpathLiteral('a\'b"c'); + * //prints concat('a', "'", 'b"c') + * + * + * @param string $s String to be escaped + * + * @return string Converted string + */ + public static function xpathLiteral($s) + { + if (false === strpos($s, "'")) { + return sprintf("'%s'", $s); + } + + if (false === strpos($s, '"')) { + return sprintf('"%s"', $s); + } + + $string = $s; + $parts = array(); + while (true) { + if (false !== $pos = strpos($string, "'")) { + $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = "\"'\""; + $string = substr($string, $pos + 1); + } else { + $parts[] = "'$string'"; + break; + } + } + + return sprintf('concat(%s)', implode(', ', $parts)); + } + + /** + * Filters the list of nodes with an XPath expression. + * + * The XPath expression should already be processed to apply it in the context of each node. + * + * @param string $xpath + * + * @return self + */ + private function filterRelativeXPath($xpath) + { + $prefixes = $this->findNamespacePrefixes($xpath); + + $crawler = $this->createSubCrawler(null); + + foreach ($this as $node) { + $domxpath = $this->createDOMXPath($node->ownerDocument, $prefixes); + $crawler->add($domxpath->query($xpath, $node)); + } + + return $crawler; + } + + /** + * Make the XPath relative to the current context. + * + * The returned XPath will match elements matching the XPath inside the current crawler + * when running in the context of a node of the crawler. + * + * @param string $xpath + * + * @return string + */ + private function relativize($xpath) + { + $expressions = array(); + + // An expression which will never match to replace expressions which cannot match in the crawler + // We cannot simply drop + $nonMatchingExpression = 'a[name() = "b"]'; + + $xpathLen = strlen($xpath); + $openedBrackets = 0; + $startPosition = strspn($xpath, " \t\n\r\0\x0B"); + + for ($i = $startPosition; $i <= $xpathLen; ++$i) { + $i += strcspn($xpath, '"\'[]|', $i); + + if ($i < $xpathLen) { + switch ($xpath[$i]) { + case '"': + case "'": + if (false === $i = strpos($xpath, $xpath[$i], $i + 1)) { + return $xpath; // The XPath expression is invalid + } + continue 2; + case '[': + ++$openedBrackets; + continue 2; + case ']': + --$openedBrackets; + continue 2; + } + } + if ($openedBrackets) { + continue; + } + + if ($startPosition < $xpathLen && '(' === $xpath[$startPosition]) { + // If the union is inside some braces, we need to preserve the opening braces and apply + // the change only inside it. + $j = 1 + strspn($xpath, "( \t\n\r\0\x0B", $startPosition + 1); + $parenthesis = substr($xpath, $startPosition, $j); + $startPosition += $j; + } else { + $parenthesis = ''; + } + $expression = rtrim(substr($xpath, $startPosition, $i - $startPosition)); + + // BC for Symfony 2.4 and lower were elements were adding in a fake _root parent + if (0 === strpos($expression, '/_root/')) { + $expression = './'.substr($expression, 7); + } elseif (0 === strpos($expression, 'self::*/')) { + $expression = './'.substr($expression, 8); + } + + // add prefix before absolute element selector + if ('' === $expression) { + $expression = $nonMatchingExpression; + } elseif (0 === strpos($expression, '//')) { + $expression = 'descendant-or-self::'.substr($expression, 2); + } elseif (0 === strpos($expression, './/')) { + $expression = 'descendant-or-self::'.substr($expression, 3); + } elseif (0 === strpos($expression, './')) { + $expression = 'self::'.substr($expression, 2); + } elseif (0 === strpos($expression, 'child::')) { + $expression = 'self::'.substr($expression, 7); + } elseif ('/' === $expression[0] || 0 === strpos($expression, 'self::')) { + // the only direct child in Symfony 2.4 and lower is _root, which is already handled previously + // so let's drop the expression entirely + $expression = $nonMatchingExpression; + } elseif ('.' === $expression[0]) { + // '.' is the fake root element in Symfony 2.4 and lower, which is excluded from results + $expression = $nonMatchingExpression; + } elseif (0 === strpos($expression, 'descendant::')) { + $expression = 'descendant-or-self::'.substr($expression, 12); + } elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) { + // the fake root has no parent, preceding or following nodes and also no attributes (even no namespace attributes) + $expression = $nonMatchingExpression; + } elseif (0 !== strpos($expression, 'descendant-or-self::')) { + $expression = 'self::'.$expression; + } + $expressions[] = $parenthesis.$expression; + + if ($i === $xpathLen) { + return implode(' | ', $expressions); + } + + $i += strspn($xpath, " \t\n\r\0\x0B", $i + 1); + $startPosition = $i + 1; + } + + return $xpath; // The XPath expression is invalid + } + + /** + * @param int $position + * + * @return \DOMElement|null + */ + public function getNode($position) + { + foreach ($this as $i => $node) { + if ($i == $position) { + return $node; + } + } + } + + /** + * @param \DOMElement $node + * @param string $siblingDir + * + * @return array + */ + protected function sibling($node, $siblingDir = 'nextSibling') + { + $nodes = array(); + + do { + if ($node !== $this->getNode(0) && 1 === $node->nodeType) { + $nodes[] = $node; + } + } while ($node = $node->$siblingDir); + + return $nodes; + } + + /** + * @param \DOMDocument $document + * @param array $prefixes + * + * @return \DOMXPath + * + * @throws \InvalidArgumentException + */ + private function createDOMXPath(\DOMDocument $document, array $prefixes = array()) + { + $domxpath = new \DOMXPath($document); + + foreach ($prefixes as $prefix) { + $namespace = $this->discoverNamespace($domxpath, $prefix); + if (null !== $namespace) { + $domxpath->registerNamespace($prefix, $namespace); + } + } + + return $domxpath; + } + + /** + * @param \DOMXPath $domxpath + * @param string $prefix + * + * @return string + * + * @throws \InvalidArgumentException + */ + private function discoverNamespace(\DOMXPath $domxpath, $prefix) + { + if (isset($this->namespaces[$prefix])) { + return $this->namespaces[$prefix]; + } + + // ask for one namespace, otherwise we'd get a collection with an item for each node + $namespaces = $domxpath->query(sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); + + if ($node = $namespaces->item(0)) { + return $node->nodeValue; + } + } + + /** + * @param string $xpath + * + * @return array + */ + private function findNamespacePrefixes($xpath) + { + if (preg_match_all('/(?P[a-z_][a-z_0-9\-\.]*+):[^"\/:]/i', $xpath, $matches)) { + return array_unique($matches['prefix']); + } + + return array(); + } + + /** + * Creates a crawler for some subnodes. + * + * @param \DOMElement|\DOMElement[]|\DOMNodeList|null $nodes + * + * @return static + */ + private function createSubCrawler($nodes) + { + return new static($nodes, $this->uri, $this->baseHref); + } +} diff --git a/core/vendor/symfony/dom-crawler/Field/ChoiceFormField.php b/core/vendor/symfony/dom-crawler/Field/ChoiceFormField.php new file mode 100644 index 0000000..a3539bc --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Field/ChoiceFormField.php @@ -0,0 +1,324 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * ChoiceFormField represents a choice form field. + * + * It is constructed from a HTML select tag, or a HTML checkbox, or radio inputs. + * + * @author Fabien Potencier + */ +class ChoiceFormField extends FormField +{ + /** + * @var string + */ + private $type; + /** + * @var bool + */ + private $multiple; + /** + * @var array + */ + private $options; + /** + * @var bool + */ + private $validationDisabled = false; + + /** + * Returns true if the field should be included in the submitted values. + * + * @return bool true if the field should be included in the submitted values, false otherwise + */ + public function hasValue() + { + // don't send a value for unchecked checkboxes + if (in_array($this->type, array('checkbox', 'radio')) && null === $this->value) { + return false; + } + + return true; + } + + /** + * Check if the current selected option is disabled. + * + * @return bool + */ + public function isDisabled() + { + if (parent::isDisabled() && 'select' === $this->type) { + return true; + } + + foreach ($this->options as $option) { + if ($option['value'] == $this->value && $option['disabled']) { + return true; + } + } + + return false; + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + */ + public function select($value) + { + $this->setValue($value); + } + + /** + * Ticks a checkbox. + * + * @throws \LogicException When the type provided is not correct + */ + public function tick() + { + if ('checkbox' !== $this->type) { + throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); + } + + $this->setValue(true); + } + + /** + * Unticks a checkbox. + * + * @throws \LogicException When the type provided is not correct + */ + public function untick() + { + if ('checkbox' !== $this->type) { + throw new \LogicException(sprintf('You cannot untick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); + } + + $this->setValue(false); + } + + /** + * Sets the value of the field. + * + * @param string|array $value The value of the field + * + * @throws \InvalidArgumentException When value type provided is not correct + */ + public function setValue($value) + { + if ('checkbox' === $this->type && false === $value) { + // uncheck + $this->value = null; + } elseif ('checkbox' === $this->type && true === $value) { + // check + $this->value = $this->options[0]['value']; + } else { + if (is_array($value)) { + if (!$this->multiple) { + throw new \InvalidArgumentException(sprintf('The value for "%s" cannot be an array.', $this->name)); + } + + foreach ($value as $v) { + if (!$this->containsOption($v, $this->options)) { + throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $v, implode(', ', $this->availableOptionValues()))); + } + } + } elseif (!$this->containsOption($value, $this->options)) { + throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $value, implode(', ', $this->availableOptionValues()))); + } + + if ($this->multiple) { + $value = (array) $value; + } + + if (is_array($value)) { + $this->value = $value; + } else { + parent::setValue($value); + } + } + } + + /** + * Adds a choice to the current ones. + * + * @param \DOMElement $node + * + * @throws \LogicException When choice provided is not multiple nor radio + * + * @internal + */ + public function addChoice(\DOMElement $node) + { + if (!$this->multiple && 'radio' !== $this->type) { + throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); + } + + $option = $this->buildOptionValue($node); + $this->options[] = $option; + + if ($node->hasAttribute('checked')) { + $this->value = $option['value']; + } + } + + /** + * Returns the type of the choice field (radio, select, or checkbox). + * + * @return string The type + */ + public function getType() + { + return $this->type; + } + + /** + * Returns true if the field accepts multiple values. + * + * @return bool true if the field accepts multiple values, false otherwise + */ + public function isMultiple() + { + return $this->multiple; + } + + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) { + throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName)); + } + + if ('input' === $this->node->nodeName && 'checkbox' !== strtolower($this->node->getAttribute('type')) && 'radio' !== strtolower($this->node->getAttribute('type'))) { + throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is %s).', $this->node->getAttribute('type'))); + } + + $this->value = null; + $this->options = array(); + $this->multiple = false; + + if ('input' == $this->node->nodeName) { + $this->type = strtolower($this->node->getAttribute('type')); + $optionValue = $this->buildOptionValue($this->node); + $this->options[] = $optionValue; + + if ($this->node->hasAttribute('checked')) { + $this->value = $optionValue['value']; + } + } else { + $this->type = 'select'; + if ($this->node->hasAttribute('multiple')) { + $this->multiple = true; + $this->value = array(); + $this->name = str_replace('[]', '', $this->name); + } + + $found = false; + foreach ($this->xpath->query('descendant::option', $this->node) as $option) { + $optionValue = $this->buildOptionValue($option); + $this->options[] = $optionValue; + + if ($option->hasAttribute('selected')) { + $found = true; + if ($this->multiple) { + $this->value[] = $optionValue['value']; + } else { + $this->value = $optionValue['value']; + } + } + } + + // if no option is selected and if it is a simple select box, take the first option as the value + if (!$found && !$this->multiple && !empty($this->options)) { + $this->value = $this->options[0]['value']; + } + } + } + + /** + * Returns option value with associated disabled flag. + * + * @param \DOMElement $node + * + * @return array + */ + private function buildOptionValue(\DOMElement $node) + { + $option = array(); + + $defaultDefaultValue = 'select' === $this->node->nodeName ? '' : 'on'; + $defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : $defaultDefaultValue; + $option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue; + $option['disabled'] = $node->hasAttribute('disabled'); + + return $option; + } + + /** + * Checks whether given value is in the existing options. + * + * @param string $optionValue + * @param array $options + * + * @return bool + */ + public function containsOption($optionValue, $options) + { + if ($this->validationDisabled) { + return true; + } + + foreach ($options as $option) { + if ($option['value'] == $optionValue) { + return true; + } + } + + return false; + } + + /** + * Returns list of available field options. + * + * @return array + */ + public function availableOptionValues() + { + $values = array(); + + foreach ($this->options as $option) { + $values[] = $option['value']; + } + + return $values; + } + + /** + * Disables the internal validation of the field. + * + * @return self + */ + public function disableValidation() + { + $this->validationDisabled = true; + + return $this; + } +} diff --git a/core/vendor/symfony/dom-crawler/Field/FileFormField.php b/core/vendor/symfony/dom-crawler/Field/FileFormField.php new file mode 100644 index 0000000..0e0f943 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Field/FileFormField.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * FileFormField represents a file form field (an HTML file input tag). + * + * @author Fabien Potencier + */ +class FileFormField extends FormField +{ + /** + * Sets the PHP error code associated with the field. + * + * @param int $error The error code (one of UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, or UPLOAD_ERR_EXTENSION) + * + * @throws \InvalidArgumentException When error code doesn't exist + */ + public function setErrorCode($error) + { + $codes = array(UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION); + if (!in_array($error, $codes)) { + throw new \InvalidArgumentException(sprintf('The error code %s is not valid.', $error)); + } + + $this->value = array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0); + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + */ + public function upload($value) + { + $this->setValue($value); + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + */ + public function setValue($value) + { + if (null !== $value && is_readable($value)) { + $error = UPLOAD_ERR_OK; + $size = filesize($value); + $info = pathinfo($value); + $name = $info['basename']; + + // copy to a tmp location + $tmp = sys_get_temp_dir().'/'.sha1(uniqid(mt_rand(), true)); + if (array_key_exists('extension', $info)) { + $tmp .= '.'.$info['extension']; + } + if (is_file($tmp)) { + unlink($tmp); + } + copy($value, $tmp); + $value = $tmp; + } else { + $error = UPLOAD_ERR_NO_FILE; + $size = 0; + $name = ''; + $value = ''; + } + + $this->value = array('name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size); + } + + /** + * Sets path to the file as string for simulating HTTP request. + * + * @param string $path The path to the file + */ + public function setFilePath($path) + { + parent::setValue($path); + } + + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' !== $this->node->nodeName) { + throw new \LogicException(sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); + } + + if ('file' !== strtolower($this->node->getAttribute('type'))) { + throw new \LogicException(sprintf('A FileFormField can only be created from an input tag with a type of file (given type is %s).', $this->node->getAttribute('type'))); + } + + $this->setValue(null); + } +} diff --git a/core/vendor/symfony/dom-crawler/Field/FormField.php b/core/vendor/symfony/dom-crawler/Field/FormField.php new file mode 100644 index 0000000..496d45d --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Field/FormField.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * FormField is the abstract class for all form fields. + * + * @author Fabien Potencier + */ +abstract class FormField +{ + /** + * @var \DOMElement + */ + protected $node; + /** + * @var string + */ + protected $name; + /** + * @var string + */ + protected $value; + /** + * @var \DOMDocument + */ + protected $document; + /** + * @var \DOMXPath + */ + protected $xpath; + /** + * @var bool + */ + protected $disabled; + + /** + * @param \DOMElement $node The node associated with this field + */ + public function __construct(\DOMElement $node) + { + $this->node = $node; + $this->name = $node->getAttribute('name'); + $this->xpath = new \DOMXPath($node->ownerDocument); + + $this->initialize(); + } + + /** + * Returns the name of the field. + * + * @return string The name of the field + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the field. + * + * @return string|array The value of the field + */ + public function getValue() + { + return $this->value; + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + */ + public function setValue($value) + { + $this->value = (string) $value; + } + + /** + * Returns true if the field should be included in the submitted values. + * + * @return bool true if the field should be included in the submitted values, false otherwise + */ + public function hasValue() + { + return true; + } + + /** + * Check if the current field is disabled. + * + * @return bool + */ + public function isDisabled() + { + return $this->node->hasAttribute('disabled'); + } + + /** + * Initializes the form field. + */ + abstract protected function initialize(); +} diff --git a/core/vendor/symfony/dom-crawler/Field/InputFormField.php b/core/vendor/symfony/dom-crawler/Field/InputFormField.php new file mode 100644 index 0000000..1c3c84d --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Field/InputFormField.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * InputFormField represents an input form field (an HTML input tag). + * + * For inputs with type of file, checkbox, or radio, there are other more + * specialized classes (cf. FileFormField and ChoiceFormField). + * + * @author Fabien Potencier + */ +class InputFormField extends FormField +{ + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' !== $this->node->nodeName && 'button' !== $this->node->nodeName) { + throw new \LogicException(sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); + } + + $type = strtolower($this->node->getAttribute('type')); + if ('checkbox' === $type) { + throw new \LogicException('Checkboxes should be instances of ChoiceFormField.'); + } + + if ('file' === $type) { + throw new \LogicException('File inputs should be instances of FileFormField.'); + } + + $this->value = $this->node->getAttribute('value'); + } +} diff --git a/core/vendor/symfony/dom-crawler/Field/TextareaFormField.php b/core/vendor/symfony/dom-crawler/Field/TextareaFormField.php new file mode 100644 index 0000000..15526e1 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Field/TextareaFormField.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * TextareaFormField represents a textarea form field (an HTML textarea tag). + * + * @author Fabien Potencier + */ +class TextareaFormField extends FormField +{ + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('textarea' !== $this->node->nodeName) { + throw new \LogicException(sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); + } + + $this->value = ''; + foreach ($this->node->childNodes as $node) { + $this->value .= $node->wholeText; + } + } +} diff --git a/core/vendor/symfony/dom-crawler/Form.php b/core/vendor/symfony/dom-crawler/Form.php new file mode 100644 index 0000000..258be96 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Form.php @@ -0,0 +1,479 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\DomCrawler\Field\ChoiceFormField; +use Symfony\Component\DomCrawler\Field\FormField; + +/** + * Form represents an HTML form. + * + * @author Fabien Potencier + */ +class Form extends Link implements \ArrayAccess +{ + /** + * @var \DOMElement + */ + private $button; + + /** + * @var FormFieldRegistry + */ + private $fields; + + /** + * @var string + */ + private $baseHref; + + /** + * @param \DOMElement $node A \DOMElement instance + * @param string $currentUri The URI of the page where the form is embedded + * @param string $method The method to use for the link (if null, it defaults to the method defined by the form) + * @param string $baseHref The URI of the used for relative links, but not for empty action + * + * @throws \LogicException if the node is not a button inside a form tag + */ + public function __construct(\DOMElement $node, $currentUri, $method = null, $baseHref = null) + { + parent::__construct($node, $currentUri, $method); + $this->baseHref = $baseHref; + + $this->initialize(); + } + + /** + * Gets the form node associated with this form. + * + * @return \DOMElement A \DOMElement instance + */ + public function getFormNode() + { + return $this->node; + } + + /** + * Sets the value of the fields. + * + * @param array $values An array of field values + * + * @return $this + */ + public function setValues(array $values) + { + foreach ($values as $name => $value) { + $this->fields->set($name, $value); + } + + return $this; + } + + /** + * Gets the field values. + * + * The returned array does not include file fields (@see getFiles). + * + * @return array An array of field values + */ + public function getValues() + { + $values = array(); + foreach ($this->fields->all() as $name => $field) { + if ($field->isDisabled()) { + continue; + } + + if (!$field instanceof Field\FileFormField && $field->hasValue()) { + $values[$name] = $field->getValue(); + } + } + + return $values; + } + + /** + * Gets the file field values. + * + * @return array An array of file field values + */ + public function getFiles() + { + if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { + return array(); + } + + $files = array(); + + foreach ($this->fields->all() as $name => $field) { + if ($field->isDisabled()) { + continue; + } + + if ($field instanceof Field\FileFormField) { + $files[$name] = $field->getValue(); + } + } + + return $files; + } + + /** + * Gets the field values as PHP. + * + * This method converts fields with the array notation + * (like foo[bar] to arrays) like PHP does. + * + * @return array An array of field values + */ + public function getPhpValues() + { + $values = array(); + foreach ($this->getValues() as $name => $value) { + $qs = http_build_query(array($name => $value), '', '&'); + if (!empty($qs)) { + parse_str($qs, $expandedValue); + $varName = substr($name, 0, strlen(key($expandedValue))); + $values = array_replace_recursive($values, array($varName => current($expandedValue))); + } + } + + return $values; + } + + /** + * Gets the file field values as PHP. + * + * This method converts fields with the array notation + * (like foo[bar] to arrays) like PHP does. + * The returned array is consistent with the array for field values + * (@see getPhpValues), rather than uploaded files found in $_FILES. + * For a compound file field foo[bar] it will create foo[bar][name], + * instead of foo[name][bar] which would be found in $_FILES. + * + * @return array An array of file field values + */ + public function getPhpFiles() + { + $values = array(); + foreach ($this->getFiles() as $name => $value) { + $qs = http_build_query(array($name => $value), '', '&'); + if (!empty($qs)) { + parse_str($qs, $expandedValue); + $varName = substr($name, 0, strlen(key($expandedValue))); + + array_walk_recursive( + $expandedValue, + function (&$value, $key) { + if (ctype_digit($value) && ('size' === $key || 'error' === $key)) { + $value = (int) $value; + } + } + ); + + reset($expandedValue); + + $values = array_replace_recursive($values, array($varName => current($expandedValue))); + } + } + + return $values; + } + + /** + * Gets the URI of the form. + * + * The returned URI is not the same as the form "action" attribute. + * This method merges the value if the method is GET to mimics + * browser behavior. + * + * @return string The URI + */ + public function getUri() + { + $uri = parent::getUri(); + + if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { + $query = parse_url($uri, PHP_URL_QUERY); + $currentParameters = array(); + if ($query) { + parse_str($query, $currentParameters); + } + + $queryString = http_build_query(array_merge($currentParameters, $this->getValues()), '', '&'); + + $pos = strpos($uri, '?'); + $base = false === $pos ? $uri : substr($uri, 0, $pos); + $uri = rtrim($base.'?'.$queryString, '?'); + } + + return $uri; + } + + protected function getRawUri() + { + return $this->node->getAttribute('action'); + } + + /** + * Gets the form method. + * + * If no method is defined in the form, GET is returned. + * + * @return string The method + */ + public function getMethod() + { + if (null !== $this->method) { + return $this->method; + } + + return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET'; + } + + /** + * Returns true if the named field exists. + * + * @param string $name The field name + * + * @return bool true if the field exists, false otherwise + */ + public function has($name) + { + return $this->fields->has($name); + } + + /** + * Removes a field from the form. + * + * @param string $name The field name + */ + public function remove($name) + { + $this->fields->remove($name); + } + + /** + * Gets a named field. + * + * @param string $name The field name + * + * @return FormField The field instance + * + * @throws \InvalidArgumentException When field is not present in this form + */ + public function get($name) + { + return $this->fields->get($name); + } + + /** + * Sets a named field. + */ + public function set(FormField $field) + { + $this->fields->add($field); + } + + /** + * Gets all fields. + * + * @return FormField[] + */ + public function all() + { + return $this->fields->all(); + } + + /** + * Returns true if the named field exists. + * + * @param string $name The field name + * + * @return bool true if the field exists, false otherwise + */ + public function offsetExists($name) + { + return $this->has($name); + } + + /** + * Gets the value of a field. + * + * @param string $name The field name + * + * @return FormField The associated Field instance + * + * @throws \InvalidArgumentException if the field does not exist + */ + public function offsetGet($name) + { + return $this->fields->get($name); + } + + /** + * Sets the value of a field. + * + * @param string $name The field name + * @param string|array $value The value of the field + * + * @throws \InvalidArgumentException if the field does not exist + */ + public function offsetSet($name, $value) + { + $this->fields->set($name, $value); + } + + /** + * Removes a field from the form. + * + * @param string $name The field name + */ + public function offsetUnset($name) + { + $this->fields->remove($name); + } + + /** + * Disables validation. + * + * @return self + */ + public function disableValidation() + { + foreach ($this->fields->all() as $field) { + if ($field instanceof Field\ChoiceFormField) { + $field->disableValidation(); + } + } + + return $this; + } + + /** + * Sets the node for the form. + * + * Expects a 'submit' button \DOMElement and finds the corresponding form element, or the form element itself. + * + * @throws \LogicException If given node is not a button or input or does not have a form ancestor + */ + protected function setNode(\DOMElement $node) + { + $this->button = $node; + if ('button' === $node->nodeName || ('input' === $node->nodeName && in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image')))) { + if ($node->hasAttribute('form')) { + // if the node has the HTML5-compliant 'form' attribute, use it + $formId = $node->getAttribute('form'); + $form = $node->ownerDocument->getElementById($formId); + if (null === $form) { + throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); + } + $this->node = $form; + + return; + } + // we loop until we find a form ancestor + do { + if (null === $node = $node->parentNode) { + throw new \LogicException('The selected node does not have a form ancestor.'); + } + } while ('form' !== $node->nodeName); + } elseif ('form' !== $node->nodeName) { + throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); + } + + $this->node = $node; + } + + /** + * Adds form elements related to this form. + * + * Creates an internal copy of the submitted 'button' element and + * the form node or the entire document depending on whether we need + * to find non-descendant elements through HTML5 'form' attribute. + */ + private function initialize() + { + $this->fields = new FormFieldRegistry(); + + $xpath = new \DOMXPath($this->node->ownerDocument); + + // add submitted button if it has a valid name + if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) { + if ('input' == $this->button->nodeName && 'image' == strtolower($this->button->getAttribute('type'))) { + $name = $this->button->getAttribute('name'); + $this->button->setAttribute('value', '0'); + + // temporarily change the name of the input node for the x coordinate + $this->button->setAttribute('name', $name.'.x'); + $this->set(new Field\InputFormField($this->button)); + + // temporarily change the name of the input node for the y coordinate + $this->button->setAttribute('name', $name.'.y'); + $this->set(new Field\InputFormField($this->button)); + + // restore the original name of the input node + $this->button->setAttribute('name', $name); + } else { + $this->set(new Field\InputFormField($this->button)); + } + } + + // find form elements corresponding to the current form + if ($this->node->hasAttribute('id')) { + // corresponding elements are either descendants or have a matching HTML5 form attribute + $formId = Crawler::xpathLiteral($this->node->getAttribute('id')); + + $fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)]', $formId)); + foreach ($fieldNodes as $node) { + $this->addField($node); + } + } else { + // do the xpath query with $this->node as the context node, to only find descendant elements + // however, descendant elements with form attribute are not part of this form + $fieldNodes = $xpath->query('descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)]', $this->node); + foreach ($fieldNodes as $node) { + $this->addField($node); + } + } + + if ($this->baseHref && '' !== $this->node->getAttribute('action')) { + $this->currentUri = $this->baseHref; + } + } + + private function addField(\DOMElement $node) + { + if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { + return; + } + + $nodeName = $node->nodeName; + if ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == strtolower($node->getAttribute('type'))) { + $this->set(new Field\ChoiceFormField($node)); + } elseif ('input' == $nodeName && 'radio' == strtolower($node->getAttribute('type'))) { + // there may be other fields with the same name that are no choice + // fields already registered (see https://github.com/symfony/symfony/issues/11689) + if ($this->has($node->getAttribute('name')) && $this->get($node->getAttribute('name')) instanceof ChoiceFormField) { + $this->get($node->getAttribute('name'))->addChoice($node); + } else { + $this->set(new Field\ChoiceFormField($node)); + } + } elseif ('input' == $nodeName && 'file' == strtolower($node->getAttribute('type'))) { + $this->set(new Field\FileFormField($node)); + } elseif ('input' == $nodeName && !in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image'))) { + $this->set(new Field\InputFormField($node)); + } elseif ('textarea' == $nodeName) { + $this->set(new Field\TextareaFormField($node)); + } + } +} diff --git a/core/vendor/symfony/dom-crawler/FormFieldRegistry.php b/core/vendor/symfony/dom-crawler/FormFieldRegistry.php new file mode 100644 index 0000000..6ad6d93 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/FormFieldRegistry.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\DomCrawler\Field\FormField; + +/** + * This is an internal class that must not be used directly. + * + * @internal + */ +class FormFieldRegistry +{ + private $fields = array(); + + private $base; + + /** + * Adds a field to the registry. + */ + public function add(FormField $field) + { + $segments = $this->getSegments($field->getName()); + + $target = &$this->fields; + while ($segments) { + if (!is_array($target)) { + $target = array(); + } + $path = array_shift($segments); + if ('' === $path) { + $target = &$target[]; + } else { + $target = &$target[$path]; + } + } + $target = $field; + } + + /** + * Removes a field and its children from the registry. + * + * @param string $name The fully qualified name of the base field + */ + public function remove($name) + { + $segments = $this->getSegments($name); + $target = &$this->fields; + while (count($segments) > 1) { + $path = array_shift($segments); + if (!array_key_exists($path, $target)) { + return; + } + $target = &$target[$path]; + } + unset($target[array_shift($segments)]); + } + + /** + * Returns the value of the field and its children. + * + * @param string $name The fully qualified name of the field + * + * @return mixed The value of the field + * + * @throws \InvalidArgumentException if the field does not exist + */ + public function &get($name) + { + $segments = $this->getSegments($name); + $target = &$this->fields; + while ($segments) { + $path = array_shift($segments); + if (!array_key_exists($path, $target)) { + throw new \InvalidArgumentException(sprintf('Unreachable field "%s"', $path)); + } + $target = &$target[$path]; + } + + return $target; + } + + /** + * Tests whether the form has the given field. + * + * @param string $name The fully qualified name of the field + * + * @return bool Whether the form has the given field + */ + public function has($name) + { + try { + $this->get($name); + + return true; + } catch (\InvalidArgumentException $e) { + return false; + } + } + + /** + * Set the value of a field and its children. + * + * @param string $name The fully qualified name of the field + * @param mixed $value The value + * + * @throws \InvalidArgumentException if the field does not exist + */ + public function set($name, $value) + { + $target = &$this->get($name); + if ((!is_array($value) && $target instanceof Field\FormField) || $target instanceof Field\ChoiceFormField) { + $target->setValue($value); + } elseif (is_array($value)) { + $fields = self::create($name, $value); + foreach ($fields->all() as $k => $v) { + $this->set($k, $v); + } + } else { + throw new \InvalidArgumentException(sprintf('Cannot set value on a compound field "%s".', $name)); + } + } + + /** + * Returns the list of field with their value. + * + * @return FormField[] The list of fields as array((string) Fully qualified name => (mixed) value) + */ + public function all() + { + return $this->walk($this->fields, $this->base); + } + + /** + * Creates an instance of the class. + * + * This function is made private because it allows overriding the $base and + * the $values properties without any type checking. + * + * @param string $base The fully qualified name of the base field + * @param array $values The values of the fields + * + * @return static + */ + private static function create($base, array $values) + { + $registry = new static(); + $registry->base = $base; + $registry->fields = $values; + + return $registry; + } + + /** + * Transforms a PHP array in a list of fully qualified name / value. + * + * @param array $array The PHP array + * @param string $base The name of the base field + * @param array $output The initial values + * + * @return array The list of fields as array((string) Fully qualified name => (mixed) value) + */ + private function walk(array $array, $base = '', array &$output = array()) + { + foreach ($array as $k => $v) { + $path = empty($base) ? $k : sprintf('%s[%s]', $base, $k); + if (is_array($v)) { + $this->walk($v, $path, $output); + } else { + $output[$path] = $v; + } + } + + return $output; + } + + /** + * Splits a field name into segments as a web browser would do. + * + * + * getSegments('base[foo][3][]') = array('base', 'foo, '3', ''); + * + * + * @param string $name The name of the field + * + * @return string[] The list of segments + */ + private function getSegments($name) + { + if (preg_match('/^(?P[^[]+)(?P(\[.*)|$)/', $name, $m)) { + $segments = array($m['base']); + while (!empty($m['extra'])) { + $extra = $m['extra']; + if (preg_match('/^\[(?P.*?)\](?P.*)$/', $extra, $m)) { + $segments[] = $m['segment']; + } else { + $segments[] = $extra; + } + } + + return $segments; + } + + return array($name); + } +} diff --git a/core/vendor/symfony/dom-crawler/LICENSE b/core/vendor/symfony/dom-crawler/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 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/core/vendor/symfony/dom-crawler/Link.php b/core/vendor/symfony/dom-crawler/Link.php new file mode 100644 index 0000000..b68f246 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Link.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +/** + * Link represents an HTML link (an HTML a, area or link tag). + * + * @author Fabien Potencier + */ +class Link +{ + /** + * @var \DOMElement + */ + protected $node; + + /** + * @var string The method to use for the link + */ + protected $method; + + /** + * @var string The URI of the page where the link is embedded (or the base href) + */ + protected $currentUri; + + /** + * @param \DOMElement $node A \DOMElement instance + * @param string $currentUri The URI of the page where the link is embedded (or the base href) + * @param string $method The method to use for the link (get by default) + * + * @throws \InvalidArgumentException if the node is not a link + */ + public function __construct(\DOMElement $node, $currentUri, $method = 'GET') + { + if (!in_array(strtolower(substr($currentUri, 0, 4)), array('http', 'file'))) { + throw new \InvalidArgumentException(sprintf('Current URI must be an absolute URL ("%s").', $currentUri)); + } + + $this->setNode($node); + $this->method = $method ? strtoupper($method) : null; + $this->currentUri = $currentUri; + } + + /** + * Gets the node associated with this link. + * + * @return \DOMElement A \DOMElement instance + */ + public function getNode() + { + return $this->node; + } + + /** + * Gets the method associated with this link. + * + * @return string The method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Gets the URI associated with this link. + * + * @return string The URI + */ + public function getUri() + { + $uri = trim($this->getRawUri()); + + // absolute URL? + if (null !== parse_url($uri, PHP_URL_SCHEME)) { + return $uri; + } + + // empty URI + if (!$uri) { + return $this->currentUri; + } + + // an anchor + if ('#' === $uri[0]) { + return $this->cleanupAnchor($this->currentUri).$uri; + } + + $baseUri = $this->cleanupUri($this->currentUri); + + if ('?' === $uri[0]) { + return $baseUri.$uri; + } + + // absolute URL with relative schema + if (0 === strpos($uri, '//')) { + return preg_replace('#^([^/]*)//.*$#', '$1', $baseUri).$uri; + } + + $baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUri); + + // absolute path + if ('/' === $uri[0]) { + return $baseUri.$uri; + } + + // relative path + $path = parse_url(substr($this->currentUri, strlen($baseUri)), PHP_URL_PATH); + $path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri); + + return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path; + } + + /** + * Returns raw URI data. + * + * @return string + */ + protected function getRawUri() + { + return $this->node->getAttribute('href'); + } + + /** + * Returns the canonicalized URI path (see RFC 3986, section 5.2.4). + * + * @param string $path URI path + * + * @return string + */ + protected function canonicalizePath($path) + { + if ('' === $path || '/' === $path) { + return $path; + } + + if ('.' === substr($path, -1)) { + $path .= '/'; + } + + $output = array(); + + foreach (explode('/', $path) as $segment) { + if ('..' === $segment) { + array_pop($output); + } elseif ('.' !== $segment) { + $output[] = $segment; + } + } + + return implode('/', $output); + } + + /** + * Sets current \DOMElement instance. + * + * @param \DOMElement $node A \DOMElement instance + * + * @throws \LogicException If given node is not an anchor + */ + protected function setNode(\DOMElement $node) + { + if ('a' !== $node->nodeName && 'area' !== $node->nodeName && 'link' !== $node->nodeName) { + throw new \LogicException(sprintf('Unable to navigate from a "%s" tag.', $node->nodeName)); + } + + $this->node = $node; + } + + /** + * Removes the query string and the anchor from the given uri. + * + * @param string $uri The uri to clean + * + * @return string + */ + private function cleanupUri($uri) + { + return $this->cleanupQuery($this->cleanupAnchor($uri)); + } + + /** + * Remove the query string from the uri. + * + * @param string $uri + * + * @return string + */ + private function cleanupQuery($uri) + { + if (false !== $pos = strpos($uri, '?')) { + return substr($uri, 0, $pos); + } + + return $uri; + } + + /** + * Remove the anchor from the uri. + * + * @param string $uri + * + * @return string + */ + private function cleanupAnchor($uri) + { + if (false !== $pos = strpos($uri, '#')) { + return substr($uri, 0, $pos); + } + + return $uri; + } +} diff --git a/core/vendor/symfony/dom-crawler/README.md b/core/vendor/symfony/dom-crawler/README.md new file mode 100644 index 0000000..5fad2e2 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/README.md @@ -0,0 +1,13 @@ +DomCrawler Component +==================== + +The DomCrawler component eases DOM navigation for HTML and XML documents. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/dom_crawler.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/core/vendor/symfony/dom-crawler/Tests/CrawlerTest.php b/core/vendor/symfony/dom-crawler/Tests/CrawlerTest.php new file mode 100644 index 0000000..74acfdf --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Tests/CrawlerTest.php @@ -0,0 +1,1050 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\CssSelector\CssSelector; +use Symfony\Component\DomCrawler\Crawler; + +class CrawlerTest extends TestCase +{ + public function testConstructor() + { + $crawler = new Crawler(); + $this->assertCount(0, $crawler, '__construct() returns an empty crawler'); + + $crawler = new Crawler(new \DOMNode()); + $this->assertCount(1, $crawler, '__construct() takes a node as a first argument'); + } + + public function testAdd() + { + $crawler = new Crawler(); + $crawler->add($this->createDomDocument()); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMDocument'); + + $crawler = new Crawler(); + $crawler->add($this->createNodeList()); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMNodeList'); + + foreach ($this->createNodeList() as $node) { + $list[] = $node; + } + $crawler = new Crawler(); + $crawler->add($list); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from an array of nodes'); + + $crawler = new Crawler(); + $crawler->add($this->createNodeList()->item(0)); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMElement'); + + $crawler = new Crawler(); + $crawler->add('Foo'); + $this->assertEquals('Foo', $crawler->filterXPath('//body')->text(), '->add() adds nodes from a string'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testAddInvalidNode() + { + $crawler = new Crawler(); + $crawler->add(1); + } + + public function testAddHtmlContent() + { + $crawler = new Crawler(); + $crawler->addHtmlContent('
    ', 'UTF-8'); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addHtmlContent() adds nodes from an HTML string'); + + $crawler->addHtmlContent('', 'UTF-8'); + + $this->assertEquals('http://symfony.com', $crawler->filterXPath('//base')->attr('href'), '->addHtmlContent() adds nodes from an HTML string'); + $this->assertEquals('http://symfony.com/contact', $crawler->filterXPath('//a')->link()->getUri(), '->addHtmlContent() adds nodes from an HTML string'); + } + + /** + * @requires extension mbstring + */ + public function testAddHtmlContentCharset() + { + $crawler = new Crawler(); + $crawler->addHtmlContent('
    Tiếng Việt', 'UTF-8'); + + $this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text()); + } + + public function testAddHtmlContentInvalidBaseTag() + { + $crawler = new Crawler(null, 'http://symfony.com'); + + $crawler->addHtmlContent('', 'UTF-8'); + + $this->assertEquals('http://symfony.com/contact', current($crawler->filterXPath('//a')->links())->getUri(), '->addHtmlContent() correctly handles a non-existent base tag href attribute'); + } + + public function testAddHtmlContentUnsupportedCharset() + { + $crawler = new Crawler(); + $crawler->addHtmlContent(file_get_contents(__DIR__.'/Fixtures/windows-1250.html'), 'Windows-1250'); + + $this->assertEquals('Žťčýů', $crawler->filterXPath('//p')->text()); + } + + /** + * @requires extension mbstring + */ + public function testAddHtmlContentCharsetGbk() + { + $crawler = new Crawler(); + //gbk encode of

    中文

    + $crawler->addHtmlContent(base64_decode('PGh0bWw+PHA+1tDOxDwvcD48L2h0bWw+'), 'gbk'); + + $this->assertEquals('中文', $crawler->filterXPath('//p')->text()); + } + + public function testAddHtmlContentWithErrors() + { + $internalErrors = libxml_use_internal_errors(true); + + $crawler = new Crawler(); + $crawler->addHtmlContent(<<<'EOF' + + + + + + + + +EOF + , 'UTF-8'); + + $errors = libxml_get_errors(); + $this->assertCount(1, $errors); + $this->assertEquals("Tag nav invalid\n", $errors[0]->message); + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + } + + public function testAddXmlContent() + { + $crawler = new Crawler(); + $crawler->addXmlContent('
    ', 'UTF-8'); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addXmlContent() adds nodes from an XML string'); + } + + public function testAddXmlContentCharset() + { + $crawler = new Crawler(); + $crawler->addXmlContent('
    Tiếng Việt
    ', 'UTF-8'); + + $this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text()); + } + + public function testAddXmlContentWithErrors() + { + $internalErrors = libxml_use_internal_errors(true); + + $crawler = new Crawler(); + $crawler->addXmlContent(<<<'EOF' + + + + + +
    + + +EOF + , 'UTF-8'); + + $this->assertGreaterThan(1, libxml_get_errors()); + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + } + + public function testAddContent() + { + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/html; charset=UTF-8'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string'); + + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/html; charset=UTF-8; dir=RTL'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string with extended content type'); + + $crawler = new Crawler(); + $crawler->addContent('
    '); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() uses text/html as the default type'); + + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/xml; charset=UTF-8'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string'); + + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/xml'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string'); + + $crawler = new Crawler(); + $crawler->addContent('foo bar', 'text/plain'); + $this->assertCount(0, $crawler, '->addContent() does nothing if the type is not (x|ht)ml'); + + $crawler = new Crawler(); + $crawler->addContent('中文'); + $this->assertEquals('中文', $crawler->filterXPath('//span')->text(), '->addContent() guess wrong charset'); + } + + /** + * @requires extension iconv + */ + public function testAddContentNonUtf8() + { + $crawler = new Crawler(); + $crawler->addContent(iconv('UTF-8', 'SJIS', '日本語')); + $this->assertEquals('日本語', $crawler->filterXPath('//body')->text(), '->addContent() can recognize "Shift_JIS" in html5 meta charset tag'); + } + + public function testAddDocument() + { + $crawler = new Crawler(); + $crawler->addDocument($this->createDomDocument()); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addDocument() adds nodes from a \DOMDocument'); + } + + public function testAddNodeList() + { + $crawler = new Crawler(); + $crawler->addNodeList($this->createNodeList()); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodeList() adds nodes from a \DOMNodeList'); + } + + public function testAddNodes() + { + foreach ($this->createNodeList() as $node) { + $list[] = $node; + } + + $crawler = new Crawler(); + $crawler->addNodes($list); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodes() adds nodes from an array of nodes'); + } + + public function testAddNode() + { + $crawler = new Crawler(); + $crawler->addNode($this->createNodeList()->item(0)); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNode() adds nodes from a \DOMElement'); + } + + public function testClear() + { + $crawler = new Crawler(new \DOMNode()); + $crawler->clear(); + $this->assertCount(0, $crawler, '->clear() removes all the nodes from the crawler'); + } + + public function testEq() + { + $crawler = $this->createTestCrawler()->filterXPath('//li'); + $this->assertNotSame($crawler, $crawler->eq(0), '->eq() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->eq() returns a new instance of a crawler'); + + $this->assertEquals('Two', $crawler->eq(1)->text(), '->eq() returns the nth node of the list'); + $this->assertCount(0, $crawler->eq(100), '->eq() returns an empty crawler if the nth node does not exist'); + } + + public function testEach() + { + $data = $this->createTestCrawler()->filterXPath('//ul[1]/li')->each(function ($node, $i) { + return $i.'-'.$node->text(); + }); + + $this->assertEquals(array('0-One', '1-Two', '2-Three'), $data, '->each() executes an anonymous function on each node of the list'); + } + + public function testSlice() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + $this->assertNotSame($crawler->slice(), $crawler, '->slice() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler->slice(), '->slice() returns a new instance of a crawler'); + + $this->assertCount(3, $crawler->slice(), '->slice() does not slice the nodes in the list if any param is entered'); + $this->assertCount(1, $crawler->slice(1, 1), '->slice() slices the nodes in the list'); + } + + public function testReduce() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + $nodes = $crawler->reduce(function ($node, $i) { + return 1 !== $i; + }); + $this->assertNotSame($nodes, $crawler, '->reduce() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $nodes, '->reduce() returns a new instance of a crawler'); + + $this->assertCount(2, $nodes, '->reduce() filters the nodes in the list'); + } + + public function testAttr() + { + $this->assertEquals('first', $this->createTestCrawler()->filterXPath('//li')->attr('class'), '->attr() returns the attribute of the first element of the node list'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->attr('class'); + $this->fail('->attr() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->attr() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testMissingAttrValueIsNull() + { + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/html; charset=UTF-8'); + $div = $crawler->filterXPath('//div'); + + $this->assertEquals('sample value', $div->attr('non-empty-attr'), '->attr() reads non-empty attributes correctly'); + $this->assertEquals('', $div->attr('empty-attr'), '->attr() reads empty attributes correctly'); + $this->assertNull($div->attr('missing-attr'), '->attr() reads missing attributes correctly'); + } + + public function testNodeName() + { + $this->assertEquals('li', $this->createTestCrawler()->filterXPath('//li')->nodeName(), '->nodeName() returns the node name of the first element of the node list'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->nodeName(); + $this->fail('->nodeName() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->nodeName() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testText() + { + $this->assertEquals('One', $this->createTestCrawler()->filterXPath('//li')->text(), '->text() returns the node value of the first element of the node list'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->text(); + $this->fail('->text() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->text() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testHtml() + { + $this->assertEquals('Bar', $this->createTestCrawler()->filterXPath('//a[5]')->html()); + $this->assertEquals('', trim(preg_replace('~>\s+<~', '><', $this->createTestCrawler()->filterXPath('//form[@id="FooFormId"]')->html()))); + + try { + $this->createTestCrawler()->filterXPath('//ol')->html(); + $this->fail('->html() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->html() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testExtract() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + + $this->assertEquals(array('One', 'Two', 'Three'), $crawler->extract('_text'), '->extract() returns an array of extracted data from the node list'); + $this->assertEquals(array(array('One', 'first'), array('Two', ''), array('Three', '')), $crawler->extract(array('_text', 'class')), '->extract() returns an array of extracted data from the node list'); + $this->assertEquals(array(array(), array(), array()), $crawler->extract(array()), '->extract() returns empty arrays if the attribute list is empty'); + + $this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->extract('_text'), '->extract() returns an empty array if the node list is empty'); + } + + public function testFilterXpathComplexQueries() + { + $crawler = $this->createTestCrawler()->filterXPath('//body'); + + $this->assertCount(0, $crawler->filterXPath('/input')); + $this->assertCount(0, $crawler->filterXPath('/body')); + $this->assertCount(1, $crawler->filterXPath('/_root/body')); + $this->assertCount(1, $crawler->filterXPath('./body')); + $this->assertCount(1, $crawler->filterXPath('.//body')); + $this->assertCount(5, $crawler->filterXPath('.//input')); + $this->assertCount(4, $crawler->filterXPath('//form')->filterXPath('//button | //input')); + $this->assertCount(1, $crawler->filterXPath('body')); + $this->assertCount(6, $crawler->filterXPath('//button | //input')); + $this->assertCount(1, $crawler->filterXPath('//body')); + $this->assertCount(1, $crawler->filterXPath('descendant-or-self::body')); + $this->assertCount(1, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('./div'), 'A child selection finds only the current div'); + $this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child'); + $this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child'); + $this->assertCount(5, $crawler->filterXPath('(//a | //div)//img')); + $this->assertCount(7, $crawler->filterXPath('((//a | //div)//img | //ul)')); + $this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )')); + $this->assertCount(1, $crawler->filterXPath("//a[./@href][((./@id = 'Klausi|Claudiu' or normalize-space(string(.)) = 'Klausi|Claudiu' or ./@title = 'Klausi|Claudiu' or ./@rel = 'Klausi|Claudiu') or .//img[./@alt = 'Klausi|Claudiu'])]")); + } + + public function testFilterXPath() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->filterXPath('//li'), '->filterXPath() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filterXPath() returns a new instance of a crawler'); + + $crawler = $this->createTestCrawler()->filterXPath('//ul'); + $this->assertCount(6, $crawler->filterXPath('//li'), '->filterXPath() filters the node list with the XPath expression'); + + $crawler = $this->createTestCrawler(); + $this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->parents(), '->filterXpath() preserves parents when chained'); + } + + public function testFilterXPathWithDefaultNamespace() + { + $crawler = $this->createTestXmlCrawler()->filterXPath('//default:entry/default:id'); + $this->assertCount(1, $crawler, '->filterXPath() automatically registers a namespace'); + $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); + } + + public function testFilterXPathWithCustomDefaultNamespace() + { + $crawler = $this->createTestXmlCrawler(); + $crawler->setDefaultNamespacePrefix('x'); + $crawler = $crawler->filterXPath('//x:entry/x:id'); + + $this->assertCount(1, $crawler, '->filterXPath() lets to override the default namespace prefix'); + $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); + } + + public function testFilterXPathWithNamespace() + { + $crawler = $this->createTestXmlCrawler()->filterXPath('//yt:accessControl'); + $this->assertCount(2, $crawler, '->filterXPath() automatically registers a namespace'); + } + + public function testFilterXPathWithMultipleNamespaces() + { + $crawler = $this->createTestXmlCrawler()->filterXPath('//media:group/yt:aspectRatio'); + $this->assertCount(1, $crawler, '->filterXPath() automatically registers multiple namespaces'); + $this->assertSame('widescreen', $crawler->text()); + } + + public function testFilterXPathWithManuallyRegisteredNamespace() + { + $crawler = $this->createTestXmlCrawler(); + $crawler->registerNamespace('m', 'http://search.yahoo.com/mrss/'); + + $crawler = $crawler->filterXPath('//m:group/yt:aspectRatio'); + $this->assertCount(1, $crawler, '->filterXPath() uses manually registered namespace'); + $this->assertSame('widescreen', $crawler->text()); + } + + public function testFilterXPathWithAnUrl() + { + $crawler = $this->createTestXmlCrawler(); + + $crawler = $crawler->filterXPath('//media:category[@scheme="http://gdata.youtube.com/schemas/2007/categories.cat"]'); + $this->assertCount(1, $crawler); + $this->assertSame('Music', $crawler->text()); + } + + public function testFilterXPathWithFakeRoot() + { + $crawler = $this->createTestCrawler(); + $this->assertCount(0, $crawler->filterXPath('.'), '->filterXPath() returns an empty result if the XPath references the fake root node'); + $this->assertCount(0, $crawler->filterXPath('/_root'), '->filterXPath() returns an empty result if the XPath references the fake root node'); + $this->assertCount(0, $crawler->filterXPath('self::*'), '->filterXPath() returns an empty result if the XPath references the fake root node'); + $this->assertCount(0, $crawler->filterXPath('self::_root'), '->filterXPath() returns an empty result if the XPath references the fake root node'); + } + + public function testFilterXPathWithAncestorAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//form'); + + $this->assertCount(0, $crawler->filterXPath('ancestor::*'), 'The fake root node has no ancestor nodes'); + } + + public function testFilterXPathWithAncestorOrSelfAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//form'); + + $this->assertCount(0, $crawler->filterXPath('ancestor-or-self::*'), 'The fake root node has no ancestor nodes'); + } + + public function testFilterXPathWithAttributeAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//form'); + + $this->assertCount(0, $crawler->filterXPath('attribute::*'), 'The fake root node has no attribute nodes'); + } + + public function testFilterXPathWithAttributeAxisAfterElementAxis() + { + $this->assertCount(3, $this->createTestCrawler()->filterXPath('//form/button/attribute::*'), '->filterXPath() handles attribute axes properly when they are preceded by an element filtering axis'); + } + + public function testFilterXPathWithChildAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]'); + + $this->assertCount(1, $crawler->filterXPath('child::div'), 'A child selection finds only the current div'); + } + + public function testFilterXPathWithFollowingAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//a'); + + $this->assertCount(0, $crawler->filterXPath('following::div'), 'The fake root node has no following nodes'); + } + + public function testFilterXPathWithFollowingSiblingAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//a'); + + $this->assertCount(0, $crawler->filterXPath('following-sibling::div'), 'The fake root node has no following nodes'); + } + + public function testFilterXPathWithNamespaceAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//button'); + + $this->assertCount(0, $crawler->filterXPath('namespace::*'), 'The fake root node has no namespace nodes'); + } + + public function testFilterXPathWithNamespaceAxisAfterElementAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]/namespace::*'); + + $this->assertCount(0, $crawler->filterXPath('namespace::*'), 'Namespace axes cannot be requested'); + } + + public function testFilterXPathWithParentAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//button'); + + $this->assertCount(0, $crawler->filterXPath('parent::*'), 'The fake root node has no parent nodes'); + } + + public function testFilterXPathWithPrecedingAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//form'); + + $this->assertCount(0, $crawler->filterXPath('preceding::*'), 'The fake root node has no preceding nodes'); + } + + public function testFilterXPathWithPrecedingSiblingAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//form'); + + $this->assertCount(0, $crawler->filterXPath('preceding-sibling::*'), 'The fake root node has no preceding nodes'); + } + + public function testFilterXPathWithSelfAxes() + { + $crawler = $this->createTestCrawler()->filterXPath('//a'); + + $this->assertCount(0, $crawler->filterXPath('self::a'), 'The fake root node has no "real" element name'); + $this->assertCount(0, $crawler->filterXPath('self::a/img'), 'The fake root node has no "real" element name'); + $this->assertCount(10, $crawler->filterXPath('self::*/a')); + } + + public function testFilter() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->filter('li'), '->filter() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filter() returns a new instance of a crawler'); + + $crawler = $this->createTestCrawler()->filter('ul'); + + $this->assertCount(6, $crawler->filter('li'), '->filter() filters the node list with the CSS selector'); + } + + public function testFilterWithDefaultNamespace() + { + $crawler = $this->createTestXmlCrawler()->filter('default|entry default|id'); + $this->assertCount(1, $crawler, '->filter() automatically registers namespaces'); + $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); + } + + public function testFilterWithNamespace() + { + CssSelector::disableHtmlExtension(); + + $crawler = $this->createTestXmlCrawler()->filter('yt|accessControl'); + $this->assertCount(2, $crawler, '->filter() automatically registers namespaces'); + } + + public function testFilterWithMultipleNamespaces() + { + CssSelector::disableHtmlExtension(); + + $crawler = $this->createTestXmlCrawler()->filter('media|group yt|aspectRatio'); + $this->assertCount(1, $crawler, '->filter() automatically registers namespaces'); + $this->assertSame('widescreen', $crawler->text()); + } + + public function testFilterWithDefaultNamespaceOnly() + { + $crawler = new Crawler(' + + + http://localhost/foo + weekly + 0.5 + 2012-11-16 + + + http://localhost/bar + weekly + 0.5 + 2012-11-16 + + + '); + + $this->assertEquals(2, $crawler->filter('url')->count()); + } + + public function testSelectLink() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->selectLink('Foo'), '->selectLink() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectLink() returns a new instance of a crawler'); + + $this->assertCount(1, $crawler->selectLink('Fabien\'s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(1, $crawler->selectLink('Fabien\'s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(2, $crawler->selectLink('Fabien"s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(2, $crawler->selectLink('Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(1, $crawler->selectLink('\' Fabien"s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(1, $crawler->selectLink('\' Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(4, $crawler->selectLink('Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(4, $crawler->selectLink('Bar'), '->selectLink() selects links by the node values'); + } + + public function testSelectButton() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->selectButton('FooValue'), '->selectButton() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectButton() returns a new instance of a crawler'); + + $this->assertEquals(1, $crawler->selectButton('FooValue')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('FooName')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('FooId')->count(), '->selectButton() selects buttons'); + + $this->assertEquals(1, $crawler->selectButton('BarValue')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('BarName')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('BarId')->count(), '->selectButton() selects buttons'); + + $this->assertEquals(1, $crawler->selectButton('FooBarValue')->count(), '->selectButton() selects buttons with form attribute too'); + $this->assertEquals(1, $crawler->selectButton('FooBarName')->count(), '->selectButton() selects buttons with form attribute too'); + } + + public function testSelectButtonWithSingleQuotesInNameAttribute() + { + $html = <<<'HTML' + + + +
    +
    + + + + +HTML; + + $crawler = new Crawler($html); + + $this->assertCount(1, $crawler->selectButton('Click \'Here\'')); + } + + public function testSelectButtonWithDoubleQuotesInNameAttribute() + { + $html = <<<'HTML' + + + +
    + Login +
    +
    + + + + +HTML; + + $crawler = new Crawler($html); + + $this->assertCount(1, $crawler->selectButton('Click "Here"')); + } + + public function testLink() + { + $crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $crawler->link(), '->link() returns a Link instance'); + + $this->assertEquals('POST', $crawler->link('post')->getMethod(), '->link() takes a method as its argument'); + + $crawler = $this->createTestCrawler('http://example.com/bar')->selectLink('GetLink'); + $this->assertEquals('http://example.com/bar?get=param', $crawler->link()->getUri(), '->link() returns a Link instance'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->link(); + $this->fail('->link() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->link() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testSelectLinkAndLinkFiltered() + { + $html = <<<'HTML' + + + +
    + Login +
    +
    + + + + +HTML; + + $crawler = new Crawler($html); + $filtered = $crawler->filterXPath("descendant-or-self::*[@id = 'login-form']"); + + $this->assertCount(0, $filtered->selectLink('Login')); + $this->assertCount(1, $filtered->selectButton('Submit')); + + $filtered = $crawler->filterXPath("descendant-or-self::*[@id = 'action']"); + + $this->assertCount(1, $filtered->selectLink('Login')); + $this->assertCount(0, $filtered->selectButton('Submit')); + + $this->assertCount(1, $crawler->selectLink('Login')->selectLink('Login')); + $this->assertCount(1, $crawler->selectButton('Submit')->selectButton('Submit')); + } + + public function testChaining() + { + $crawler = new Crawler('
    '); + + $this->assertEquals('a', $crawler->filterXPath('//div')->filterXPath('div')->filterXPath('div')->attr('name')); + } + + public function testLinks() + { + $crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo'); + $this->assertInternalType('array', $crawler->links(), '->links() returns an array'); + + $this->assertCount(4, $crawler->links(), '->links() returns an array'); + $links = $crawler->links(); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $links[0], '->links() returns an array of Link instances'); + + $this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->links(), '->links() returns an empty array if the node selection is empty'); + } + + public function testForm() + { + $testCrawler = $this->createTestCrawler('http://example.com/bar/'); + $crawler = $testCrawler->selectButton('FooValue'); + $crawler2 = $testCrawler->selectButton('FooBarValue'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler->form(), '->form() returns a Form instance'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler2->form(), '->form() returns a Form instance'); + + $this->assertEquals($crawler->form()->getFormNode()->getAttribute('id'), $crawler2->form()->getFormNode()->getAttribute('id'), '->form() works on elements with form attribute'); + + $this->assertEquals(array('FooName' => 'FooBar', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form(array('FooName' => 'FooBar'))->getValues(), '->form() takes an array of values to submit as its first argument'); + $this->assertEquals(array('FooName' => 'FooValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form()->getValues(), '->getValues() returns correct form values'); + $this->assertEquals(array('FooBarName' => 'FooBarValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler2->form()->getValues(), '->getValues() returns correct form values'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->form(); + $this->fail('->form() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->form() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testLast() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + $this->assertNotSame($crawler, $crawler->last(), '->last() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->last() returns a new instance of a crawler'); + + $this->assertEquals('Three', $crawler->last()->text()); + } + + public function testFirst() + { + $crawler = $this->createTestCrawler()->filterXPath('//li'); + $this->assertNotSame($crawler, $crawler->first(), '->first() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->first() returns a new instance of a crawler'); + + $this->assertEquals('One', $crawler->first()->text()); + } + + public function testSiblings() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1); + $this->assertNotSame($crawler, $crawler->siblings(), '->siblings() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->siblings() returns a new instance of a crawler'); + + $nodes = $crawler->siblings(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('One', $nodes->eq(0)->text()); + $this->assertEquals('Three', $nodes->eq(1)->text()); + + $nodes = $this->createTestCrawler()->filterXPath('//li')->eq(0)->siblings(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('Two', $nodes->eq(0)->text()); + $this->assertEquals('Three', $nodes->eq(1)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->siblings(); + $this->fail('->siblings() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->siblings() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testNextAll() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1); + $this->assertNotSame($crawler, $crawler->nextAll(), '->nextAll() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->nextAll() returns a new instance of a crawler'); + + $nodes = $crawler->nextAll(); + $this->assertEquals(1, $nodes->count()); + $this->assertEquals('Three', $nodes->eq(0)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->nextAll(); + $this->fail('->nextAll() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->nextAll() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testPreviousAll() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(2); + $this->assertNotSame($crawler, $crawler->previousAll(), '->previousAll() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->previousAll() returns a new instance of a crawler'); + + $nodes = $crawler->previousAll(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('Two', $nodes->eq(0)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->previousAll(); + $this->fail('->previousAll() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->previousAll() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testChildren() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul'); + $this->assertNotSame($crawler, $crawler->children(), '->children() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->children() returns a new instance of a crawler'); + + $nodes = $crawler->children(); + $this->assertEquals(3, $nodes->count()); + $this->assertEquals('One', $nodes->eq(0)->text()); + $this->assertEquals('Two', $nodes->eq(1)->text()); + $this->assertEquals('Three', $nodes->eq(2)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->children(); + $this->fail('->children() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->children() throws an \InvalidArgumentException if the node list is empty'); + } + + try { + $crawler = new Crawler('

    '); + $crawler->filter('p')->children(); + $this->assertTrue(true, '->children() does not trigger a notice if the node has no children'); + } catch (\PHPUnit\Framework\Error\Notice $e) { + $this->fail('->children() does not trigger a notice if the node has no children'); + } catch (\PHPUnit_Framework_Error_Notice $e) { + $this->fail('->children() does not trigger a notice if the node has no children'); + } + } + + public function testParents() + { + $crawler = $this->createTestCrawler()->filterXPath('//li[1]'); + $this->assertNotSame($crawler, $crawler->parents(), '->parents() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->parents() returns a new instance of a crawler'); + + $nodes = $crawler->parents(); + $this->assertEquals(3, $nodes->count()); + + $nodes = $this->createTestCrawler()->filterXPath('//html')->parents(); + $this->assertEquals(0, $nodes->count()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->parents(); + $this->fail('->parents() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->parents() throws an \InvalidArgumentException if the node list is empty'); + } + } + + /** + * @dataProvider getBaseTagData + */ + public function testBaseTag($baseValue, $linkValue, $expectedUri, $currentUri = null, $description = null) + { + $crawler = new Crawler('', $currentUri); + $this->assertEquals($expectedUri, $crawler->filterXPath('//a')->link()->getUri(), $description); + } + + public function getBaseTagData() + { + return array( + array('http://base.com', 'link', 'http://base.com/link'), + array('//base.com', 'link', 'https://base.com/link', 'https://domain.com', ' tag can use a schema-less URL'), + array('path/', 'link', 'https://domain.com/path/link', 'https://domain.com', ' tag can set a path'), + array('http://base.com', '#', 'http://base.com#', 'http://domain.com/path/link', ' tag does work with links to an anchor'), + array('http://base.com', '', 'http://base.com', 'http://domain.com/path/link', ' tag does work with empty links'), + ); + } + + /** + * @dataProvider getBaseTagWithFormData + */ + public function testBaseTagWithForm($baseValue, $actionValue, $expectedUri, $currentUri = null, $description = null) + { + $crawler = new Crawler('
    ', + array('bar' => array('InputFormField', 'bar')), + ), + array( + 'appends the submitted button value but not other submit buttons', + ' + ', + array('foobar' => array('InputFormField', 'foobar')), + ), + array( + 'turns an image input into x and y fields', + '', + array('bar.x' => array('InputFormField', '0'), 'bar.y' => array('InputFormField', '0')), + ), + array( + 'returns textareas', + ' + ', + array('foo' => array('TextareaFormField', 'foo')), + ), + array( + 'returns inputs', + ' + ', + array('foo' => array('InputFormField', 'foo')), + ), + array( + 'returns checkboxes', + ' + ', + array('foo' => array('ChoiceFormField', 'foo')), + ), + array( + 'returns not-checked checkboxes', + ' + ', + array('foo' => array('ChoiceFormField', false)), + ), + array( + 'returns radio buttons', + ' + + ', + array('foo' => array('ChoiceFormField', 'bar')), + ), + array( + 'returns file inputs', + ' + ', + array('foo' => array('FileFormField', array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), + ), + ); + } + + public function testGetFormNode() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
    '); + + $form = new Form($dom->getElementsByTagName('input')->item(0), 'http://example.com'); + + $this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); + } + + public function testGetFormNodeFromNamedForm() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
    '); + + $form = new Form($dom->getElementsByTagName('form')->item(0), 'http://example.com'); + + $this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); + } + + public function testGetMethod() + { + $form = $this->createForm('
    '); + $this->assertEquals('GET', $form->getMethod(), '->getMethod() returns get if no method is defined'); + + $form = $this->createForm('
    '); + $this->assertEquals('POST', $form->getMethod(), '->getMethod() returns the method attribute value of the form'); + + $form = $this->createForm('
    ', 'put'); + $this->assertEquals('PUT', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + + $form = $this->createForm('
    ', 'delete'); + $this->assertEquals('DELETE', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + + $form = $this->createForm('
    ', 'patch'); + $this->assertEquals('PATCH', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + } + + public function testGetSetValue() + { + $form = $this->createForm('
    '); + + $this->assertEquals('foo', $form['foo']->getValue(), '->offsetGet() returns the value of a form field'); + + $form['foo'] = 'bar'; + + $this->assertEquals('bar', $form['foo']->getValue(), '->offsetSet() changes the value of a form field'); + + try { + $form['foobar'] = 'bar'; + $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } + + try { + $form['foobar']; + $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } + } + + public function testDisableValidation() + { + $form = $this->createForm('
    + + + + '); + + $form->disableValidation(); + + $form['foo[bar]']->select('foo'); + $form['foo[baz]']->select('bar'); + $this->assertEquals('foo', $form['foo[bar]']->getValue(), '->disableValidation() disables validation of all ChoiceFormField.'); + $this->assertEquals('bar', $form['foo[baz]']->getValue(), '->disableValidation() disables validation of all ChoiceFormField.'); + } + + public function testOffsetUnset() + { + $form = $this->createForm('
    '); + unset($form['foo']); + $this->assertArrayNotHasKey('foo', $form, '->offsetUnset() removes a field'); + } + + public function testOffsetExists() + { + $form = $this->createForm('
    '); + + $this->assertArrayHasKey('foo', $form, '->offsetExists() return true if the field exists'); + $this->assertArrayNotHasKey('bar', $form, '->offsetExists() return false if the field does not exist'); + } + + public function testGetValues() + { + $form = $this->createForm('
    '); + $this->assertEquals(array('foo[bar]' => 'foo', 'bar' => 'bar', 'baz' => array()), $form->getValues(), '->getValues() returns all form field values'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include not-checked checkboxes'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include file input fields'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include disabled fields'); + } + + public function testSetValues() + { + $form = $this->createForm('
    '); + $form->setValues(array('foo' => false, 'bar' => 'foo')); + $this->assertEquals(array('bar' => 'foo'), $form->getValues(), '->setValues() sets the values of fields'); + } + + public function testMultiselectSetValues() + { + $form = $this->createForm('
    '); + $form->setValues(array('multi' => array('foo', 'bar'))); + $this->assertEquals(array('multi' => array('foo', 'bar')), $form->getValues(), '->setValue() sets the values of select'); + } + + public function testGetPhpValues() + { + $form = $this->createForm('
    '); + $this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), '->getPhpValues() converts keys with [] to arrays'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('fo.o' => array('ba.r' => 'foo'), 'ba r' => 'bar'), $form->getPhpValues(), '->getPhpValues() preserves periods and spaces in names'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('fo.o' => array('ba.r' => array('foo', 'ba.z' => 'bar'))), $form->getPhpValues(), '->getPhpValues() preserves periods and spaces in names recursively'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), "->getPhpValues() doesn't return empty values"); + } + + public function testGetFiles() + { + $form = $this->createForm('
    '); + $this->assertEquals(array(), $form->getFiles(), '->getFiles() returns an empty array if method is get'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for POST'); + + $form = $this->createForm('
    ', 'put'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PUT'); + + $form = $this->createForm('
    ', 'delete'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for DELETE'); + + $form = $this->createForm('
    ', 'patch'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PATCH'); + + $form = $this->createForm('
    '); + $this->assertEquals(array(), $form->getFiles(), '->getFiles() does not include disabled file fields'); + } + + public function testGetPhpFiles() + { + $form = $this->createForm('
    '); + $this->assertEquals(array('foo' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() converts keys with [] to arrays'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('f.o o' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() preserves periods and spaces in names'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('f.o o' => array('bar' => array('ba.z' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0), array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)))), $form->getPhpFiles(), '->getPhpFiles() preserves periods and spaces in names recursively'); + + $form = $this->createForm('
    '); + $files = $form->getPhpFiles(); + + $this->assertSame(0, $files['foo']['bar']['size'], '->getPhpFiles() converts size to int'); + $this->assertSame(4, $files['foo']['bar']['error'], '->getPhpFiles() converts error to int'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('size' => array('error' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() int conversion does not collide with file names'); + } + + /** + * @dataProvider provideGetUriValues + */ + public function testGetUri($message, $form, $values, $uri, $method = null) + { + $form = $this->createForm($form, $method); + $form->setValues($values); + + $this->assertEquals('http://example.com'.$uri, $form->getUri(), '->getUri() '.$message); + } + + public function testGetBaseUri() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
    '); + + $nodes = $dom->getElementsByTagName('input'); + $form = new Form($nodes->item($nodes->length - 1), 'http://www.foo.com/'); + $this->assertEquals('http://www.foo.com/foo.php', $form->getUri()); + } + + public function testGetUriWithAnchor() + { + $form = $this->createForm('
    ', null, 'http://example.com/id/123'); + + $this->assertEquals('http://example.com/id/123#foo', $form->getUri()); + } + + public function testGetUriActionAbsolute() + { + $formHtml = '
    '; + + $form = $this->createForm($formHtml); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://login.foo.com'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://login.foo.com/bar/'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + // The action URI haven't the same domain Host have an another domain as Host + $form = $this->createForm($formHtml, null, 'https://www.foo.com'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://www.foo.com/bar/'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + } + + public function testGetUriAbsolute() + { + $form = $this->createForm('
    ', null, 'http://localhost/foo/'); + $this->assertEquals('http://localhost/foo/foo', $form->getUri(), '->getUri() returns absolute URIs'); + + $form = $this->createForm('
    ', null, 'http://localhost/foo/'); + $this->assertEquals('http://localhost/foo', $form->getUri(), '->getUri() returns absolute URIs'); + } + + public function testGetUriWithOnlyQueryString() + { + $form = $this->createForm('
    ', null, 'http://localhost/foo/bar'); + $this->assertEquals('http://localhost/foo/bar?get=param', $form->getUri(), '->getUri() returns absolute URIs only if the host has been defined in the constructor'); + } + + public function testGetUriWithoutAction() + { + $form = $this->createForm('
    ', null, 'http://localhost/foo/bar'); + $this->assertEquals('http://localhost/foo/bar', $form->getUri(), '->getUri() returns path if no action defined'); + } + + public function provideGetUriValues() + { + return array( + array( + 'returns the URI of the form', + '
    ', + array(), + '/foo', + ), + array( + 'appends the form values if the method is get', + '
    ', + array(), + '/foo?foo=foo', + ), + array( + 'appends the form values and merges the submitted values', + '
    ', + array('foo' => 'bar'), + '/foo?foo=bar', + ), + array( + 'does not append values if the method is post', + '
    ', + array(), + '/foo', + ), + array( + 'does not append values if the method is patch', + '
    ', + array(), + '/foo', + 'PUT', + ), + array( + 'does not append values if the method is delete', + '
    ', + array(), + '/foo', + 'DELETE', + ), + array( + 'does not append values if the method is put', + '
    ', + array(), + '/foo', + 'PATCH', + ), + array( + 'appends the form values to an existing query string', + '
    ', + array(), + '/foo?bar=bar&foo=foo', + ), + array( + 'replaces query values with the form values', + '
    ', + array(), + '/foo?bar=foo', + ), + array( + 'returns an empty URI if the action is empty', + '
    ', + array(), + '/', + ), + array( + 'appends the form values even if the action is empty', + '
    ', + array(), + '/?foo=foo', + ), + array( + 'chooses the path if the action attribute value is a sharp (#)', + '
    ', + array(), + '/#', + ), + ); + } + + public function testHas() + { + $form = $this->createForm('
    '); + + $this->assertFalse($form->has('foo'), '->has() returns false if a field is not in the form'); + $this->assertTrue($form->has('bar'), '->has() returns true if a field is in the form'); + } + + public function testRemove() + { + $form = $this->createForm('
    '); + $form->remove('bar'); + $this->assertFalse($form->has('bar'), '->remove() removes a field'); + } + + public function testGet() + { + $form = $this->createForm('
    '); + + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Field\\InputFormField', $form->get('bar'), '->get() returns the field object associated with the given name'); + + try { + $form->get('foo'); + $this->fail('->get() throws an \InvalidArgumentException if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->get() throws an \InvalidArgumentException if the field does not exist'); + } + } + + public function testAll() + { + $form = $this->createForm('
    '); + + $fields = $form->all(); + $this->assertCount(1, $fields, '->all() return an array of form field objects'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Field\\InputFormField', $fields['bar'], '->all() return an array of form field objects'); + } + + public function testSubmitWithoutAFormButton() + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + +
    + + + + '); + + $nodes = $dom->getElementsByTagName('form'); + $form = new Form($nodes->item(0), 'http://example.com'); + $this->assertSame($nodes->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); + } + + public function testTypeAttributeIsCaseInsensitive() + { + $form = $this->createForm('
    '); + $this->assertTrue($form->has('example.x'), '->has() returns true if the image input was correctly turned into an x and a y fields'); + $this->assertTrue($form->has('example.y'), '->has() returns true if the image input was correctly turned into an x and a y fields'); + } + + public function testFormFieldRegistryAcceptAnyNames() + { + $field = $this->getFormFieldMock('[t:dbt%3adate;]data_daterange_enddate_value'); + + $registry = new FormFieldRegistry(); + $registry->add($field); + $this->assertEquals($field, $registry->get('[t:dbt%3adate;]data_daterange_enddate_value')); + $registry->set('[t:dbt%3adate;]data_daterange_enddate_value', null); + + $form = $this->createForm('
    '); + $form['[t:dbt%3adate;]data_daterange_enddate_value'] = 'bar'; + + $registry->remove('[t:dbt%3adate;]data_daterange_enddate_value'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryGetThrowAnExceptionWhenTheFieldDoesNotExist() + { + $registry = new FormFieldRegistry(); + $registry->get('foo'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistrySetThrowAnExceptionWhenTheFieldDoesNotExist() + { + $registry = new FormFieldRegistry(); + $registry->set('foo', null); + } + + public function testFormFieldRegistryHasReturnsTrueWhenTheFQNExists() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo[bar]')); + + $this->assertTrue($registry->has('foo')); + $this->assertTrue($registry->has('foo[bar]')); + $this->assertFalse($registry->has('bar')); + $this->assertFalse($registry->has('foo[foo]')); + } + + public function testFormRegistryFieldsCanBeRemoved() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo')); + $registry->remove('foo'); + $this->assertFalse($registry->has('foo')); + } + + public function testFormRegistrySupportsMultivaluedFields() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo[]')); + $registry->add($this->getFormFieldMock('foo[]')); + $registry->add($this->getFormFieldMock('bar[5]')); + $registry->add($this->getFormFieldMock('bar[]')); + $registry->add($this->getFormFieldMock('bar[baz]')); + + $this->assertEquals( + array('foo[0]', 'foo[1]', 'bar[5]', 'bar[6]', 'bar[baz]'), + array_keys($registry->all()) + ); + } + + public function testFormRegistrySetValues() + { + $registry = new FormFieldRegistry(); + $registry->add($f2 = $this->getFormFieldMock('foo[2]')); + $registry->add($f3 = $this->getFormFieldMock('foo[3]')); + $registry->add($fbb = $this->getFormFieldMock('foo[bar][baz]')); + + $f2 + ->expects($this->exactly(2)) + ->method('setValue') + ->with(2) + ; + + $f3 + ->expects($this->exactly(2)) + ->method('setValue') + ->with(3) + ; + + $fbb + ->expects($this->exactly(2)) + ->method('setValue') + ->with('fbb') + ; + + $registry->set('foo[2]', 2); + $registry->set('foo[3]', 3); + $registry->set('foo[bar][baz]', 'fbb'); + + $registry->set('foo', array( + 2 => 2, + 3 => 3, + 'bar' => array( + 'baz' => 'fbb', + ), + )); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cannot set value on a compound field "foo[bar]". + */ + public function testFormRegistrySetValueOnCompoundField() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo[bar][baz]')); + + $registry->set('foo[bar]', 'fbb'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unreachable field "0" + */ + public function testFormRegistrySetArrayOnNotCompoundField() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('bar')); + + $registry->set('bar', array('baz')); + } + + public function testDifferentFieldTypesWithSameName() + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + + +
    + + + + + + + + + + '); + $form = new Form($dom->getElementsByTagName('form')->item(0), 'http://example.com'); + + $this->assertInstanceOf('Symfony\Component\DomCrawler\Field\ChoiceFormField', $form->get('option')); + } + + protected function getFormFieldMock($name, $value = null) + { + $field = $this + ->getMockBuilder('Symfony\\Component\\DomCrawler\\Field\\FormField') + ->setMethods(array('getName', 'getValue', 'setValue', 'initialize')) + ->disableOriginalConstructor() + ->getMock() + ; + + $field + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)) + ; + + $field + ->expects($this->any()) + ->method('getValue') + ->will($this->returnValue($value)) + ; + + return $field; + } + + protected function createForm($form, $method = null, $currentUri = null) + { + $dom = new \DOMDocument(); + $dom->loadHTML(''.$form.''); + + $xPath = new \DOMXPath($dom); + $nodes = $xPath->query('//input | //button'); + + if (null === $currentUri) { + $currentUri = 'http://example.com/'; + } + + return new Form($nodes->item($nodes->length - 1), $currentUri, $method); + } + + protected function createTestHtml5Form() + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + +

    Hello form

    +
    +
    + +
    + + + + +
    +
    +
    + + + +
    + +
    + +
    +
    +
    + + +
    + + + +
    +
    +
    + + + + +