diff --git a/README.md b/README.md deleted file mode 100644 index 8844cb4..0000000 --- a/README.md +++ /dev/null @@ -1,50 +0,0 @@ -## **Требования к верстке** - -### **С точки зрения веб-разработки:** -* верстаем на чистом HTML/CSS с использованием flex и grid (без подключение библиотек типа bootstrap и т.п.) -* при именовании блоков используем методологию **БЭМ в стиле "Two Dashes"**: - `block-name__elem-name--mod-name--mod-val` - + имена записываются латиницей в нижнем регистре - + для разделения слов в именах БЭМ-сущностей используется дефис (-) - + имя элемента отделяется от имени блока двумя подчеркиваниями (__) - + модификаторы отделяются от имения блока или элемента двумя дефисами (--) - + значение модификатора отделяется от его имени двумя дефисами (--) -* из конечного HTML-файла убрать все комментарии, если такие имеются -* все медиазапросы пишутся в отдельных файлах (которые мы подготовили) для того чтобы оптимизировать скорость загрузки; если используется препроцессор, то не нужно в классе каждого блока использовать импорты медиа запросов, а наоборот выносим пример можно посмотреть на ["видео"](https://www.youtube.com/watch?v=9uaENbRyVT4) -* **ВАЖНО:** если верстка делается для многостраничного сайта (особенно на CMS), то для всех страниц прописывать уникальный класс в \, от которого строить все CSS-правила на этой странице + для кажой станицы использовать свои отдельные CSS-файлы - - -### **С точки зрения SEO:** -* все теги использовать строго по назначению! Текстовые (strong, b, i, em, h1-h6… p) используем только в текстовых блоках и заголовках. Для дизайна используем div и span. Например не нужно втыкать в подвал, шапку или формы захвата h4 или закрывать просто крупный текст, который не является заголовком в h2. -* на странице должен быть только один заголовок первого уровня (h1) -* должны присутствовать все основные теги и атрибуты — html, head, body, title, description, lang=ru, content=html, charset=utf8 -* для кнопок, нажатие на которые не требует редиректа используем тег \, а не \ -* для \ самостоятельно проставляем атрибут alt="", со значением описывающим того что изображено на картинке - - -## **Порядок работы** - -### **Подготовка окружения для локальной разработки** -1. Выкачивать локально на свой компьютер шаблон командой: `git clone https://git.good-production.xyz/Good-Production/template-for-verstka.git` -2. Далее создать свою ветку командой: `git checkout -b ${project_name}--${name}` -3. Приступить к локальной разработке в созданной ветке -4. Для демонстрации работы пушить свои комиты в созданную ветку командой: `git push origin ${project_name}--${name}` - - во время push для авторизации понадобиться логин/пароль: `freelancer/freelancerfreelancer00` -5. Отписать в чат, чтоб руководитель проектом посмотрел результат и выкачал его на тестовый стенд - -в командах заменить переменные: -`${project_name}` -- условное названия проекта (как варинат использовать домен сайта) -`${name}` -- имя/ник исполнителя - - -## **Оптимальный порядок непосредственно в процессе верстки** -1. Сначала определить CSS-переменные для основных цветов/градиентов, начертаний шрифтов (толщина, размер, модификация, межтрочный интервал) и занести в специально подгтовленный CSS-файл `style-core.css` -2. Все шрифты выкачать и сохранить локально в директорию `/assets/fonts/` и подключить их в файле `style-core.css` -3. Далее начинать стоит с верстки UI kit (должен быть предоставлен в макете дизайнером), для того чтобы переиспользовать готовые элементы по ходу верстки макетов. Стили для UI kit писать так же в файл `style-core.css`. -Саму HTML-верстку для UI kit делать в файле `ui_kit.html` -3. Как UI kit будет готов, можно смело переходить к верстке основных листов макета "*.html" и просто использовать готовые классы из `style-core.css` - - -### **Примечания** - -Для работы с Figma рекомендуем использовать плагин ["Inspect Styles"](https://www.figma.com/community/plugin/1254262542670221199) для инспевтирования CSS-свойсв. \ No newline at end of file diff --git a/assets/css/gp-style-core.css b/assets/css/gp-style-core.css index 5f160ef..d7c2d17 100644 --- a/assets/css/gp-style-core.css +++ b/assets/css/gp-style-core.css @@ -1,16 +1,149 @@ -/* Переменные, шрифты, UI kit */ +@import url('https://fonts.googleapis.com/css2?family=Onest:wght@100..900&display=swap'); +@font-face { + font-family: 'DIN Pro'; + src: url('../fonts/DINPro-Medium.eot'); + src: local('DIN Pro Medium'), local('DINPro-Medium'), + url('../fonts/DINPro-Medium.eot?#iefix') format('embedded-opentype'), + url('../fonts/DINPro-Medium.woff') format('woff'), + url('../fonts/DINPro-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'DIN Pro'; + src: url('../fonts/DINPro-Black.eot'); + src: local('DIN Pro Black'), local('DINPro-Black'), + url('../fonts/DINPro-Black.eot?#iefix') format('embedded-opentype'), + url('../fonts/DINPro-Black.woff') format('woff'), + url('../fonts/DINPro-Black.ttf') format('truetype'); + font-weight: 900; + font-style: normal; + font-display: swap; +} - - -/* - -ШАБЛОН использования глобальных переменных: - -:root { - --main-text: #e1667c; - --main-color: #8da6cb; - --font-family: "Craftwork Grotesk", sans-serif; -} - -*/ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/assets/css/gp-style-desktop.css b/assets/css/gp-style-desktop.css index 631a1a2..c9fae78 100644 --- a/assets/css/gp-style-desktop.css +++ b/assets/css/gp-style-desktop.css @@ -1,18 +1,868 @@ -/* Основные стили для компьютера */ +:root { + --clr-general: #141210; +} +* { + box-sizing: border-box; +} +html { + overflow-x: hidden; +} +body { + font-family: 'Onest', sans-serif; + font-weight: 400; + font-size: 14px; + color: var(--clr-general); + background: #fffaf7; + overflow-x: hidden; +} +*[class*='__container'] { + width: 100%; + max-width: 1328px; + margin: 0 auto; + padding: 0 64px; +} -/* писать сюда... */ +.flex { + display: flex; +} +.flex-ac { + align-items: center; +} +.flex-jcsb { + justify-content: space-between; +} +.gap-10 { + gap: 10px; +} +.gap-24 { + gap: 24px; +} +.gap-42 { + gap: 42px; +} +/* Title */ +h1 { + font-family: 'DIN Pro', sans-serif; + font-weight: 500; + font-size: 62px; + line-height: 110%; + letter-spacing: -0.04em; + text-transform: uppercase; + background: linear-gradient( + 142deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.15) 32%, + rgba(255, 255, 255, 0) 100% + ), + var(--clr-general); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} +h1 b, +h1 strong { + font-weight: 900; +} +h1 span { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0px 16px; + letter-spacing: -0.02em; + border-radius: 14px; + background: linear-gradient( + 142deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.15) 41.5%, + rgba(255, 255, 255, 0) 100% + ), + #ff875c; + font-weight: 600; + font-size: 54px; + line-height: 135%; + color: #fff; + position: relative; + background-clip: initial; + -webkit-background-clip: initial; + -webkit-text-fill-color: initial; + margin-top: 6px; +} +/* Button */ +.btn { + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 12px 24px 0 rgba(255, 204, 0, 0.25); + background: linear-gradient( + 322deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.32) 41.5%, + rgba(255, 255, 255, 0) 100% + ), + #fc0; + border: none; + outline: none; + font-weight: 600; + font-size: 15px; + line-height: 140%; + color: var(--clr-general); + cursor: pointer; + transition: all 0.15s ease-in-out; +} +.btn:hover { + box-shadow: 0 1px 14px 0 rgba(255, 204, 0, 0.2); + transform: scale(0.98); +} +.btn-small { + border-radius: 14px; + padding: 17px 24px; +} +.btn-big { + height: 72px; + font-size: 16px; + border-radius: 20px; + padding: 20px 24px; +} +/* Header */ +.header { + display: flex; + justify-content: center; + width: 100%; +} +.header__container { + position: absolute; + top: 24px; + z-index: 2; +} +.header__logo { + max-width: 280px; +} +.header__logo p { + font-weight: 400; + font-size: 14px; + line-height: 130%; + opacity: 0.8; +} +.header__contacts { +} +.header__socials { +} +.header__socials-link { + display: flex; + align-items: center; + justify-content: center; + width: 47px; + height: 47px; + border-radius: 9px; + box-shadow: 0 10px 32px 0 rgba(0, 0, 0, 0.07); + background: #fff; + transition: transform 0.2s ease-in-out; +} +.header__socials-link:hover { + transform: scale(1.1); +} +.header__tel { + font-weight: 700; + font-size: 22px; + letter-spacing: -0.03em; + color: var(--clr-general); + text-decoration: none; + transition: opacity 0.2s ease-in-out; +} +.header__tel:hover { + opacity: 0.7; +} +/* Hero */ +.hero { + padding-top: 160px; + padding-bottom: 92px; + position: relative; + z-index: 1; +} +.hero::after { + content: ''; + width: 100%; + height: 100%; + background: url('../img/bg_after.webp') top left no-repeat; + background-size: contain; + position: absolute; + bottom: 0; + left: 0; + z-index: -1; +} +.hero::before { + content: ''; + width: 220px; + height: 295px; + background: url('../img/bg_before.webp') center no-repeat; + background-size: contain; + position: absolute; + bottom: 100px; + left: 0; + z-index: 1; + animation: herobefore 6s ease-in-out infinite; +} +@keyframes herobefore { + 0%, + 100% { + transform: translateY(0) translateX(0); + } + 40%, + 60% { + transform: translateY(20px) translateX(-2px); + } +} +.hero__img { +} +.hero__content { + display: flex; + align-items: flex-start; + gap: 64px; + justify-content: space-between; + position: relative; + z-index: 1; +} +.hero__content::after { + content: ''; + position: absolute; + top: -64px; + right: -240px; + width: 880px; + height: 830px; + pointer-events: none; + z-index: -1; + background: url('../img/hero-img.webp') center no-repeat; + background-size: cover; +} +.hero__leftside { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 42px; + max-width: 590px; +} +.hero__title { +} +.hero__title h1 { +} +.hero__desc { + display: inline-flex; + align-items: center; + gap: 12px; +} +.hero__desc p { + display: inline-flex; + align-items: center; + gap: 12px; + font-weight: 500; + font-size: 18px; + line-height: 130%; + padding: 12px 20px 12px 18px; + border-radius: 12px; + border: 1px solid rgba(0, 7, 30, 0.15); + box-shadow: 0 10px 32px 0 rgba(0, 0, 0, 0.01); +} +.hero__desc p::before { + display: none; + content: ''; + width: 24px; + height: 24px; + background: url('../img/i-info.svg') center no-repeat; + background-size: cover; +} +.hero__desc p b { + font-weight: 700; +} +.hero__price { + display: flex; + flex-direction: column; + gap: 24px; + border-radius: 28px; + padding: 6px; + box-shadow: 0 10px 32px 0 rgba(0, 0, 0, 0.02); + background: #fff; +} +.hero__price-header { + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + width: 100%; + margin-bottom: 4px; +} +.hero__price-header span { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + border-radius: 14px; + padding: 14px; + font-weight: 600; + font-size: 18px; + line-height: 140%; + color: #fff; + width: 100%; + border-right: 3px solid #54596c; + border-left: 3px solid #54596c; + background: linear-gradient( + 329deg, + rgba(255, 255, 255, 0) 9.59%, + rgba(255, 255, 255, 0.1) 47.6%, + rgba(255, 255, 255, 0) 100% + ), + var(--clr-general); +} +.hero__price-header span::before { + content: ''; + width: 22px; + height: 22px; + background: url('../img/i-percent.svg') center no-repeat; + background-size: cover; +} +.hero__price-header p { + font-weight: 500; + font-size: 16.5px; + line-height: 130%; +} +.hero__price-header p b { + font-weight: 600; + color: #ffcc02; +} +.hero__price-content { + display: flex; + align-items: center; + justify-content: center; + gap: 32px; +} +.hero__price-stock { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; +} +.hero__price-stock span { + font-weight: 500; + font-size: 20px; + line-height: 140%; + letter-spacing: -0.02em; + text-decoration: line-through; + text-align: right; + opacity: 0.4; +} +.hero__price-stock p { + font-weight: 700; + font-size: 30px; + line-height: 140%; + letter-spacing: -0.02em; +} +.hero__price-unit { + border-radius: 10px; + padding: 7px 14px; + background: linear-gradient( + 322deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.34) 41.5%, + rgba(255, 255, 255, 0) 100% + ), + #fc0; +} +.hero__price-unit p { + font-weight: 500; + font-size: 18px; + line-height: 140%; +} +.hero__rightside { +} +.hero__form { + display: flex; + flex-direction: column; + gap: 24px; + max-width: 490px; + border: 1px solid rgba(255, 255, 255, 0.7); + border-radius: 26px; + padding: 12px; + padding-bottom: 32px; + box-shadow: 0 10px 42px 0 rgba(0, 0, 0, 0.17); + background: linear-gradient( + 142deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.2) 41.5%, + rgba(255, 255, 255, 0) 100% + ), + #f2f2f2; +} +.hero__form-title { + padding-left: 12px; + padding-right: 12px; + max-width: 320px; + margin-left: auto; + margin-right: auto; +} +.hero__form-title p { + font-weight: 500; + font-size: 17.5px; + line-height: 130%; + letter-spacing: -0.02em; + text-align: center; +} +.hero__form-title p b { + font-weight: 600; +} +.hero__form-action { + padding-left: 24px; + padding-right: 24px; +} +.hero__form-action form { + display: flex; + flex-direction: column; + gap: 12px; +} +.hero__form-action select, +.hero__form-action input { + width: 100%; + height: 72px; + border: none; + outline: none; + border-radius: 20px; + padding: 12px 32px; + background: #fff; + font-family: 'Onest', sans-serif; + font-weight: 500; + font-size: 16px; + color: var(--clr-general); + transition: color 0.15s ease-in-out; +} +.hero__form-action input:hover::placeholder, +.hero__form-action select:hover { + color: rgba(28, 27, 27, 0.75); +} +.hero__form-action select { + color: rgba(28, 27, 27, 0.5); + -webkit-appearance: none; + -moz-appearance: none; + text-indent: 1px; + text-overflow: ''; + position: relative; + background: url('../img/i-select.svg') center no-repeat #fff; + background-size: 18px; + background-position-x: calc(100% - 32px); + cursor: pointer; +} +.hero__form-action select::-ms-expand { + display: none; +} +.hero__form-action select:has(option[value='']:not(:checked)) { + color: var(--clr-general); +} +.hero__form-action input::placeholder { + color: rgba(28, 27, 27, 0.5); + transition: color 0.15s ease-in-out; +} +.hero__form-gift { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 10px; + margin-top: 8px; + border-radius: 20px; + padding: 24px 12px 24px 24px; + background: linear-gradient( + 130deg, + rgba(255, 255, 255, 0) 30%, + rgba(255, 255, 255, 0.24) 80%, + rgba(255, 255, 255, 0) 100% + ), + var(--clr-general); + position: relative; + z-index: 1; +} +.hero__form-gift::after { + content: ''; + width: 150px; + height: 105px; + z-index: -1; + pointer-events: none; + background: url('../img/gift.webp') center no-repeat; + background-size: cover; + position: absolute; + bottom: 0; + right: 0; +} +.hero__form-gift p { + display: inline-flex; + border-radius: 90px; + padding: 4px 12px; + background: rgba(255, 255, 255, 0.12); + font-weight: 500; + font-size: 16px; + line-height: 140%; + text-align: center; + color: #fff; +} +.hero__form-gift ul { + list-style-type: disc; + padding-left: 24px; +} +.hero__form-gift ul li { + list-style-type: disc; + font-weight: 600; + font-size: 16px; + line-height: 140%; + color: #fff; +} +.hero__advantages { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 24px; + margin-top: 92px; + margin-top: 72px; + position: relative; + z-index: 1; +} +.hero__advantages-item { + display: flex; + align-items: center; + justify-content: flex-start; + flex-direction: column; + gap: 20px; + border-radius: 24px; + padding: 24px 14px; + box-shadow: 0 10px 32px 0 rgba(0, 0, 0, 0.01); + background: #fff; +} +.hero__advantages-item p { + font-weight: 500; + font-size: 16px; + line-height: 140%; + text-align: center; +} +.hero__advantages-icon { + display: flex; + align-items: center; + justify-content: center; + border-radius: 20px; + min-width: 60px; + width: 60px; + height: 60px; + background: linear-gradient( + 329deg, + rgba(255, 255, 255, 0) 9.59%, + rgba(255, 255, 255, 0.1) 47.6%, + rgba(255, 255, 255, 0) 100% + ), + var(--clr-general); +} +/* Footer */ +.footer { + background: linear-gradient( + 329deg, + rgba(255, 255, 255, 0) 9.59%, + rgba(255, 255, 255, 0.02) 47.6%, + rgba(255, 255, 255, 0) 100% + ), + var(--clr-general); + padding-top: 42px; + padding-bottom: 52px; + color: #fff; +} +.footer__container { + display: flex; + flex-direction: column; + gap: 42px; +} +.footer__top { + display: grid; + align-items: center; + grid-template-columns: repeat(3, 1fr); +} +.footer__logo { +} +.footer__logo p { + font-weight: 500; + font-size: 13px; + line-height: 140%; + max-width: 210px; +} +.footer__address { + font-weight: 500; + font-size: 16px; + text-align: center; +} +.footer__contacts { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 46px; +} +.footer__tel { + font-weight: 700; + font-size: 22px; + color: #fff; + text-decoration: none; + transition: color 0.15s ease-in-out; +} +.footer__tel:hover { + color: #fc0; +} +.footer__socials { + display: flex; + align-items: center; + gap: 10px; +} +.footer__socials-link { + display: flex; + align-items: center; + justify-content: center; + border-radius: 9px; + padding: 10px; + width: 44px; + height: 44px; + box-shadow: 0 10px 32px 0 rgba(0, 0, 0, 0.07); + background: rgba(255, 255, 255, 0.1); + transition: transform 0.2s ease-in-out; +} +.footer__socials-link:hover { + transform: scale(1.1); +} +.footer__bottom { + display: grid; + align-items: center; + grid-template-columns: repeat(3, 1fr); +} +.footer__bottom p, +.footer__bottom a { + font-weight: 500; + font-size: 14px; + color: #fff; + text-decoration: none; +} +.footer__bottom a { + transition: opacity 0.2s ease-in-out; +} +.footer__bottom a:hover { + opacity: 0.5; +} +.footer__bottom a:nth-child(2) { + text-align: center; +} +.footer__bottom a:nth-child(3) { + text-align: right; +} +/* Preloader */ +.preloader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 7, 30, 0.8); + -webkit-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + opacity: 1; + z-index: 200; + pointer-events: none; +} +.preloader.hidden { + opacity: 0; +} -/* Стили для лептопов */ -/* @media only screen and (min-width: 992px) and (max-width: 1400px) { +.preloader.hidden .preloader__icon { + opacity: 0; +} -} */ \ No newline at end of file +.preloader__icon { + position: absolute; + top: 50%; + left: 50%; + width: 128px; + height: 128px; + background: url('data:image/svg+xml,'); + background-size: contain; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + opacity: 1; + -webkit-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; +} + +/* Modal */ +#modal__bg { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 7, 30, 0.8); + z-index: 155; + opacity: 0; +} +.modal { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 255; + padding: 42px; + background: #fff; + border-radius: 32px; + opacity: 0; +} +.modal.opened, +#modal__bg.opened { + display: flex; + opacity: 1; +} +.modal__close { + position: absolute; + top: -32px; + right: -32px; + width: 42px; + height: 42px; + background: url('../img/i-close.svg') center no-repeat; + background-size: 20px; + border-radius: 12px; + cursor: pointer; + transition: opacity 0.15s ease-in-out; +} +.modal__close:hover { + opacity: 0.5; +} +.modal__content { + position: relative; + font-size: 16px; + line-height: 145%; +} +.modal__content p { +} +.modal__content span { + display: inline-flex; + font-size: 18px; + font-weight: 600; + margin-bottom: 14px; +} +.modal__title { + max-width: 420px; + margin-left: auto; + margin-right: auto; + margin-bottom: 24px; +} +.modal__title p { + font-family: 'DIN Pro', sans-serif; + font-size: 26px; + font-weight: 500; + line-height: 130%; + text-align: center; + letter-spacing: -0.04em; + text-transform: uppercase; + background: linear-gradient( + 142deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.22) 42%, + rgba(255, 255, 255, 0) 100% + ), + var(--clr-general); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} +.modal__title p b, +.modal__title p strong { + font-weight: 600; +} +.modal__form { +} +.modal__form form { + display: flex; + flex-direction: column; + gap: 12px; + width: 100%; +} +.modal__form form input { + width: 100%; + height: 72px; + border: none; + outline: none; + border-radius: 20px; + padding: 12px 32px; + background: linear-gradient( + 142deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.2) 41.5%, + rgba(255, 255, 255, 0) 100% + ), + #f2f2f2; + font-family: 'Onest', sans-serif; + font-weight: 500; + font-size: 16px; + color: var(--clr-general); + transition: color 0.25s ease-in-out; +} +.modal__form form input::placeholder { + color: rgba(28, 27, 27, 0.5); + transition: color 0.15s ease-in-out; +} +.modal__form form input:hover::placeholder { + color: rgba(28, 27, 27, 0.8); +} + +/* Form */ +form.sending { + position: relative; +} +form.sending::before, +form.sending::after { + content: ''; + position: absolute; +} +form.sending::after { + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(245 245 245 / 60%); + z-index: 10; +} +section.modal form.sending::after { + background: rgb(255 255 255 / 60%); +} +form::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 128px; + height: 128px; + background: url('data:image/svg+xml,'); + background-size: contain; + transform: translate(-50%, -50%); + opacity: 0; + pointer-events: none; +} +form.sending::before { + opacity: 1; + z-index: 15; +} + +@media screen and (max-width: 1024px) and (min-width: 992px) { + .hero__content { + flex-direction: column; + } + .hero__content::after { + opacity: 0.2; + } + .footer__socials { + display: none; + } +} diff --git a/assets/css/gp-style-mobile.css b/assets/css/gp-style-mobile.css index 0cf51d3..4cb6aa6 100644 --- a/assets/css/gp-style-mobile.css +++ b/assets/css/gp-style-mobile.css @@ -1,5 +1,247 @@ -/* Стили для мобильных устройств */ -@media only screen and (max-width: 576px) { - - -} \ No newline at end of file +@media screen and (max-width: 576px) { + .hero__rightside { + width: 100%; + } + .hero__price { + margin-top: 24px; + } + .hero__content::after { + right: -320px; + top: 290px; + } + .hero__desc { + gap: 6px; + } + .hero__desc p { + font-size: 13px; + padding: 8px 12px; + border-radius: 10px; + } + .hero__desc img { + width: 24px; + height: 24px; + } + .hero__leftside { + gap: 18px; + } + .hero__advantages { + margin-top: 42px; + } +} +@media screen and (max-width: 540px) { + .header__container { + align-items: flex-start; + gap: 24px; + padding-bottom: 20px; + top: 20px; + } + .header__container::after { + content: ''; + position: absolute; + bottom: 0; + left: 24px; + width: calc(100% - 48px); + height: 1px; + background: #e7e7e7; + } + .header__logo { + max-width: 240px; + } + .header__logo img { + width: 50px; + } + .header__logo p { + font-size: 9px; + } + .header__socials { + gap: 6px; + } + .header__contacts { + flex-wrap: wrap; + width: 100%; + justify-content: flex-end; + gap: 6px; + } + .header__contacts .header__tel { + } + .header__tel { + font-size: 20px; + margin: 0; + } + .hero { + padding-top: 110px; + } +} +@media screen and (max-width: 470px) { + h1 { + font-size: 39px; + text-align: left; + } + h1 span { + font-size: 32px; + padding-left: 12px; + padding-right: 12px; + border-radius: 10px; + margin-top: 6px; + } + .hero__leftside { + align-items: flex-start; + } + .hero__price { + margin-top: 12px; + } + .hero__content { + gap: 34px; + } + .hero__content::after { + opacity: 0.6; + right: -125px; + width: 200px; + height: 140px; + background-size: contain; + top: 0px; + } + .hero::before { + opacity: 0.4; + } + .hero__form-gift { + overflow: hidden; + } + .hero__form-gift::after { + right: -10%; + opacity: 0.6; + } + .hero__advantages { + grid-template-columns: 1fr; + gap: 6px; + } + .hero__price { + border-radius: 12px; + padding: 10px; + padding-bottom: 14px; + } + .hero__price-header { + gap: 10px; + padding: 10px; + padding-bottom: 0; + border-radius: 10px; + margin-bottom: 0; + } + .hero__price-header p { + font-size: 13.5px; + } + .hero__price-header span { + border-radius: 10px; + border: none; + padding-left: 2px; + padding-right: 2px; + gap: 7px; + } + .hero__price-header span::before { + min-width: 18px; + width: 18px; + height: 18px; + } + .hero__price-stock p { + font-size: 20px; + } + .hero__price-stock span { + font-size: 15px; + } + .hero__price-unit { + border-radius: 8px; + padding: 6px 12px; + } + .hero__price-unit p { + font-size: 13px; + } + .hero__price-content { + gap: 10px; + } + .header__logo { + width: 100%; + } + .header__container { + align-items: center; + } + .header__tel { + font-size: 18px; + } + .hero__form { + gap: 20px; + padding: 0 0 20px 0; + border-radius: 16px; + } + .hero__form-title p { + font-size: 15px; + } +} +@media screen and (max-width: 390px) { + h1 { + font-size: 36px; + } + h1 span { + font-size: 29px; + } + .header__logo img { + width: 42px; + } + .header__logo p { + font-size: 9px; + } + .header__tel { + font-size: 17px; + } + .hero__form-action select, + .hero__form-action input { + height: 60px; + border-radius: 12px; + } + .hero__price-header p { + font-size: 12.5px; + } +} +@media screen and (max-width: 375px) { + *[class*='__container'] { + padding-left: 12px; + padding-right: 12px; + } + .header__container { + gap: 0; + } + h1 { + font-size: 34px; + } + .hero__desc::before { + display: none; + } + .header__logo img { + width: 39px; + } + .header__tel { + font-size: 16.5px; + } + .hero__form-action select, + .hero__form-action input { + font-size: 13.5px; + } +} +@media screen and (max-width: 360px) { + .hero__desc { + flex-direction: column; + align-items: flex-start; + } + .hero__price-header span { + padding-left: 20px; + padding-right: 20px; + gap: 12px; + } + .hero__content { + gap: 20px; + } + .hero__price-header span::before { + min-width: 20px; + width: 20px; + height: 20px; + } +} diff --git a/assets/css/gp-style-tablet.css b/assets/css/gp-style-tablet.css index 32a3ef6..f9b79bb 100644 --- a/assets/css/gp-style-tablet.css +++ b/assets/css/gp-style-tablet.css @@ -1,5 +1,255 @@ -/* Стили для планшетов */ -@media only screen and (max-width: 992px) { - - -} \ No newline at end of file +*[class*='__container'] { + padding: 0 42px; +} +h1 { + font-size: 52px; +} +h1 span { + font-size: 42px; + margin-top: 0; + top: -2px; +} +.hero__content { + flex-direction: column; + align-items: center; +} +.hero__leftside { + max-width: 100%; + align-items: center; + text-align: center; +} +.hero__content::after { + opacity: 0.1; + right: -140px; +} +.footer__tel { + font-size: 17px; +} +.header__contacts { + gap: 10px; +} +.header__contacts button.btn, +.header__contacts .header__tel p { + display: none; +} +.header__contacts .header__tel { + display: flex; + align-items: center; + justify-content: center; + width: 47px; + height: 47px; + border-radius: 9px; + position: relative; + padding: 0; + background: linear-gradient( + 322deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.32) 41.5%, + rgba(255, 255, 255, 0) 100% + ), + #fc0; +} +.header__contacts .header__tel::before { + content: ''; + width: 24px; + height: 24px; + background: url('../img/i-tel.svg') center no-repeat; + background-size: cover; +} +.hero__advantages { + grid-template-columns: repeat(2, 1fr); +} +.hero__advantages-item { + flex-direction: row; +} +.hero__advantages-item p { + text-align: left; +} +.hero__advantages-item p br { + content: ''; + padding: 0; +} +.footer__logo { + flex-direction: column; + text-align: center; +} +.footer__tel { + font-size: 19px; +} +.footer__contacts { + gap: 24px; +} +.footer__top, +.footer__bottom { + display: flex; + flex-direction: column; + gap: 32px; +} +.footer__bottom { + gap: 12px; +} +.footer__bottom p { + padding-bottom: 32px; +} +@media screen and (max-width: 740px) { + .header__logo { + max-width: 200px; + gap: 12px; + } + .header__logo img { + width: 64px; + height: auto; + } + .header__logo p { + font-size: 11px; + } +} +@media screen and (max-width: 670px) { + .header__socials-link, + .footer__socials-link, + .header__contacts .header__tel { + width: 40px; + height: 40px; + } + .header__socials-link img, + .footer__socials-link img { + width: 24px; + height: 24px; + } + .header__contacts .header__tel::before { + width: 20px; + height: 20px; + } + .header__tel, + .footer__tel { + font-size: 18px; + } + h1 { + font-size: 49px; + } + h1 span { + font-size: 39px; + top: 0; + } +} +@media screen and (max-width: 620px) { + *[class*='__container'] { + padding: 0 24px; + } + .header__logo { + max-width: 140px; + gap: 14px; + } + .header__logo img { + width: 56px; + } + .hero__advantages { + gap: 16px; + } + .hero__advantages-item { + gap: 14px; + padding: 16px 14px; + border-radius: 16px; + } + .hero__advantages-icon { + min-width: 46px; + width: 46px; + height: 46px; + border-radius: 10px; + } + .hero__advantages-icon img { + width: 30px; + height: 30px; + } + .hero__advantages-item p { + font-size: 14px; + } + h1 { + font-size: 42px; + } + h1 span { + font-size: 32px; + } + .header__socials-link, + .footer__socials-link, + .header__contacts .header__tel { + width: 37px; + height: 37px; + } + .header__socials-link img, + .footer__socials-link img { + width: 22px; + height: 22px; + } + .header__contacts .header__tel::before { + width: 18px; + height: 18px; + } + .hero { + padding-top: 140px; + padding-bottom: 64px; + } + .hero__leftside { + gap: 24px; + } + .hero__desc::before { + width: 20px; + height: 20px; + } + .hero__desc p { + font-size: 16px; + } + .hero__price { + gap: 12px; + margin-right: auto; + } + .hero__price-header, + .hero__price-content { + flex-direction: column; + gap: 16px; + } + .hero__price-stock { + align-items: center; + text-align: center; + } + .hero__price-header p { + font-size: 16px; + } + .hero__price-stock span { + font-size: 18px; + } + .hero__price-stock p { + font-size: 24px; + } + .hero__price-unit { + } + .hero__price-unit p { + font-size: 16px; + } + .hero__content::after { + opacity: 1; + right: -290px; + width: 620px; + height: 420px; + background-size: contain; + top: 230px; + } + .hero__form-title p { + font-size: 16px; + max-width: 390px; + margin-left: auto; + margin-right: auto; + } + .hero__form-action select, + .hero__form-action input, + .btn-big { + font-size: 15px; + } + .hero__form-gift p, + .hero__form-gift ul li { + font-size: 14px; + } + .hero__advantages { + margin-top: 64px; + } +} diff --git a/assets/css/gp-style-ultra.css b/assets/css/gp-style-ultra.css deleted file mode 100644 index c149baa..0000000 --- a/assets/css/gp-style-ultra.css +++ /dev/null @@ -1,4 +0,0 @@ -/* Стили для ультрашироких экранов */ -@media only screen and (min-width: 1400px) { - -} \ No newline at end of file diff --git a/assets/fonts/DINPro-Black.eot b/assets/fonts/DINPro-Black.eot new file mode 100644 index 0000000..1e1f6d8 Binary files /dev/null and b/assets/fonts/DINPro-Black.eot differ diff --git a/assets/fonts/DINPro-Black.ttf b/assets/fonts/DINPro-Black.ttf new file mode 100644 index 0000000..2e89480 Binary files /dev/null and b/assets/fonts/DINPro-Black.ttf differ diff --git a/assets/fonts/DINPro-Black.woff b/assets/fonts/DINPro-Black.woff new file mode 100644 index 0000000..17158ce Binary files /dev/null and b/assets/fonts/DINPro-Black.woff differ diff --git a/assets/fonts/DINPro-Medium.eot b/assets/fonts/DINPro-Medium.eot new file mode 100644 index 0000000..96085ae Binary files /dev/null and b/assets/fonts/DINPro-Medium.eot differ diff --git a/assets/fonts/DINPro-Medium.ttf b/assets/fonts/DINPro-Medium.ttf new file mode 100644 index 0000000..25c87b7 Binary files /dev/null and b/assets/fonts/DINPro-Medium.ttf differ diff --git a/assets/fonts/DINPro-Medium.woff b/assets/fonts/DINPro-Medium.woff new file mode 100644 index 0000000..fc04073 Binary files /dev/null and b/assets/fonts/DINPro-Medium.woff differ diff --git a/assets/img/advantages_icon1.svg b/assets/img/advantages_icon1.svg new file mode 100644 index 0000000..09330fb --- /dev/null +++ b/assets/img/advantages_icon1.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/advantages_icon2.svg b/assets/img/advantages_icon2.svg new file mode 100644 index 0000000..c272a00 --- /dev/null +++ b/assets/img/advantages_icon2.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/advantages_icon3.svg b/assets/img/advantages_icon3.svg new file mode 100644 index 0000000..3b232ae --- /dev/null +++ b/assets/img/advantages_icon3.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/advantages_icon4.svg b/assets/img/advantages_icon4.svg new file mode 100644 index 0000000..bd4e983 --- /dev/null +++ b/assets/img/advantages_icon4.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/img/bg_after.webp b/assets/img/bg_after.webp new file mode 100644 index 0000000..2af7dde Binary files /dev/null and b/assets/img/bg_after.webp differ diff --git a/assets/img/bg_before.webp b/assets/img/bg_before.webp new file mode 100644 index 0000000..af59cf8 Binary files /dev/null and b/assets/img/bg_before.webp differ diff --git a/assets/img/favicon.ico b/assets/img/favicon.ico deleted file mode 100644 index 5517fde..0000000 Binary files a/assets/img/favicon.ico and /dev/null differ diff --git a/assets/img/footer-logo.webp b/assets/img/footer-logo.webp new file mode 100644 index 0000000..23a1d8c Binary files /dev/null and b/assets/img/footer-logo.webp differ diff --git a/assets/img/hero-img.webp b/assets/img/hero-img.webp new file mode 100644 index 0000000..8e82e46 Binary files /dev/null and b/assets/img/hero-img.webp differ diff --git a/assets/img/i-close.svg b/assets/img/i-close.svg new file mode 100644 index 0000000..ca3cfdc --- /dev/null +++ b/assets/img/i-close.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/img/i-delivery.svg b/assets/img/i-delivery.svg new file mode 100644 index 0000000..91556cc --- /dev/null +++ b/assets/img/i-delivery.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/i-info.svg b/assets/img/i-info.svg new file mode 100644 index 0000000..fc5671f --- /dev/null +++ b/assets/img/i-info.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/img/i-percent.svg b/assets/img/i-percent.svg new file mode 100644 index 0000000..35002fb --- /dev/null +++ b/assets/img/i-percent.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/img/i-select.svg b/assets/img/i-select.svg new file mode 100644 index 0000000..dac577f --- /dev/null +++ b/assets/img/i-select.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/i-tel.svg b/assets/img/i-tel.svg new file mode 100644 index 0000000..6483da2 --- /dev/null +++ b/assets/img/i-tel.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/i-volume.svg b/assets/img/i-volume.svg new file mode 100644 index 0000000..1f75730 --- /dev/null +++ b/assets/img/i-volume.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/img/logo.webp b/assets/img/logo.webp new file mode 100644 index 0000000..1f1374e Binary files /dev/null and b/assets/img/logo.webp differ diff --git a/assets/img/tg-white.svg b/assets/img/tg-white.svg new file mode 100644 index 0000000..7350f11 --- /dev/null +++ b/assets/img/tg-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/tg.svg b/assets/img/tg.svg new file mode 100644 index 0000000..aa66c0f --- /dev/null +++ b/assets/img/tg.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/wh-white.svg b/assets/img/wh-white.svg new file mode 100644 index 0000000..8fc0eb6 --- /dev/null +++ b/assets/img/wh-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/wh.svg b/assets/img/wh.svg new file mode 100644 index 0000000..bf9e5d9 --- /dev/null +++ b/assets/img/wh.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/js/jquery.maskedinput.min.js b/assets/js/jquery.maskedinput.min.js new file mode 100644 index 0000000..fe6370e --- /dev/null +++ b/assets/js/jquery.maskedinput.min.js @@ -0,0 +1,8 @@ +/** + * Minified by jsDelivr using UglifyJS v3.4.4. + * Original file: /npm/jquery.maskedinput@1.4.1/src/jquery.maskedinput.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?e(require("jquery")):e(jQuery)}(function(R){var a,e=navigator.userAgent,S=/iphone/i.test(e),i=/chrome/i.test(e),T=/android/i.test(e);R.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},autoclear:!0,dataName:"rawMaskFn",placeholder:"_"},R.fn.extend({caret:function(e,t){var n;if(0!==this.length&&!this.is(":hidden")&&this.get(0)===document.activeElement)return"number"==typeof e?(t="number"==typeof t?t:e,this.each(function(){this.setSelectionRange?this.setSelectionRange(e,t):this.createTextRange&&((n=this.createTextRange()).collapse(!0),n.moveEnd("character",t),n.moveStart("character",e),n.select())})):(this[0].setSelectionRange?(e=this[0].selectionStart,t=this[0].selectionEnd):document.selection&&document.selection.createRange&&(n=document.selection.createRange(),e=0-n.duplicate().moveStart("character",-1e5),t=e+n.text.length),{begin:e,end:t})},unmask:function(){return this.trigger("unmask")},mask:function(t,v){var n,b,k,y,x,j,A;if(!t&&0i.length){m(t+1,j);break}}else c[t]===i.charAt(a)&&a++,tt.length){for(p(!0);0 - - - - - SEO title - - - - - - - - - - + + + + + Кирпич напрямую от производителя в Томске | Вектор + + + - - - - + + - - - - - - - - - - - -
-
- - + +
+
+
+
+
+
+ +
+ + +

