• Как принимать платежи на сайте без ИП?

    @alpeg
    Робин Гуд, сервис работает через тот же апи киви, который ты и сам можешь легко подключить через https://p2p.qiwi.com/ там есть форма, хуки настраиваются элементарно.
    у яндекса слжнее, но тоже можно.

    а сервис про то что "автоматически принимаешь платежи без комиссии " врёт. у них есть платный тариф. это значит, что это вроде бы и без комиссии, но зато сервис "условно бесплатный".

    То есть рано или поздно ты не сможешь принять платёж так как надо заплатить.
  • Как реализовать грамотную авторизацию PHP?

    @alpeg
    не нужна такая запутанная дичь )))

    ubuntu_lts, у вас только одно устройство? никогда не заходили с нескольких в один аккаунт?
    И в вашем случае сессия-то зачем, токен можно в кукисы записать с таким же успехом.
  • Как реализовать грамотную авторизацию PHP?

    @alpeg
    ubuntu_lts, непонятно, зачем тогда вообще сессия, токен можно и в куки записать.
    Кроме этого, это не даст зайти с нескольких устройств, правильно будет делать отдельно таблицу "токен-айди пользователя-время последней активности-время протухания"
  • Как реализовать грамотную авторизацию PHP?

    @alpeg
    Ну а если в сессию записать id пользователя полученный при авторизации, и далее работать только с ним, со стороны безопасности это нормально?

    GoldFox2015, нормально (и хорошо с точки зрения уменьшения запросов к базе), но надо иметь возможность следить за такими пользователями и обрубать их в случае необходимости (ведь сессию при должном умении можно держать вечно).
    Т.е. по хорошему надо в сессию также писать таймаут, по истечении которого скрипт будет лезть в базу и смотреть, не протух ли пользователь и его права доступа, не хочет ли пользователь разлогинить все сессии и т.п.

    Фактически такой подход отличается от "основного" (токены в базе) только тем, что вместо токена хранящегося в базе, токеном служит ид сессии, по которому на сервере хранится связть "ид сессии -> ид пользователя"
  • Ошибка при запуске сервера на React, как исправить?

    @alpeg
    gtomilin,
    Содержит ли вывод set NODE строку NODE_SKIP_PLATFORM_CHECK=1?
    Если нет - плохо поставили переменную (возможно нужна перезагрузка)
    Если да - поставили не ту версию. Нужно найти самую новую тут https://nodejs.org/download/nightly/ (очень похоже на Ваш вариант, так как у новой версии выводит не "This application is only ..." а "Node.js is only ...")
  • Ошибка при запуске сервера на React, как исправить?

    @alpeg
    gtomilin, установщик node, который устанавливается из msi, не установится.
    Для его правильной установки нужно либо ставить всё врчную, что мне не понравилось.
    Вместо этого я сделал вот как:

    Внимание! Вместо всех этих танцев с бубном можно (и, наверное, НУЖНО) поставить LTS-версию - https://nodejs.org/ru/download/ !!1
    0. Добавил переменную окружения для всего компьютера (не для пользователя) NODE_SKIP_PLATFORM_CHECK со значением 1
    1. Сохранил вывод
    npm config ls -l
    особенно понадобилась строчка
    prefix = "C:\\Users\\alpeg\\AppData\\Roaming\\npm"
    2. Переименовал C:\program Files\nodejs в C:\program Files\nodejs-old
    3. Создал папку C:\program Files\nodejs
    4. Распаковал туда новейший архив c 64-битной версией (Исходный код для Windows (.zip) - это плохо переведённый "Windows Binary (.zip)") со страницы https://nodejs.org/ru/download/current/
    Не совсем лирическое отступление. Через некоторое время этот шаг не понадобится, но сейчас, пока не вышла новая версия в которой уже есть NODE_SKIP_PLATFORM_CHECK , на шаге 4 надо вместо обычной страницы загрузок скачать nightly-версию отсюда:
    https://nodejs.org/download/nightly/
    Жмем Ctrl+F, и ищем сегодняшную дату в формате ГГГГММДД (20200620), если такой нет - ищем вчерашнюю.
    Внутри берём 7z или zip архив нужной разрядности
    мне подошла папка "v15.0.0-nightly20200620fdf10adef8", файл "node-v15.0.0-nightly20200620fdf10adef8-win-x64.7z"

    5. Снова посмотрел вывод node --version, потом npm config ls -l
    6. Увидел, что префикс теперь не тот
    7. Вернул префикс командой
    npm config set prefix "C:\Users\alpeg\AppData\Roaming\npm"

    8. Многие глобально установленные пакеты сломались, пришлось переустановить их сначала посмотрев вывод npm list -g --depth=0 2>/dev/null, затем запустив команду npm i -g package1 package2 package3 ... чтобы их все переустановить.
    9. Если установка падает из-за того что не может что-то собрать, нужно установить windows-build-tools, но для их установки надо запустить командную строку от имени администратора(!), и там ввести npm install -g windows-build-tools (внимательно читаем вывод, может потребоваться пляска с бубном - но там будет написана инструкция что делать). После танцев с бубном можно попробовать пакеты снова
  • Можно ли из JS передать обработанное изображение в $_FILES?

    @alpeg
    Дмитрий, я слегка опоздал, но никаких других идей кроме тех, что вы сами в гугле нашли, у меня нет(

    в canvas.toBlob передается параметр quality, который 0.95, там можно поставить 1, но и 0.95 должно давать хорошее качество, т.е. визуально разницы не будет между 0.95 и 1.
    canvas.toBlob(function (blob) { ... }, 'image/jpeg', 0.95);


    Вообще, можно попробовать библиотеку https://github.com/nodeca/pica (выглядит многообещающе)
  • Ошибка при запуске сервера на React, как исправить?

    @alpeg
    Владимир, ошибка возникает при запуске любым способом.
  • Можно ли из JS передать обработанное изображение в $_FILES?

    @alpeg
    Дмитрий,
    1.php:
    echo "\$_POST: \n";
    print_r($_POST);
    echo "\n\$_FILES: \n";
    print_r($_FILES);

    index.php:
    <style type="text/css">
        #пикчи {display:flex;flex-flow:row wrap;}
        #пикчи > div > img {max-width: 100px;max-height:100px;width:auto;height:auto;}
    </style>
    <form action="" method="POST" id="form1">
        Имя: <input type="text" name="myname" placeholder="Вася" required>
        <br>Отзыв: <input type="text" name="mytext" placeholder="Чипсы понравились!" required>
        <br>Пикчи: <input type="file" id="uploadItem"/>
        <div>
            Пикчи:
            <div id="пикчи">
            </div>
        </div>
        <button type="submit" id="form1sbm">go</button>
        <pre id="result"></pre>
    </form>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
    <script type="text/javascript">
        // https://qna.habr.com/q/787649
        $(document).ready(function () {
            $('#uploadItem').change(function () {
                $.each(this.files, function (i, file) {
                    storeResized(file);
                });
                $('#uploadItem').val('');
            });
            var form1 = $('#form1');
            var form1sbm = $('#form1sbm');
            form1.submit(function (event) {
                event.preventDefault();
                if (form1sbm.is(':disabled')) {
                    return;
                }
                var valid = form1[0].reportValidity();
                if (!valid) {
                    return;
                }
    
                var data = new FormData();
    
                // вручную добавляем элементы формы (с чекбоксами всё по-другому)
                data.append("myname", $('#form1 input[name="myname"]').val());
                data.append("mytext", $('#form1 input[name="mytext"]').val());
    
                $.each(imagesStorage, function (i, v) {
                    data.append("pics[]", v.blob, v.name);
                });
                // // вариант 1 (если blob был получен из canvas.toBlob(), то он уже будет иметь правильный mime-тип)
                // data.append("file_field_blob", blob, "filename1.ext");
                // // вариант 2
                // data.append("file_field_file_1", new File([blob], "filename2.ext", {type: 'image/jpeg'}), "filename1.ext");
                // // или так
                // data.append("file_field_file_2", new File([blob], "filename2.ext", {type: 'image/jpeg'}));
    
                var request = new XMLHttpRequest();
                form1sbm.attr('disabled', true);
                request.onerror = function () {
                    // show error?
                    $('#result').text(this.response);
                    form1sbm.attr('disabled', false);
                };
                request.onload = function () {
                    if (this.status >= 200 && this.status < 400) {
                        $('#result').text(this.response);
                        form1sbm.attr('disabled', false);
                        imageRemoveAll();
                    } else {
                        // show error?
                        $('#result').text(this.response);
                        form1sbm.attr('disabled', false);
                    }
                };
                request.open("POST", "1.php");
                request.send(data);
            });
            var imagesStorage = [];
            var пикчи = $('#пикчи');
            function imageRemoveAll() {
                while (imagesStorage.length > 0) {
                    imageRemove(0);
                }
                пикчи.text('');
            }
            function imageRemove(index) {
                var removed = imagesStorage.splice(index, 1)[0];
                URL.revokeObjectURL(removed.localUrl);
                delete removed.blob;
            }
            пикчи.on('click', '.pics-del', function (event) {
                event.preventDefault();
                var pic = $(this).closest('.pics-pic');
                imageRemove(pic.index());
                pic.remove();
            });
            function imageAdd(blob, name) {
                var o = {blob: blob, name: name, localUrl: URL.createObjectURL(blob)};
                imagesStorage.push(o);
                var div = $('<div class="pics-pic">');
                $('<div></div>').text(name + ' ').append($('<button type="button" class="pics-del">&times;</button>')).appendTo(div);
                $('<div></div>').append($('<img>').attr('src', o.localUrl)).appendTo(div)
                div.appendTo(пикчи);
            }
            function lol2() {
                $('#uploadItem2')[0].files
            }
            function storeResized(file) {
                // будем использовать также file.type, file.name
                var reader = new FileReader();
                reader.onload = function () {
                    var arrayBuffer = reader.result;
    
                    // File может быть использован вместо Blob!
                    // var blob = new Blob([arrayBuffer], {type: file.type});
                    var blob = new File([arrayBuffer], file.name, {type: file.type});
                    var imageUrl = URL.createObjectURL(blob);
                    var once = false;
    
                    var maxWidth = 1000;
                    var tempImg = new Image();
                    tempImg.src = imageUrl;
                    tempImg.onerror = function () {
                        if (once) {
                            return;
                        }
                        once = true;
                        URL.revokeObjectURL(imageUrl);
                        delete arrayBuffer;
                        delete blob;
                    };
                    tempImg.onload = function () {
                        if (once) {
                            return;
                        }
                        once = true;
                        var tempW = tempImg.width;
                        var tempH = tempImg.height;
                        if (tempW > tempH) {
                            if (tempW > maxWidth) {
                                tempH *= maxWidth / tempW;
                                tempW = maxWidth;
                            }
                        } else {
                            if (tempH > maxWidth) {
                                tempW *= maxWidth / tempH;
                                tempH = maxWidth;
                            }
                        }
                        tempW = Math.round(tempW);
                        tempH = Math.round(tempH);
    
                        var canvas = document.createElement('canvas');
                        canvas.width = tempW;
                        canvas.height = tempH;
                        var ctx = canvas.getContext("2d");
                        ctx.drawImage(this, 0, 0, tempW, tempH);
                        canvas.toBlob(function (blob) {
                            imageAdd(blob, file.name);
                        }, 'image/jpeg', 0.95);
                        this.src = '';
                        URL.revokeObjectURL(imageUrl);
                        delete arrayBuffer;
                        delete blob;
                    };
                };
                reader.readAsArrayBuffer(file);
            }
        });
    </script>
  • Можно ли из JS передать обработанное изображение в $_FILES?

    @alpeg
    Дмитрий, Вы уже близко!
    при изменении change() input file на мою страницу действительно приходит массив $_FILES, но при отправке формы он ведь теряется...

    Стоп-стоп-стоп) На .change() мы обрабатываем картинку и сохраняем в простой js-массив обработанный блоб! и НЕ отправляем! А храним до тех пор, пока пользователь не решится отправить форму - что придётся делать через javascript чтобы в FormData-объект добавить их.
  • Можно ли из JS передать обработанное изображение в $_FILES?

    @alpeg
    Дмитрий,
    Я думал через js/canvas их сжать и потом вместе с отправкой формы обрабатывать уже через Imagick.


    Я думал ты так и собирался сделать? Ты сжимаешь их через canvas (код у тебя уже есть, только надо поменять на чтение в blob как я комментировал), но вместо отправки на сервер сразу, ты их отправляешь вместе с формой. (только обязательно нужно освободить память от несжатых картинок)

    Кстати, очень рекомендую предварительно отправить форму без картинок и на сервере проверить, что с ней всё в порядке (e-mail хороший, капча пройдена, сессия не протухла), и только после этого отправлять уже форму с картинками.

    на клиент ещё желательно выдать переменные ini_get('max_file_uploads'), ini_get('upload_max_filesize') и ini_get('post_max_size'), чтобы ты через js не пытался что-то отправить, что сервер не примет.

    И последнее - даже если нет регистрации, всегда можно добавить картинки после отправки формы, записав какой-нибудь постоянный ключик автора (вроде трипкода) в сессию (а лучше - в cookies, через которые он сможет потом без доказывания что e-mail его отзыв отредактировать/удалить)
  • Можно ли из JS передать обработанное изображение в $_FILES?

    @alpeg
    Дмитрий, я не пойму что ты пытаешься сделать) С $_FILES ты и так работаешь после отправки формы, если файлы, про которые ты говоришь, были в этой форме.

    массив $_FILES, как и всё остальное окружение скрипта существует только в момент обработки запроса, скрипт закончился - php удаляет файлы, которые были в $_FILES[*]['tmp_name'].

    Если тебе необходимо чтобы была сначала загрузка файлов, а потом отправка основной формы (всё-таки POST нерезиновый, у nginx, apache и php у всех есть ограничение на суммарный размер тела и на количество файлов) - то никакого другого способа, кроме как сохранять куда-нибудь эти файлы, нет.

    Для предотвращения злоупотреблений нужно чтобы у пользователя (например в сессии) хранилась инфа о загруженных файлах, и если их многовато - не давать ему грузить ещё. И не давать грузить без регистрации/авторизации.

    А файлы просто по дате изменения удалять, если они не были востребованы.
  • Можно ли из JS передать обработанное изображение в $_FILES?

    @alpeg
    Не используйте readAsDataURL! Вместо него надо использовать FileReader.readAsArrayBuffer().
    А если нужна ссылка, например для <img> то нужно создавать её через URL.createObjectURL (и после использования удалять через URL.revokeObjectURL())
  • Можно ли принимать платежи от пользователей сервиса, не являясь юридическим лицом?

    @alpeg
    Хотя физическим лицам не запрещается принимать платежи, платежные сервисы часто имеют ограничения для физ.лиц (или просто с ними не работают) так как они могут попасть под статью "незаконное предпринимательство". В законе определения этому термину нет (рекомендую почитать мнение юристов и практику). Если вы - физ.лицо и принимаете платежи - убедитесь, что ваша деятельность не будет считаться незаконным предпринимательством. Ну или что вы неуловимый Джо.
  • Как принимать платежи на сайте без ИП?

    @alpeg
    Хотя физическим лицам не запрещается принимать платежи, платежные сервисы часто имеют ограничения для физ.лиц (или просто с ними не работают) так как они могут попасть под статью "незаконное предпринимательство". В законе определения этому термину нет (рекомендую почитать мнение юристов и практику). Если вы - физ.лицо и принимаете платежи - убедитесь, что ваша деятельность не будет считаться незаконным предпринимательством. Ну или что вы неуловимый Джо.
  • Как безопасно давать фрилансеру доступ к коду сайта?

    @alpeg
    awesomer всё именно так.
    Обычно при работе с "недоработанным" кодом возникает только одно желание: заставить предыдущих разработчиков написать этот код обратно. И, чем более он самописный, тем больше желание.

    И это при работе с практически любым кодом, от сайта-визитки до энтерпрайза.
  • Что вы думаете о бирже фриланса guru.com?

    @alpeg
    Биржа нереально бесит тем, что в момент принятия заказа сразу же снимается комиссия за проект - т.е. сначала ты платишь за "соединение" с заказчиком, а дальше уже ваши проблемы.

    Своим тёмным интерфейсом и отношением к пользователю как к дойной корове она превосходит фриланс ру. У меня друг попался на премиум, который, конечно же, через 30 дней съел его деньги с привязанной к paypal карте.

    Создали с ним тикет, потом решили воспользоваться "онлайн чатом". Написал, что, якобы, я звонил на горячую линию банка, где посоветовали обратиться в paypal, где, в свою очередь, сказали обратиться к вам.

    Несколько раз в чате нам сказали, что вы согласились с условиями и что можете отменить подписку, и премиум кончится через месяц. После моих слов по поводу того, что в банке обещали вернуть деньги, если предоставлю скриншоты отказа вернуть их от того кто предоставлял услуги, спросил что-то вроде "если я правильно понял, вы отказываетесь делать полный возврат за неиспользованный триал?" после чего деньги пообщеали вернуть, закрыли диалог, лог переписки в онлайн-чате не показывался, а созданный до этого тикет исчез (не закрылся, именно исчез).
  • Как отследить на каких доменах используется моя CMS?

    @alpeg
    никак не отследить.

    Ну-ну, скажите это microsoft, с его сливающими инфу о методах взлома закладками, не говоря уже о тысячах разнообразной неотключаемой телеметрии
  • Как отследить на каких доменах используется моя CMS?

    @alpeg
    Adamos: Я так и понял.

    На локальном сервере разработки это может стать проблемой на ровном месте.


    У вас сервер разработки изолирован от интернета? Вон, 1С не работает без хардверного ключа хоть ты десять раз разработчик, и ничего.