Пользователь
0,0
рейтинг
18 июня 2011 в 17:40

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

PHP*
Скрипт генерирует приглашения для регистрации на сайте в виде картинки 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. При построение изображения допущена одна неточность, благодаря которой изображения получаются не симметричными.
Роман @voodee
карма
99,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (66)

  • +57
    Тут однажды один пользователь учил, как взламывать капчу хабры. Так его с тех пор никто не видел.
    • +26
      это совсем разные вещи.
    • +15
      Капчу теперь тут тоже больше никто не видит
      • +26
        Как это? Я тут.
  • +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
    Сколько уже можно позорить наш язык
    • +11
      и вопросительный знак?
    • +15
      php?
      • –8
        Да
  • +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
    Мне бы понравились инвайты в виде смайликов. Типа того:
    • +34
      или вот такие

  • –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 на хабре, прочитали наконец-то руководство по кодстайлу.

    Твой Серёжа.
    • –3
      • +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 //

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