+7 (923) 435 25 57

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

Кирпич в Томске напрямую от производителя от 11 руб./шт.

+
+
+

+ Любой объем + Любой объем +

+

+ Доставим за 2 дня + Доставим за 2 дня +

+
+
+
+
+
+ +

Только до 30.09 розница по оптовым ценам

+
+
+
+

Оставьте заявку, чтобы узнать цену и зафиксировать скидку

+
+
+ + + + + +
+
+
+
+
+
+
+ Гарантия 50 лет
-
- - +

Гарантия 50 лет

+
+
+
+ Выгрузка кран/манипулятор
- +

Выгрузка с помощью крана

+
+
+
+ Доставка от 1го дня по Томску и области до вашего объекта +
+

Соответствует ГОСТ

+
+
+
+ Большой ассортимент всегда в наличии +
+

Большой ассортимент всегда в наличии

+
+
+
+
+
+ + + + + + + + + - - - \ No newline at end of file diff --git a/send-telegram.php b/send-telegram.php deleted file mode 100644 index 285ed9a..0000000 --- a/send-telegram.php +++ /dev/null @@ -1,38 +0,0 @@ - $name, - 'Телефон:' => $phone - ); - - //Настраиваем внешний вид сообщения в телеграме - foreach($arr as $key => $value) { - $txt .= "".$key." ".$value."%0A"; - }; - - //Передаем данные боту - $sendToTelegram = fopen("https://api.telegram.org/bot{$token}/sendMessage?chat_id={$chat_id}&parse_mode=html&text={$txt}","r"); - - //Выводим сообщение об успешной отправке - if ($sendToTelegram) { - echo 'Спасибо! Ваша заявка принята. Мы свяжемся с вами в ближайшее время.'; - }else{ - echo 'Что-то пошло не так. Попробуйте отправить форму ещё раз.'; - } -} - -?> \ No newline at end of file diff --git a/send/ajax.js b/send/ajax.js new file mode 100644 index 0000000..38ef8d1 --- /dev/null +++ b/send/ajax.js @@ -0,0 +1,29 @@ +jQuery(document).ready(function () { + jQuery('form').submit(function () { + var formID = jQuery(this).attr('id') + var formNm = jQuery('#' + formID) + formNm.addClass('sending') + jQuery.ajax({ + type: 'POST', + url: 'send/send.php', + data: formNm.serialize(), + dataType: 'json', + success: function (data, jqXHR) { + if (data.result == 'success') { + setTimeout(() => { + formNm.removeClass('sending') + jQuery('section.modal.opened').removeClass('opened') + jQuery('#modal__bg').addClass('opened') + jQuery('section.modal[data-id="success"]').addClass('opened') + }, 1000) + } + }, + error: function (jqXHR, text, error) { + console.log('ошибка') + console.log(jqXHR) + console.log(error) + }, + }) + return false + }) +}) diff --git a/send/phpmailer/Exception.php b/send/phpmailer/Exception.php new file mode 100644 index 0000000..52eaf95 --- /dev/null +++ b/send/phpmailer/Exception.php @@ -0,0 +1,40 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "
\n"; + } +} diff --git a/send/phpmailer/PHPMailer.php b/send/phpmailer/PHPMailer.php new file mode 100644 index 0000000..7f56ea2 --- /dev/null +++ b/send/phpmailer/PHPMailer.php @@ -0,0 +1,5126 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int|null + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = ''; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = ''; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. + * If not specified, the first one from that list that the server supports will be selected. + * + * @var string + */ + public $AuthType = ''; + + /** + * An implementation of the PHPMailer OAuthTokenProvider interface. + * + * @var OAuthTokenProvider + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * @see SMTP::DEBUG_OFF: No output + * @see SMTP::DEBUG_CLIENT: Client messages + * @see SMTP::DEBUG_SERVER: Client and server messages + * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep the SMTP connection open after each message. + * If this is set to true then the connection will remain open after a send, + * and closing the connection will require an explicit call to smtpClose(). + * It's a good idea to use this if you are sending multiple messages as it reduces overhead. + * See the mailing list example for how to use it. + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + * + * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available text strings for the current language. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.8.1'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * The SMTP standard CRLF line break. + * If you want to change line break format, change static::$LE, not this. + */ + const CRLF = "\r\n"; + + /** + * "Folding White Space" a white space string used for line folding. + */ + const FWS = ' '; + + /** + * SMTP RFC standard line ending; Carriage Return, Line Feed. + * + * @var string + */ + protected static $LE = self::CRLF; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if ((int)ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + $this->edebug('Sending with mail()'); + $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); + $this->edebug("Envelope sender: {$this->Sender}"); + $this->edebug("To: {$to}"); + $this->edebug("Subject: {$subject}"); + $this->edebug("Headers: {$header}"); + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $this->edebug("Additional params: {$params}"); + $result = @mail($to, $subject, $body, $header, $params); + } + $this->edebug('Result: ' . ($result ? 'true' : 'false')); + return $result; + } + + /** + * Output debugging info via a user-defined method. + * Only generates output if debug output is enabled. + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address + * @param string $name An optional username associated with the address + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $pos = false; + if ($address !== null) { + $address = trim($address); + $pos = strrpos($address, '@'); + } + if (false === $pos) { + //At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ($name !== null && is_string($name)) { + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + } else { + $name = ''; + } + $params = [$kind, $address, $name]; + //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + //Domain is assumed to be whatever is after the last @ symbol in the address + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + //Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Set the boundaries to use for delimiting MIME parts. + * If you override this, ensure you set all 3 boundaries to unique values. + * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, + * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 + * + * @return void + */ + public function setBoundaries() + { + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1=_' . $this->uniqueid; + $this->boundary[2] = 'b2=_' . $this->uniqueid; + $this->boundary[3] = 'b3=_' . $this->uniqueid; + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
" into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * @param string $charset The charset to use when decoding the address list string. + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + // Clear any potential IMAP errors to get rid of notices being thrown at end of script. + imap_errors(); + foreach ($list as $address) { + if ( + '.SYNTAX-ERROR.' !== $address->host && + static::validateAddress($address->mailbox . '@' . $address->host) + ) { + //Decode the name part if it's present and encoded + if ( + property_exists($address, 'personal') && + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + defined('MB_CASE_UPPER') && + preg_match('/^=\?.*\?=$/s', $address->personal) + ) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $address->personal = str_replace('_', '=20', $address->personal); + //Decode the name + $address->personal = mb_decode_mimeheader($address->personal); + mb_internal_encoding($origCharset); + } + + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + $name = trim($name); + if (static::validateAddress($email)) { + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + //If this name is encoded, decode it + if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $name = str_replace('_', '=20', $name); + //Decode the name + $name = mb_decode_mimeheader($name); + mb_internal_encoding($origCharset); + } + $addresses[] = [ + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim((string)$address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + //Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ( + (false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 + if (is_callable($patternselect) && !is_string($patternselect)) { + return call_user_func($patternselect, $address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + //Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if ( + !empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + //Convert the domain from whatever charset it's in to UTF-8 + $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + if (defined('INTL_IDNA_VARIANT_UTS46')) { + //Use the current punycode standard (appeared in PHP 7.2) + $punycode = idn_to_ascii( + $domain, + \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | + \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, + \INTL_IDNA_VARIANT_UTS46 + ); + } elseif (defined('INTL_IDNA_VARIANT_2003')) { + //Fall back to this old, deprecated/removed encoding + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); + } else { + //Fall back to a default we don't know about + $punycode = idn_to_ascii($domain, $errorcode); + } + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ( + 'smtp' === $this->Mailer + || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE(self::CRLF); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ( + 'mail' === $this->Mailer + && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) + || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error($this->lang('buggy_php'), E_USER_WARNING); + } + + try { + $this->error_count = 0; //Reset errors + $this->mailHeader = ''; + + //Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + //Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + $this->{$address_kind} = trim($this->{$address_kind}); + if (empty($this->{$address_kind})) { + continue; + } + $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); + if (!static::validateAddress($this->{$address_kind})) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->{$address_kind} + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + //Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + //createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + //To capture the complete message when using mail(), create + //an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + //Sign with DKIM if enabled + if ( + !empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + //Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { + $this->smtp->reset(); + } + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + if ($this->Mailer === 'qmail') { + $this->edebug('Sending with qmail'); + } else { + $this->edebug('Sending with sendmail'); + } + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + if ($this->Mailer === 'qmail') { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } else { + //allow sendmail to choose a default envelope sender. It may + //seem preferable to force it to use the From header as with + //SMTP, but that introduces new problems (see + //), and + //it has historically worked this way. + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $this->edebug('Sendmail path: ' . $this->Sendmail); + $this->edebug('Sendmail command: ' . $sendmail); + $this->edebug('Envelope sender: ' . $this->Sender); + $this->edebug("Headers: {$header}"); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + $this->edebug("To: {$toAddr}"); + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + ($result === 0), + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, + //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, + //so we don't. + if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { + return false; + } + + if ( + escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$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; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!static::isPermittedPath($path)) { + return false; + } + $readable = is_file($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = trim(implode(', ', $toArr)); + + //If there are no To-addresses (e.g. when sending only to BCC-addresses) + //the following should be added to get a correct DKIM-signature. + //Compare with $this->preSend() + if ($to === '') { + $to = 'undisclosed-recipients:;'; + } + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + $result, + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + //Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; + } + } + + //Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [[$cb['to'], $cb['name']]], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + //Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + if ($this->Host === null) { + $this->Host = 'localhost'; + } + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if ( + !preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + ) + ) { + $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); + //Not a valid host entry + continue; + } + //$hostinfo[1]: optional ssl or tls prefix + //$hostinfo[2]: the hostname + //$hostinfo[3]: optional port number + //The host string prefix can temporarily override the current setting for SMTPSecure + //If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[2])) { + $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; //Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[1]) { + $tls = true; + //TLS doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[2]; + $port = $this->Port; + if ( + array_key_exists(3, $hostinfo) && + is_numeric($hostinfo[3]) && + $hostinfo[3] > 0 && + $hostinfo[3] < 65536 + ) { + $port = (int) $hostinfo[3]; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + //* it's not disabled + //* we have openssl extension + //* we are not already using SSL + //* the server offers STARTTLS + if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + //We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ( + $this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + if ($this->exceptions) { + // no exception was thrown, likely $this->smtp->connect() failed + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * Optionally, the language code can be enhanced with a 4-character + * script annotation and/or a 2-character country annotation. + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * Do not set this from user input! + * + * @return bool Returns true if the requested language was loaded, false otherwise. + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + //Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + 'am' => 'hy', + ]; + + if (array_key_exists($langcode, $renamed_langcodes)) { + $langcode = $renamed_langcodes[$langcode]; + } + + //Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'extension_missing' => 'Extension missing: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'invalid_header' => 'Invalid header name or value', + 'invalid_hostentry' => 'Invalid hostentry: ', + 'invalid_host' => 'Invalid host: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_code' => 'SMTP code: ', + 'smtp_code_ex' => 'Additional SMTP info: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_detail' => 'Detail: ', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + ]; + if (empty($lang_path)) { + //Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + + //Validate $langcode + $foundlang = true; + $langcode = strtolower($langcode); + if ( + !preg_match('/^(?P[a-z]{2})(?P