Генерация приглашений, похожих на инвайты сайта habrahabr

    Скрипт генерирует приглашения для регистрации на сайте в виде картинки 51x51 пикселей формата PNG, написан на PHP, в качестве базы данных использует MySQL. Сделан ради интереса, будет интересен только новичкам.

    С помощью библиотеки GD можно без проблем создавать и редактировать изображения различных форматов. Для создания изображений нам понадобиться несколько функций:
    int imagecolorallocate(resource image, int red, int green, int blue)

    * This source code was highlighted with Source Code Highlighter.
    и
    int imagecolorallocatealpha(resource image, int red, int green, int blue, int alpha)

    * This source code was highlighted with Source Code Highlighter.
    обе функции возвращают идентификатор цвета для изображения. Единственное отличие в параметре alpha, который задаёт прозрачность изображения. Но так как с PNG прозрачностью в IE издревле были проблемы, то лучше использовать первую функцию. Первый аргумент image можно получить при помощи функции:
    resource imagecreate(int x, int y)

    * This source code was highlighted with Source Code Highlighter.
    она возвращает идентификатор изображения, представляющий пустое палитровое изображение размером x на y.

    Для начала необходимо создать таблицу в базе данных:
    CREATE TABLE IF NOT EXISTS `di_invite` (
     `invite_id` int(15) unsigned NOT NULL AUTO_INCREMENT,
     `invite_hash` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
     `invite_serialize` text COLLATE utf8_unicode_ci,
     `invite_username_owner` varchar(25) COLLATE utf8_unicode_ci DEFAULT NULL,
     `invite_username_recipient` varchar(25) COLLATE utf8_unicode_ci DEFAULT NULL,
     PRIMARY KEY (`invite_id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

    * This source code was highlighted with Source Code Highlighter.
    В БД будет храниться уникальный идентификатор каждого приглашения(invite_id), hash приглашения(invite_hash), массив с четырьмя RGB-компонентами, в байтово-поточном представление(invite_serialize), никнейм владельца приглашения(invite_username_owner) и никнейм пользователя, активировавшего его(invite_username_recipient).

    Теперь необходимо сформировать массив 3x4, со значениями красного, зелёного и синего компонентов для каждого из четырёх цветов и записать его в БД.
    1. function add_invite() {
    2. // Создаёт четыре случайных цвета и записывает их в массив
    3. $rgb_array = Array();
    4. for ($idx=0; $idx < 4; ++$idx) {
    5.  $rgb = array();
    6.  for($idx2 = 0; $idx2 < 3; ++ $idx2) $rgb[] = mt_rand(0, 255);
    7.  $rgb_array[] = $rgb;
    8. }
    9. // Преобоазует массив в байтово-поточное представление для записи в БД
    10. $serialize = serialize($rgb_array);
    11. // Хэш инвайта
    12. $hash = md5($serialize);
    13. // Вставляет
    14. $_CORE['db']->query('INSERT INTO `di_invite` (`invite_hash`, `invite_serialize`, `invite_username_owner`) VALUES (?, ?, ?);', $hash, $serialize, $_CORE['user']->user_info['user_name']);
    15. if ($_CORE['db']->affected_rows() == 1) return array(true, 'Инвайт "'.$hash.'" создан');
    16. else return array(false, 'ERROR - ошибка базы данных');
    17. return true;
    18. }
    * This source code was highlighted with Source Code Highlighter.
    Надо подумать над тем, как это приглашение отдать пользователю. Конечно, легче всего это можно сделать представив его в виде картинки. При генерации изображения надо учесть, что вместо активированного инвайта стоит отдавать чёрно белую картинку, иначе же полноценный четырёхцветный инвайт.

    1. if (isset($_GET['hash']{31})) {
    2. // ищет в БД инвайт с таким хэшем
    3. $result = $_CORE['db']->query('SELECT `di_invite`.`invite_serialize`, `di_invite`.`invite_username_recipient` FROM `di_invite` WHERE `di_invite`.`invite_hash` = ?;', $hash);
    4. // если нашли
    5. if ($result && $_CORE['db']->num_rows() == 1) {
    6.  // если инвайт уже использовали, то отдаём чёрно белую картинку
    7.  if ($_CORE['db']->result($result, 0, 'invite_username_recipient') != '') {
    8.   // отдаёи браузеру информацию, о том что это картинка
    9.   header('Content-type: image/png');
    10.   // создаём квадратик 51px на 51px
    11.   $im = ImageCreate(51, 51) or die('Ошибка при создании изображения');
    12.   // выделяет два цвета серый и белый, причём белый будет использован в качестве фона для изображения
    13.   $color[1] = imagecolorallocate($im, 255, 255, 255);
    14.   $color[2] = imagecolorallocate($im, 200, 200, 200);
    15.   // рисует два серых прямоугольника по диагонали
    16.   ImageFilledRectangle($im, 26, 0, 50, 25, $color[2]);
    17.   ImageFilledRectangle($im, 0, 25, 25, 50, $color[2]);
    18.   // Выводит изображение в браузер
    19.   ImagePng($im);
    20.   // Разрушает его
    21.   imagedestroy($im);
    22.  // иначе, если инвайт ещё не использован
    23.  } else {
    24.   // востанавливает массив
    25.   $rgb_array = unserialize(mysql_result($result, 0, 'invite_serialize'));
    26.   header('Content-type: image/png');
    27.   $im = ImageCreate(51, 51) or die('Ошибка при создании изображения');
    28.   // выделяет 4 цвета, из значений массива
    29.   for ($i=0; $i < 4; ++$i)
    30.   $color[$i] = imagecolorallocate($im, $rgb_array[$i][0], $rgb_array[$i][1], $rgb_array[$i][2]);
    31.   // рисует три прямоугольника, чётвёртый (т.е. нулевой элемент массива) цвет по умолчанию являеться фоном изображения
    32.   ImageFilledRectangle($im, 0, 0, 25, 25, $color[1]);
    33.   ImageFilledRectangle($im, 26, 0, 50, 25, $color[2]);
    34.   ImageFilledRectangle($im, 0, 25, 25, 50, $color[3]);
    35.   ImagePng($im);
    36.   imagedestroy($im);
    37.  }
    38. } else {
    39.  exit();
    40. }
    41. } else {
    42. exit();
    43. }
    * This source code was highlighted with Source Code Highlighter.
    Осталось дело за малым, а именно проверить на валидность приглашение. Чтобы это сделать достаточно считать в массив значение цвета в четырёх углах картинки, и попытаться найти хэш получившегося массива в БД.

    1. function validate_invite($hash_serialize, $userfile) {    
    2. // если при загрузке изображения не было ошибок, размер изображения не превышает допустимый и тип стоответсвует нужному
    3. if (isset($userfile) && is_uploaded_file($userfile['tmp_name']) && $userfile['size'] < 5*1024 && $userfile['type'] == 'image/png' && $userfile['error'] == 0) { 
    4.  // создаём изображение на основе загруженного
    5.  $source = ImageCreateFromPNG($userfile['tmp_name']);  
    6.  // определяем точки в которых будет проверяться цвет
    7.  $x[] = 48; $y[] = 48; $x[] = 2; $y[] = 2; $x[] = 48; $y[] = 2; $x[] = 2; $y[] = 48;
    8.  $rgb_array = Array();
    9.  for ($i = 0; $i < 4; ++$i) { 
    10.   // получает индекс цвета в точке
    11.   $color_index = imagecolorat($source, $x[$i], $y[$i]);
    12.   // получает цвет для индекса
    13.   $color_tran = imagecolorsforindex($source, $color_index); 
    14.   // записываеться в массив
    15.   $rgb = Array();
    16.   $rgb[] = $color_tran['red'];
    17.   $rgb[] = $color_tran['green'];
    18.   $rgb[] = $color_tran['blue'];
    19.   $rgb_array[] = $rgb;
    20.  }      
    21.  // преобоазует массив в байтово-поточное представление для сопоставления с записью в массиве
    22.  $serialize_rgb_array = serialize($rgb_array);  
    23.  // получает хэш
    24.  $hash_serialize_rgb_array = md5($serialize_rgb_array); 
    25.  // ищет запись в бд
    26.  $_CORE['db']->query('SELECT `di_invite`.`invite_id` FROM `di_invite` WHERE `di_invite`.`invite_hash` = ?;', $hash_serialize_rgb_array);
    27.  // если найдено, очищает память, и возвращает true, иначе очищает память и возвращает false
    28.  if ($_CORE['db']->num_rows() < 1) {
    29.   imagedestroy($source);
    30.   return false;
    31.  } else {
    32.   imagedestroy($source);
    33.   $hash_serialize = $hash_serialize_rgb_array;
    34.   return true;
    35.  }
    36. } else return false;
    37. }
    * This source code was highlighted with Source Code Highlighter.
    Получится, что то похожее на:


    P.S. При построение изображения допущена одна неточность, благодаря которой изображения получаются не симметричными.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 66
    • +57
      Тут однажды один пользователь учил, как взламывать капчу хабры. Так его с тех пор никто не видел.
      • +26
        это совсем разные вещи.
        • +15
          Капчу теперь тут тоже больше никто не видит
      • +30
        Вот по-моему лучше бы приглашения были примерно такого вида: image
        Если уж делать интересный инвайт. Программистам не сильно сложней, а у пользователя полет фантазии.
        • +6
          Да уж, художники-абстракционисты останутся без хлеба.
          • +4
            Через лет 50 такие приглашения будут стоить миллионы!
          • +13
            Вместо Source Code Highlighter на Хабрахабре давно ужé можно пользоваться парою нестандартных элементов разметки <source></source>, обеспечивающих синтаксическую подсветку своего содержимого в качестве исходного кода. Достоинством этого подхода также является несколько больший размер шрифта.

            Вот пример работы такой разметки:

            function validate_invite($hash_serialize, $userfile) {    
            // если при загрузке изображения не было ошибок, размер изображения не превышает допустимый и тип стоответсвует нужному
            if (isset($userfile) && is_uploaded_file($userfile['tmp_name']) && $userfile['size'] < 5*1024 && $userfile['type'] == 'image/png' && $userfile['error'] == 0) { 
               // создаём изображение на основе загруженного
               $source = ImageCreateFromPNG($userfile['tmp_name']);  
               // определяем точки в которых будет проверяться цвет
               $x[] = 48; $y[] = 48; $x[] = 2; $y[] = 2; $x[] = 48; $y[] = 2; $x[] = 2; $y[] = 48;
               $rgb_array = Array();
               for ($i = 0; $i < 4; ++$i) { 
                  // получает индекс цвета в точке
                  $color_index = imagecolorat($source, $x[$i], $y[$i]);
                  // получает цвет для индекса
                  $color_tran = imagecolorsforindex($source, $color_index); 
                  // записываеться в массив
                  $rgb = Array();
                  $rgb[] = $color_tran['red'];
                  $rgb[] = $color_tran['green'];
                  $rgb[] = $color_tran['blue'];
                  $rgb_array[] = $rgb;
               }      
               // преобоазует массив в байтово-поточное представление для сопоставления с записью в массиве
               $serialize_rgb_array = serialize($rgb_array);  
               // получает хэш
               $hash_serialize_rgb_array = md5($serialize_rgb_array); 
               // ищет запись в бд
               $_CORE['db']->query('SELECT `di_invite`.`invite_id` FROM `di_invite` WHERE `di_invite`.`invite_hash` = ?;', $hash_serialize_rgb_array);
               // если найдено, очищает память, и возвращает true, иначе очищает память и возвращает false
               if ($_CORE['db']->num_rows() < 1) {
                  imagedestroy($source);
                  return false;
               } else {
                  imagedestroy($source);
                  $hash_serialize = $hash_serialize_rgb_array;
                  return true;
               }
            } else return false;
            }
            • 0
              В следующий раз я это учту.
              • +5
                А нумерация линий? Вообще, по оформлению лучше все-таки Source Code Highlighter.
                • +1
                  Чем лучше-то?
                  • +14
                    Подозреваю, что нумерацией линий лучше
                    • 0
                      А она тут кому-то на Хабре реально нужна?
                      • +12
                        Зодиаку нужна же
                        • 0
                          О, заминусовали. А аргументировать? Я же специально написал "реально нужна". Я вообще не припомню на хабре фраз типа «на такой-то строке в вашем скрипте ошибка» или ещё что-то такое. Вообще, любого упоминания номеров строк.

                          Поэтому и задал такой вопрос.
                          • 0
                            1. Я вообще не припомню на хабре фраз типа «на такой-то строке в вашем скрипте ошибка» или ещё что-то такое.

                            быть может потому что их нет?
                      • –1
                        Дык это — чем хабровский )
                    • +1
                      фуу
                    • –21
                      Сколько уже можно позорить наш язык
                    • +26
                      Тоже мне проблема, картинку через gd сгенерить. Написал бы ты скрипт который реальные инвайты на хабру генерит)
                      • 0
                        Если я не ошибаюсь, у приглашений привязка к серверу. Если он его не выдавал, то он его не примет.
                        • +21
                          Спасибо, кэп!
                          • +1
                            Лол, можно попробовать перебор заранее сгенерированных изображений…
                            Интересно, сколько займет генерация 67108864 изображений и последующий перебор в форме регистрации?
                            • +2
                              Ошибочка вышла… 7.922816251426434e+28 изображений
                        • +30
                          Вы уж простите, но то, что написано в статье — быдлокод. Чему она, фактически, учит? Сгенерировать 4 случайных числа, записать их в базу и сделать на их основании картинку из 4 квадратиков?

                          Примеры кода
                          $rgb_array = Array();
                          for ($i=0; $i < 4; ++$i) {
                           $rgb = Array();
                           $randnum = intval(mt_rand(0,255)); $rgb[] = $randnum;
                           $randnum = intval(mt_rand(0,255)); $rgb[] = $randnum;
                           $randnum = intval(mt_rand(0,255)); $rgb[] = $randnum;
                           $rgb_array[] = $rgb;
                          }
                          


                          можно было бы переписать, например, так:
                          $rgb_array = Array();
                          for ($idx=0; $idx < 4; ++$idx) {
                            $rgb = array();
                            for($idx2 = 0; $idx2 < 3; ++ $idx2) $rgb[] = mt_rand(0, 255);
                            $rgb_array[] = $rgb;
                          }
                          


                          Потом дважды сериализуется массив, непонятно зачем:
                          $serialize = serialize($rgb_array);
                          $hash = md5(serialize($rgb_array));
                          


                          Кроме того, в коде огромное количество магических констант, мешающих восприятию смысла кода.

                          Короче говоря, такое задание и такое решение к нему может существовать, но едва ли оно заслуживает статьи про это на хабре.
                          • –6
                            Да, признаю, некоторую часть кода можно было бы оптимизировать. Просто этот скрипт взят из живого проекта и там как раз эти переменные были нужны. Но всё таки до быдлокода, как мне кажется, он не дотягиваем.
                            • +4
                              Код в топике немного изменил, соответственно этим рекомендациям.
                              • +2
                                $rgb = array();
                                for($idx2 = 0; $idx2 < 3; ++ $idx2) $rgb[] = mt_rand(0, 255);

                                $rgb = array(mt_rand(0, 255),mt_rand(0, 255),mt_rand(0, 255));

                                не?
                                • +3
                                  или ещё иначе:
                                  $rgb = array();
                                  for($idx2 = 0; $idx2 < 3; ++ $idx2) $rgb[] = mt_rand(0, 255);
                                  $rgb_array[] = $rgb;

                                  $rgb_array[] = array(mt_rand(0, 255),mt_rand(0, 255),mt_rand(0, 255));
                                  • +1
                                    Да, так даже лучше. Я поначалу подумал, что 3 раза писать mt_rand() не круто, особенно с magic numbers, но цикл for оказался уродливее. Жалко что в php нет array comprehensions, как в питоне:
                                    rgb = [ random.randint(0, 255) for _ in range(0,3) ]
                                    
                                    • +3
                                      То Вам копипаст не нравится, то нравится. Вообще, тот код что Вы упрекали не есть быдлокод, не стоит «наводить красивости» на циклы в строку или две строки копипаста, оно того реально не стоит.
                                      • 0
                                        Если это проект на один раз или proof of concept — тогда да, любой код годится.
                                        Если же предполагается, что над кто-то будет впоследствии работать, а особенно если его выкладывать на хабр — код должен быть красивым и понятным, а мысль разработчика — четко прослеживаться.
                                • +5
                                  В таких статьях я код вообще не читаю, все равно его использовать не буду.
                                  • +1
                                    Мне бы понравились инвайты в виде смайликов. Типа того:
                                  • –8
                                    Хочу видеть Online Demo!
                                    • +57
                                      Не хочу Online Demo!
                                      • +7
                                        Вовремя поднятый сервер не считается упавшим.
                                    • +2
                                      Генерируем любую картинку, сэйвим как файл, берём от него мд5 и пихаем в базу, сам файл шлём на мыло, файл удаляем.
                                      В статье зачем-то считываются 4 точки изображения и слишком много кода для такого действия. Массив откуда брать цвета совсем чудесен, потому что это именно тот случай, где помогла бы тригонометрия. Но лучше брать хэш от всей картинки и не думать о квадратах.
                                      • –1
                                        А если пользователь потеряет картинку? Или это случиться не по его вине, но картинка исчезнет.
                                        • 0
                                          Сгенерировать новую. Число инвайтов вычитать после регистрации.
                                      • 0
                                        Я так понимаю что не сложно будет сделать что бы использовано не 4 цвета, а например 6 или 8?
                                        • 0
                                          Да, но я думаю, что 4 цвета это уже достаточно хорошая защита.
                                        • +1
                                          SELECT `di_invite`.`invite_serialize`, `di_invite`.`invite_username_recipient` FROM `di_invite`.`di_invite` WHERE `invite_hash` = ?;

                                          сработает, только если БД тоже названа `di_invite`.
                                          • –1
                                            CREATE TABLE IF NOT EXISTS `di_invite` (

                                            Она так и названа, вроде бы.
                                            • 0
                                              Это у вас так названа таблица.

                                              А часть «FROM `di_invite`.`di_invite`» — говорит «ИЗ таблицы `di_invite` в БД `di_invite`»
                                              • 0
                                                Ах, да, исправил. Спасибо, что заметили.
                                          • –4
                                            Дорогой Дед Мороз!
                                            Пожалуйста, сделай так, чтобы ребята, которые постят в блоге PHP на хабре, прочитали наконец-то руководство по кодстайлу.

                                            Твой Серёжа.
                                              • +5
                                                А ничего, что это кодстайл PEAR? Почему он должен считаться единственно верным? А если мне, например, Zend стиль больше нравится?
                                                • +1
                                                  Хоть какой.
                                                  Достаточно что-бы по коду соблюдался один стиль.
                                              • 0
                                                Отбор кода для PEAR всегда отличался жестокостью, так как туда попадает только самое лучшее.
                                                Я стараюсь писать в стиле «K&R», но всё же мне удобнее писать с отступами равными двум пробелам, а не 4 или табуляции, и также я не переношу открывающую фигурную скобочку на новую строку. В чём же мой стиль вам так не угодил?

                                                P.S. Некоторые пробелы порезал Source Code Highlighter.
                                                • 0
                                                  Я знаю одного программиста, так ему удобнее писать так, что потом коду обфускатор не нужен — всё равно никто не поймёт.
                                                  • +1
                                                    Есть некий набор правил, стили (Zend, PEAR ...) лишь группируют эти наборы правил на свое усмотрение. Нет ничего плохого, если человек будет придерживаться не конкретных стилей, а набору правил.
                                                    • 0
                                                      Если пишешь для себя — пиши хоть наскальными рисунками. Если работаешь в команде или пишешь open source — никаких наборов правил, только конкретный стиль, про «мне удобнее» лучше забыть.
                                                      • 0
                                                        Андрей, хороший стиль — он как хороший вкус.
                                                        Писать хорошо и красиво совсем не сложно, зато потом приятно читать.
                                                        И если пост на хабре — он только для своих глаз.
                                                        • 0
                                                          Не только, естественно.
                                                  • 0
                                                    филиал обсуждения проблемы «пробелы против табуляции» можно считать открытым?
                                                    • 0
                                                      проблемы? не вижу никак проблем)
                                                      • 0
                                                        Не стоит, на хабре по этому вопросу статей больше, чем даже про капчу)
                                                        • 0
                                                          Сейчас еще откроется отделение «египетские скобки против прямых»
                                                    • +1
                                                      Объясните недалёкому, зачем делать инвайты картинками. Я раньше предполагал, чтобы наглядно видеть, использован ли инвайт или нет (картинка отдаётся сервером), а тут про какую — то защиту толкуют.
                                                      • +1
                                                        Картинку можно распечатать и в случае отключения интернета дойти до оффиса компании и показать её. // sɐrˈkazm //

                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.