Як створити власну сторінку реєстрації WordPress Multisite. Використання бібліотеки TGM Plugin Activation у темах WordPress Міркування activate php

Дозволяє використовувати одну установку WordPress для кількох сайтів одночасно. При цьому кожен сайт отримує власні таблиці в базі даних з унікальним префіксом.

Таблиці з даними зареєстрованих користувачів є спільними для всіх сайтів мережі. Це безперечний плюс і зареєструвавшись одного разу можна отримати доступ до кількох сайтів. Причому на кожному сайті один і той же обліковий запис може мати різні права. Наприклад, на одному сайті користувач може бути редактором, а на іншому адміністратором.

У звичайній встановленні WordPressсторінку реєстрації, авторизації та скидання пароля виводить файл wp-login.php .

  • wp-login.php - авторизація
  • wp-login.php?action=register - реєстрація
  • wp-login.php?action=lostpassword - скидання пароля

У режимі Multisite ядро ​​WordPress починає поводитися дещо інакше і при переході за посиланням wp-login.php?action=register відбудеться редирект на wp-signup.php. Це сторінка реєстрації вашої мережі, яка за промовчанням є у WordPress.

Крім реєстрації звичайних облікових користувачів на ній можна створити і новий сайт, якщо суперадміністратор включив таку можливість в налаштуваннях мережі (Network Admin → Settings → Network Settings).

Здебільшого сторінка реєстрації виглядає не зовсім добре. Багато тем оформлення використовують CSS-фреймворки, наприклад Bootstrap, і власні специфічні класи для стилізації різних елементів на сторінках, тому важко написати єдиний HTML, який підійде всім.

Але не варто впадати у відчай, якщо сторінка виглядає неохайно. Файл wp-signup.php відмінна річ спочатку, коли немає часу опрацьовувати кожну деталь сайту — можна зосередитися на інших більше важливих сторінкахта контенті.

Коли ви будете готові зробити свою власну сторінку реєстрації, wp-signup.php буде гарним зразком та прикладом, за яким легко розібратися у спектрі функцій, які надає WordPress для обробки та перевірки введених користувачами даних та створення нових облікових записів.

Основний сайт мережі

За промовчанням WordPress відкриває сторінку реєстрації (wp-signup.php) на основному домені (сайті) мережі. Тим не менш, можна створювати сторінки реєстрації для кожного сайту мережі, навіть якщо вони мають теми.

Ми розглядатимемо випадок, коли на всіх сайтах мережі використовується одна тема, але на кожному з них є сторінка реєстрації. Сайти розрізняються мовою (англійською та російською), тому сторінка реєстрації буде виводитися «рідною» мовою сайту. Якщо сайти використовують різні теми, все буде залежати від того, які саме це теми, чи підійде їм однакова верстка (відмінна ситуація, яка може підштовхнути вас до уніфікації всіх своїх тем) або варто опрацьовувати сторінки індивідуально.

Альтернатива functions.php

Порядок у файлах

MU-плагіни можуть містити будь-яку кількість файлів та структуру, яка здасться вам логічною. Я дотримуюсь приблизно такої ієрархії:

| mu-plugins | | load.php | | selena-network | | | signup | | | | plugin.php | | | ... | | | Jetpack | | | | plugin.php

У файлі load.php підключаються переклади та всі необхідні «плагіни»:

// Завантаження перекладів для MU-плагінів load_muplugin_textdomain("selena_network", "/selena-network/languages/"); // Функціонал для сторінки реєстрації require WPMU_PLUGIN_DIR . "/selena-network/signup/plugin.php"; // Ще один плагін // require WPMU_PLUGIN_DIR ...

Усередині директорії selena-network зберігаються папки плагінів. У кожній є свій plugin.php, які ми і підключаємо до load.php. Це дає гнучкість та можливість миттєво відключати та включати окремі компоненти на робочому проекті у разі екстреної необхідності.

Сторінка реєстрації

Розібравшись з тим, де і як ми писатимемо код, можна переходити до створення сторінки реєстрації.

Створимо сторінку за адресою example.org/signup/ через звичайний інтерфейс. Як адресу можна використовувати будь-яку URL-адресу, яка здасться відповідною для вашого проекту.

Редирект на потрібну сторінку реєстрації

Щоб WordPress дізнався про нашу нову сторінку реєстрації та проводив редирект саме на неї, при натисканні на посилання «Зареєструватися», використовується фільтр wp_signup_location . Його можна знайти всередині wp-login.php і саме він відповідає за редирект на wp-signup.php за умовчанням.

Case "register" : if (is_multisite()) ( wp_redirect(apply_filters("wp_signup_location", network_site_url("wp-signup.php"))); exit; // ...

Як ви пам'ятаєте, за замовчуванням сторінка реєстрації відкривається на основному домені мережі. Саме тому використовується network_site_url() .

Додамо свій обробник до фільтра в mu-plugins/selena-network/signup/plugin.php , який буде віддавати адресу сторінки реєстрації на поточному сайті:

Function selena_network_signup_page($url) ( return home_url("signup"); ) add_filter ("wp_signup_location", "selena_network_signup_page", 99);

selena_network - префікс, який я використовую в іменах всіх функцій усередині MU-плагінів на своєму сайті для уникнення колізій, його слід замінити на свій власний унікальний префікс. Пріоритет додавання фільтра 99, тому що деякі плагіни, наприклад, bbPress і BuddyPress можуть перезаписати цю адресу на власну (MU-плагіни завантажуються раніше, ніж звичайні плагіни, див. вище).

Зверніть увагу, що використовується home_url() , яка на відміну від network_site_url() віддає адресу поточного сайту, а не головного сайту мережі.

Функціонал wp-signup.php

Файл wp-signup.php містить велику кількість функцій та коду. Щоб побачити картину в цілому, можна скористатися згортанням коду. Як правило, по-англійськи це називається code folding.

На самому початку файлу з 1 по 80 рядок (у версії 4.1.1) проводяться різні перевірки та виведення «старту» сторінки за допомогою get_header() .

Далі оголошуються безліч методів і перед тим, як ми почнемо працювати з ними, варто розібратися, що робить кожна функція. Всередині багатьох часто використовуються інші функції з префіксом wpmu_ , всі вони оголошуються у файлі wp-includes/ms-functions.php . Цей розділ важко зрозуміти, не бачачи код самостійно. Нижче опис основних функцій на випадок, якщо у вас виникнуть труднощі.

  • wpmu_signup_stylesheet() – виведення додаткового CSS на сторінці реєстрації.
  • show_blog_form() — поля для реєстрації сайту (адреса, назва видимість для пошукових систем).
  • validate_blog_form() — перевірка введеної адреси сайту та назви за допомогою wpmu_validate_blog_signup().
  • show_user_form() — поля для реєстрації користувача (логін та адреса ел. пошти).
  • validate_user_form() - перевірка введеного логіну та адреси ел. пошти за допомогою wpmu_validate_user_signup() .
  • signup_another_blog() — поля для реєстрації нових сайтів за допомогою show_blog_form() для користувачів, які вже зареєстровані на сайті.
  • validate_another_blog_signup() — перевіряє адресу сайту та назву за допомогою validate_blog_form().
  • signup_user() — основна функція виведення полів сторінки реєстрації.
  • validate_user_signup() — перевіряє логін та адресу ел. пошти за допомогою validate_user_form().
  • signup_blog() — поля для введення адреси, назви та видимості сайту (другий крок реєстрації) за допомогою show_blog_form().
  • validate_blog_signup() - перевіряє логін, адресу ел. пошти, адресу та назву сайту.

У самому низу файлу wp-signup.php (з рядка 646 у версії 4.1.1) основна логіка роботи сторінки реєстрації, яка використовує всі описані вище методи. Ця частина коду не винесена на функцію. Наприкінці викликається get_footer().

Копіюємо функціонал wp-signup.php

Далі буде описано процедуру копіювання wp-signup.php в MU-плагіни та внесення змін до «форку». Можливо, це може здатися не правильним шляхом. Натомість можна з нуля написати свої функції для перевірки та виведення форм використовуючи класи, а не звичайні функції. На мій погляд, у wp-signup.php вже є вся необхідна логіка для нашої сторінки, залишається лише внести невеликі зміни.

При оновленні WordPress іноді змінюється і wp-signup.php , але це не означає що при кожному релізі доведеться синхронізувати свій «форк». Функції всередині wp-signup.php по суті займаються лише виведенням HTML, перевіркою даних, створенням облікових записів та сайтів займаються методи з префіксом wpmu_, оголошені в ms-functions.php.

Займемося створенням функції, яка виводитиме форму реєстрації на сторінці. Для цього скопіюємо wp-signup.php з кореня WordPress у mu-plugings/selena-network/signup/. Підключимо його всередині mu-plugins/selena-network/signup/plugin.php).

Require WPMU_PLUGIN_DIR . "/selena-network/signup/wp-signup.php";

Видалимо з самого початку скопійованого файлу всі потреби та непотрібні перевірки. У версії 4.1.1 це весь код з 1 по 80 рядок.

Тепер ми готові створити основну функцію для виведення форми реєстрації. Для цього всю логіку з рядка 646 і до кінця файлу перенесемо в функцію з назвою selena_network_signup_main . Наприкінці видалимо два зайві закриваючі

(Рядки 722 і 723), а також виклик get_footer() .

У щойно створеній selena_network_signup_main() на самому початку оголосимо глобальну змінну active_signup , яку використовують всі інші методи цього файлу. І додамо виклик події before_signup_form, яку ми видалили із самого початку файлу.

Function selena_network_signup_main() ( global $active_signup; do_action("before_signup_form"); // ... )

Тепер залишається лише змінити верстку у всіх місцях, де це необхідно, і сторінка реєстрації готова.

Висновок форми реєстрації

Тут є як мінімум два варіанти. Більше зручний спосіб— Створити шорткод і розмістити його на сторінці через звичайний редактор.

// Створюємо шорткод network_signup add_shortcode("network_signup", "selena_network_signup_main");

Другий варіант – створити в папці дочірньої теми шаблон сторінки page-signup.php. Замість слова "signup" можна використовувати унікальний ID, наданий сторінці. Всередині шаблону додати необхідну верстку та зробити виклик selena_network_signup_main() у потрібному місці.

В результаті моя сторінка реєстрації стала виглядати набагато краще та чистіше.

Сторінка активації

За умовчанням WordPress умовно ділить процес реєстрації в Multisite на два кроки - заповнення форми на сайті та активація облікового запису при переході за посиланням, надісланим в електронному листі. Після того, як ви заповните форму, створену в попередньому розділі, WordPress надсилає листа з невеликою інструкцією та посиланням для активації облікового запису.

За виведення сторінки активації відповідає файл wp-activate.php, розташований у кореневій директорії WordPress. wp-activate.php можна також повністю змінити. Процес подібний до того, що ми вже робили для wp-signup.php .

Створимо сторінку example.org/activate/ через стандартний інтерфейс. В якості адреси використовуйте будь-яку URL, яка здасться вам підходящим.

Скопіюємо файл wp-activate.php до себе в MU-плагіни та підключимо його до mu-plugins/selena-network/signup/plugin.php .

Require WPMU_PLUGIN_DIR . "/selena-network/signup/wp-activate.php";

Усередині не так багато вмісту, на відміну wp-signup.php . Файл виконує єдину операцію - активує обліковий запис, якщо отримано правильний ключ і виводить повідомлення про помилку або успішне виконання операції.

Видалимо всі непотрібні перевірки і require - з 1 по 69 рядок у WordPress 4.1.1. Наприкінці приберемо виклик get_footer() . Вміст, що залишився, перенесемо в функцію selena_network_activate_main() .

Цікаво зауважити, що тут перед завантаженням WordPress (wp-load.php) оголошувалась константа WP_INSTALLING. Її наявність змушує WordPress не завантажувати плагіни.

Як і у випадку зі сторінкою реєстрації, залишається лише виправити верстку там, де це необхідно. Також можна змінити текст повідомлень, що виводяться (у цьому випадку не забудьте додати текстовий домен своїх MU-плагінів у всі функції-перекладачі, за замовчуванням він ніде не встановлений).

Готову функцію можна використовувати на заздалегідь створеній сторінці через шорткод або окремий шаблон у дочірній темі.

Листи активації з правильними посиланнями

Сторінка активації готова до роботи, але WordPress не знає про неї і, як і раніше, надсилатиме листи активації з посиланням на wp-activate.php . На відміну від wp-signup.php тут немає фільтра, який дозволив би змінити адресу. Натомість потрібно написати свою функцію, яка надсилатиме листи з правильними посиланнями.

У момент заповнення та надсилання форми на сторінці реєстрації WordPress викликає wpmu_signup_ user() або wpmu_signup_ blog() залежно від типу реєстрації. Обидві функції створюють новий запис у таблиці wp_signups, заповнюючи її необхідним вмістом, серед якого є і ключ активації облікового запису.

Після цього, залежно від функції, викликається wpmu_signup_ user _notification() або wpmu_signup_ blog _notification(). Обидві функції мають схожий функціонал - генерують і надсилають листа з посиланням на активацію, але приймають різні аргументи. В обох є фільтри для перехоплення події.

If (! apply_filters("wpmu_signup_user_notification", $user, $user_email, $key, $meta)) return false;

Для активації облікових записів зі створенням блогу:

If (! apply_filters("wpmu_signup_blog_notification", $domain, $path, $title, $user, $user_email, $key, $meta)) ( return false; )

Залишається лише написати свої обробники, всередині яких надсилати листи через wp_mail() , а наприкінці обов'язково віддавати false , щоб WordPress не відправив лист активації двічі - один ваш, інше - лист за замовчуванням з посиланням на wp-activate.php.

Function selena_network_wpmu_signup_user_notification($user, $user_email, $key, $meta = array()) ( // Генеруємо заголовок, текст і заголовки листа // ... // Відправляємо листа або додаємо Cron-завдання для надсилання листа wp_mail($user_email , wp_specialchars_decode($subject), $message, $message_headers);// Віддаємо false, щоб WordPress не відправив лист активації двічі return false; ) add_filter("wpmu_signup_user_notification", "selena_network_wpmu_signup

Якщо ви надсилаєте листи через SMTP-сервер або кількість реєстрацій дуже велика, слід замислитися над тим, щоб не надсилати листи миттєво. Натомість можна додавати Cron-завдання за допомогою WordPress Cron .

Закриваємо доступ до wp-signup.php та wp-activate.php

Створивши власні сторінки реєстрації та активації, може знадобитися закрити «оригінали». Наприклад, якщо на сторінці реєстрації є додаткові поля, які потрібно заповнити. Також багато WordPress сайтів піддаються спам-реєстраціям.

Вирішити дві проблеми однією дією можна, попросивши Apache віддавати 404 у разі спроби відкриття цих сторінок. Для цього потрібно лише прописати пару додаткових RewriteRule у файл-конфігурацію або .htaccess .

RewriteEngine On RewriteBase / # Знання регулярних виразів ніколи не буде зайвим :) RewriteRule END WordPress

Висновок

Для цієї та багатьох інших проблем пов'язаних з WordPress в інтернеті є безліч рішень. Наприклад, для створення сторінок реєстрації та активації деякі пропонують переписувати оригінальні wp-signup.php та wp-activate.php . Цього не варто робити, тому що у разі оновлення WordPress ви втратите всі зміни, внесені до файлів, а також не зможете перевірити цілісність ядра за допомогою .

При розробці будь-якого доповнення, теми або рішення слід витратити трохи часу на те, щоб розібратися з тим, що відбувається в WordPress. Для цього є безліч корисних інструментів.

P.S.

Для автоматичного призначення різних ролей новим користувачам можна використовувати плагін Multisite User Management.

Якщо у вас виникли питання або труднощі під час створення сторінок реєстрації та активації після прочитання статті, залиште коментар і ми обов'язково відповімо.

27.03.2015 27.03.2015

WordPress розробник. Любить порядок у всьому і розуміється на нових інструментах. Натхненний архітектурою компонентів Symfony.

  • Сьогодні ми розглянемо експлуатацію критичної 1day-уразливості у популярній CMS Joomla, яка прогриміла на просторах інтернету наприкінці жовтня. Йтиметься про вразливості з номерами CVE-2016-8869, CVE-2016-8870і CVE-2016-9081. Усі три походять з одного шматочка коду, який п'ять довгих років нудився в надрах фреймворку в очікуванні свого часу, щоб потім вирватися на волю і принести з собою хаос, зламані сайти та сльози ні в чому не винних користувачів цієї Joomla. Лише найдоблесніші і сміливіші розробники, чиї очі червоні від світла моніторів, а клавіатури завалені хлібними крихтами, змогли кинути виклик нечисті, що розбушувалася, і покласти її голову на вівтар фіксів.

    WARNING

    Вся інформація надана виключно для ознайомлення. Ні редакція, ні автор не несуть відповідальності за будь-яку можливу шкоду, заподіяну матеріалами цієї статті.

    З чого все почалося

    6 жовтня 2016 Деміс Пальма (Demis Palma) створив топік на Stack Exchange , в якому поцікавився: а чому, власне, в Joomla версії 3.6 існують два методи реєстрації користувачів з однаковою назвою register() ? Перший знаходиться в контролері UsersControllerRegistration, а другий - у UsersControllerUser. Деміс хотів дізнатися, чи використовується десь метод UsersControllerUser::register() , або це лише еволюційний анахронізм, що залишився від старої логіки. Його турбував той факт, що навіть якщо цей метод не використовується жодним уявленням, він може бути викликаний за допомогою сформованого запиту. На що отримав відповідь від девелопера під ніком itoctopus, який підтвердив: проблема справді існує. І направив звіт розробникам Joomla.

    Далі події розвивалися найшвидшим чином. 18 жовтня розробники Joomla приймають репорт Деміса, який на той час накидав PoC, що дозволяє реєструвати користувача. Він опублікував замітку на своєму сайті, де загалом розповів про знайдену проблему та думки з цього приводу. Цього ж дня виходить Нова версія Joomla 3.6.3, яка все ще містить вразливий код.

    Після цього Давид Тампелліні (Davide Tampellini) розкручує баг до стану реєстрації не простого користувача, а адміністратора. І вже 21 жовтня команді безпеки Joomla прилітає новий кейс. У ньому вже йдеться про підвищення привілеїв. У цей же день на сайті Joomla з'являється анонс про те, що у вівторок, 25 жовтня, буде випущено чергову версію з порядковим номером 3.6.3, яка виправляє критичну вразливість в ядрі системи.

    25 жовтня Joomla Security Strike Team знаходить останню проблему, яку створює виявлений Деміс шматок коду. Потім у головну гілку офіційного репозиторію Joomla пушиться коміт від 21 жовтня з непримітною назвою Prepare 3.6.4 Stable Release, який фіксує злощасний баг.

    Після цього камін-ауту до міжсобойчика розробників підключаються численні зацікавлені особи - починають розкручувати вразливість і готувати сплоїти.

    27 жовтня дослідник Гаррі Робертс (Harry Roberts) викладає в репозиторій Xiphos Research готовий експлоїт, який може завантажувати PHP-файл на сервер із вразливою CMS.

    Деталі

    Що ж, з передісторією покінчено, переходимо до найцікавішого – розбору вразливості. Як піддослідна версія я встановив Joomla 3.6.3, тому всі номери рядків будуть актуальні саме для цієї версії. А всі шляхи до файлів, які ти побачиш далі, вказуватимуться щодо кореня встановленої CMS.

    Завдяки знахідці Деміса Пальми ми знаємо, що є два методи, які виконують реєстрацію користувача у системі. Перший використовується CMS і знаходиться у файлі /components/com_users/controllers/registration.php:108. Другий (той, що нам і потрібно буде викликати), живе в /components/com_users/controllers/user.php:293. Подивимося на нього ближче.

    286: /** 287: * Method to register a user. 288: * 289: * @return boolean 290: * 291: * @since 1.6 292: */ 293: public function register() 294: ( 295: JSession::checkToken("post") or jexit(JText::_ ("JINVALID_TOKEN"));... 300: // Get the form data.301: $data = $this->input->post->get("user", array(), "array"); .. 315: $return = $model->validate($form, $data); 316: 317: // Check for errors. / Finish the registration 346: $return = $model->register($data);

    Тут я залишив лише цікаві рядки. Повну версію вразливого методу можна переглянути у репозиторії Joomla.

    Розберемося, що відбувається при звичайній реєстрації користувача: які дані надсилаються та як вони обробляються. Якщо реєстрація користувачів включена в налаштуваннях, форму можна знайти за адресою http://joomla.local/index.php/component/users/?view=registration .


    Легітимний запит на реєстрацію користувача виглядає як наступний скріншот.


    За роботу з користувачами відповідає компонент com_users. Зверніть увагу на параметр task у запиті. Він має формат $controller.$method. Погляньмо на структуру файлів.

    Імена скриптів у папці controllersвідповідають назвам контролерів, що викликаються. Так як у нашому запиті зараз $controller = "registration", то викличеться файл registration.phpта її метод register() .

    Увага, питання: як передати обробку реєстрації вразливе місце в коді? Ти, напевно, вже здогадався. Імена вразливого і реального способів збігаються (register), тому нам досить змінити назву викликаного контролера. А де в нас знаходиться вразливий контролер? Правильно, у файлі user.php. Виходить $controller = "user". Збираємо всі разом і отримуємо task=user.register. Тепер запит на реєстрацію опрацьовується потрібним нам методом.


    Друге, що нам потрібно зробити, - це надіслати дані у правильному форматі. Тут усе просто. Легітимний register() чекає від нас масив під назвою jform, в якому ми передаємо дані для реєстрації - ім'я, логін, пароль, пошту (див. скріншот із запитом).

    • /components/com_users/controllers/registration.php: 124: // Get the user data. 125: $requestData = $this->input->post->get("jform", array(), "array");

    Наш підопічний отримує ці дані з масиву з ім'ям user.

    • /components/com_users/controllers/user.php: 301: // Get the form data. 302: $data = $this->input->post->get("user", array(), "array");

    Тому змінюємо у запиті імена всіх параметрів із jfrom на user.

    Третій наш крок - це знаходження валідного токена CSRF, тому що без нього жодної реєстрації не буде.

    • /components/com_users/controllers/user.php: 296: JSession::checkToken("post") або jexit(JText::_("JINVALID_TOKEN"));

    Він виглядає як хеш MD5, а взяти його можна, наприклад, із форми авторизації на сайті /index.php/component/users/?view=login.


    Тепер можна створювати користувачів через потрібний метод. Якщо все вийшло, то вітаю - ти щойно проексплуатував уразливість CVE-2016-8870"відсутня перевірка дозволів на реєстрацію нових користувачів".

    Ось як вона виглядає в робочому методі register() з контролера UsersControllerRegistration:

    • /components/com_users/controllers/registration.php: 113: // If registration is disabled - Redirect to login page. 114: if (JComponentHelper::getParams("com_users")->get("allowUserRegistration") == 0) 115: ( 116: $this->setRedirect(JRoute::_("index.php?option=com_users&view= login", false)); 117: 118: return false; 119: )

    А так у вразливому:

    • /components/com_users/controllers/user.php:

    Ага, ніяк.

    Щоб зрозуміти другу, набагато серйознішу проблему, відправимо сформований нами запит і простежимо, як він виконується на різних ділянках коду. Ось шматок, який відповідає за перевірку надісланих користувачем даних у робочому методі:

    Продовження доступне лише учасникам

    Варіант 1. Приєднайтесь до спільноти «сайт», щоб читати всі матеріали на сайті

    Членство у спільноті протягом зазначеного терміну відкриє тобі доступ до ВСІХ матеріалів «Хакера», збільшить особисту накопичувальну знижку та дозволить накопичувати професійний рейтинг Xakep Score!

    Створюємо власну сторінку реєстрації для мультисайту замість стандартної wp-signup.php.

    У звичайному встановленні WordPress сторінку реєстрації (авторизації, скидання пароля) виводить файл wp-login.php .

    • /wp-login.php - авторизація
    • /wp-login.php?action=register - реєстрація
    • /wp-login.php?action=lostpassword - скидання пароля

    Для мультисайту у wp-login.php є окремі умови. Так, при переході за посиланням /wp-login.php?action=register на мультисайті WordPress зробить редирект на сторінку /wp-signup.php . У багатьох темах сторінка виглядає не дуже привабливо, тому ми зробимо власну.

    Основний сайт мережі

    За промовчанням WordPress відкриває сторінку реєстрації (wp-signup.php) на основному домені (сайті) мережі. Проте, можна зробити окрему сторінку реєстрації для кожного сайту мережі, навіть якщо вони мають різні теми. Ми розглядатимемо випадок, коли на всіх сайтах мережі є своя власна сторінка реєстрації, але використовується однакова тема і сайти відрізняються лише мовою. Якщо використовуються різні теми, потрібно написати більше коду.

    functions.php?

    Ні. Ім'я цього файлу, здається, згадується у будь-якій статті про WordPress. У нашому випадку, з урахуванням того, що функціонал реєстрації розрахований на кілька сайтів, є сенс винести його в MU-плагіни, які завантажуються при відкритті будь-якого сайту.

    Ліричний відступ

    Варто зазначити, що MU-плагіни завантажуються раніше за звичайні плагіни і до повного завантаження ядра WordPress, тому виклик деяких функцій може призвести до фатальних помилок у PHP. Подібне «раннє» завантаження має свої плюси. Скажімо, всередині будь-якої теми не можна чіплятися до деяких екшенів, які спрацьовують ще до завантаження файлу functions.php з теми. Прикладом цього можуть бути екшени з плагіну Jetpack виду jetpack_module_loaded_related-posts (related-posts - назва модуля) за допомогою яких можна відстежувати активність модулів в Jetpack. До цього екшену неможливо "причепитися" з файлу теми, тому що екшен вже спрацював до завантаження теми - плагіни завантажуються раніше. Подивитися на загальну картинку порядку завантаження WordPress можна на сторінці Action Reference у кодексі.

    Порядок у файлах

    MU-плагіни можуть містити будь-яку кількість файлів та будь-яку структуру, яка здасться вам логічною. Я дотримуюсь приблизно такої ієрархії:

    |-mu-plugins |-|-load.php |-|-|-selena-network |-|-|-|-signup |-|-|-|-|-plugin.php |-|-|-| -|-... |-|-|-|-jetpack |-|-|-|-|-plugin.php

    У файлі load.php підключаються всі необхідні плагіни для нашої мережі:

    // Load Traslates for all addons load_muplugin_textdomain ("selena_network", "/selena-network/languages/"); // Network Signup require WPMU_PLUGIN_DIR . "/selena-network/signup/plugin.php"; // Інші plugins // require WPMU_PLUGIN_DIR ...

    Усередині папки selena-network зберігаються папки плагінів, у кожній є свій plugin.php , які ми підключаємо в load.php . Це дає гнучкість та можливість швидко відключати та включати деякі речі.

    Адреса сторінки реєстрації

    Щоб вказати адресу сторінки реєстрації, використовується фільтр wp_signup_location . Його можна знайти всередині файлу wp-login.php і саме він відповідає за редирект на wp-signup.php.

    Case "register" : if (is_multisite()) ( wp_redirect(apply_filters("wp_signup_location", network_site_url("wp-signup.php"))));

    Додамо свою функцію до mu-plugins/selena-network/signup/plugin.php , яка буде віддавати адресу сторінки реєстрації на поточному сайті:

    Function selena_network_signup_page ($url) ( return home_url () . "/signup/"; ) add_filter ("wp_signup_location", "selena_network_signup_page", 99);

    selena_network - префікс, який я використовую в іменах всіх функцій усередині MU-плагінів на своєму сайті для запобігання колізії, його слід замінити на свій власний унікальний префікс. Пріоритет додавання фільтра 99, тому що деякі плагіни, наприклад bbPress і BuddyPress можуть перезаписати цю адресу на свою власну (MU-плагіни завантажуються раніше, ніж звичайні плагіни, див вище). Зверніть увагу, що використовується home_url() замість network_site_url() , щоб залишити відвідувача на тому ж домені. Як адресу можна використовувати будь-яку URL-адресу.

    Створення сторінки

    Тепер створимо сторінку з адресою site.com/signup/ через звичайний інтерфейс, а у папці дочірньої теми шаблон для нашої нової сторінки- page-signup.php. Замість слова signup можна використовувати унікальний ID.

    Всередині нового шаблону необхідно виконати функцію selena_network_signup_main() , яка виводитиме форму реєстрації.

    Варто зауважити, що весь процес із шаблонами не є обов'язковим і замість цього можна створити свій шорткод, який також викликатиме функцію selena_network_signup_main() .

    wp-signup.php та wp-activate.php

    Тепер займемося створенням функції, яка виводитиме форму реєстрації. Для цього скопіюємо файли wp-signup.php та wp-activate.php з кореня WordPress у mu-plugings/selena-network/signup/ (і не забуваємо їх підключити всередині mu-plugins/selena-network/signup/plugin.php) . Подальші маніпуляції з файлами дуже складно і довго описувати, тому доведеться зробити їх самостійно. Я лише опишу, що саме треба зробити і опублікую вихідні файли свого проекту:

    1. На початку файлу видалити всі require , виклик функцій та інший код поза функціями.
    2. Перейменувати всі функції, додавши унікальні префікси до імен.
    3. Нижню частину коду wp-signup.php обернути в функцію selena_network_signup_main і на початку написати global $active_signup; .
    4. Замінити верстку на власну в потрібних місцях.

    Всередині wp-activate.php необхідно зробити приблизно те саме:

    1. Видалити весь код поза функціями, обернути верстку на окрему функцію.
    2. Змінити верстку у місцях, де це необхідно.

    Файл wp-activate.php відповідає за сторінку активації облікового запису. Як і зі сторінкою реєстрації, для неї необхідно створити окремий шаблон, усередині якого викликати функцію з файлу wp-activate.php .

    Надсилаємо листи активації

    Сторінка реєстрації надсилає відвідувачу листа з посиланням на активацію облікового запису. За замовчуванням цим займається функція wpmu_signup_user_notification() із файлу ms-functions.php . Її функціонал можна запозичувати для своєї функції. Причина, через яку необхідно відмовитися від використання цієї функції - вона відправляє посилання активації облікового запису з wp-activate.php . "Вимкнути" ж цю функцію можна за допомогою фільтра wpmu_signup_user_notification віддаючи по ньому false (якщо цього не зробити, лист активації буде відправлятися двічі, окей, насправді два різні листи).

    Function armyofselenagomez_wpmu_signup_user_notification($user, $user_email, $key, $meta = array()) ( // ... // Код з функції wpmu_signup_user_notification() wp_mail($user_email, wp_specialchars_decode($subject), ; return false; ) add_filter("wpmu_signup_user_notification", "armyofselenagomez_wpmu_signup_user_notification", 10, 4);

    В результаті сторінка реєстрації в темі Селена стала виглядати набагато чистіше та акуратніше.

    Висновок

    В інтернеті безліч інших не дуже правильних способів того, як зробити те саме - редиректи Apache, AJAX-форми, які не працюватимуть без Java Scriptі т. п. Все це мені не дуже сподобалося, тому я постарався зробити це максимально правильно на власному сайті.

    Зауважу, що правити файли слід обережно і намагатися не сильно відходити від вихідних, щоб у подальшому, якщо WordPress змінить файли wp-signup.php і wp-activate.php, їх простіше було порівнювати між собою для пошуку змін.

    Не забувайте дивитися в вихідний кодвсіх описаних вище функцій повністю розібратися з тим, що і як відбувається всередині коду.

    Бонус. Захист від спамерів

    Навіть найменші сайти на WordPress часто зазнають нальоту спам-реєстрацій. Можна писати нескінченні умови для фільтрації ботів, які часто більше схожі на спробу створити штучний інтелект🙂 У разі мультисайту мені дуже допоміг звичайний редирект в Apache, за допомогою якого при відкритті /wp-signup.php та /wp-acitvate.php я попросив видавати 404 (я не експерт з налаштування Apache, тому мої правила можуть бути не дуже правильними ).

    RewriteEngine On RewriteBase / RewriteRule ^wp-signup\.php - RewriteRule ^wp-activate\.php - # BEGIN WordPress # Правила від WordPress за замовчуванням не чіпаємо:) # ... # END WordPress

    PS Я намагаюся максимально детально описувати деякі сторонні речі, тому що коли починав я, часом не було кому підказати і пояснити багато речей. Також я вважаю, що подібні невеликі наведення на інші матеріали когось підштовхнуть до вивчення чогось нового та розширення своєї галузі знань. У записах RewriteRule використовуються Регулярні вирази, вони зовсім не складні, наприклад, символ означає початок рядка.

    Одноразові посилання можна використовувати в різних ситуаціях: для того, щоб надати тимчасовий доступ до файлу або сторінки, або для підтвердження реєстрації. У цьому уроці ми покажемо, як згенерувати та впровадити одноразові URL-адреси.

    Створення URL

    Припустимо, що у нас на сайті є система автентифікації користувачів. Після реєстрації ми просимо користувача пройти процедуру верифікації електронної пошти. Для створення подібних посилань можемо скористатися спеціальним параметром token. Приклад такого посилання:

    Http://example.com/activate?token=ee97780...

    Без бази даних тут нам не обійтися, тому давайте подивимося на таблицю, з якою працюватимемо.

    CREATE TABLE pending_users (token CHAR(40) NOT NULL, username VARCHAR(45) NOT NULL, tstamp INTEGER UNSIGNED NOT NULL, PRIMARY KEY(token));

    У таблиці зберігатимемо 3 поля: токен, ім'я користувача та час. Для генерації токена ми скористаємося функцією sha1(), яка видає рядок із 40 символів. Поле tstamp буде зберігати час генерації токена для того, щоб ми могли відстежити посилання з терміном, що минув.

    Існує безліч способів генерації токена, однак у цьому уроці ми скористаємося функціями uniqid() та sha1(). Незалежно від способу генерації токена, переконайтеся, що значення, що генеруються, будуть різними і ймовірність появи дублікатів мінімальна.

    $token = sha1(uniqid($username, true));

    Як параметр функція uniqid() приймає рядок, а на виході дає унікальний ідентифікатор, що базується на переданому аргументі та поточному часі. Також, як другий аргумент, дана функція набуває булевого значення, яке дасть сигнал uniqid додати кілька додаткових символів для збільшення ймовірності унікальності значення. Функція sha1 приймає унікальний ідентифікатор та створює хеш.

    Після роботи цих двох функцій, ми матимемо унікальний токен, який можемо використовувати для генерації url адрес. Тепер нам потрібно занести його до бази:

    $query = $db->prepare("INSERT INTO pending_users (username, token, tstamp) VALUES (?, ?, ?)"); $query->execute(array($username, $token, $_SERVER["REQUEST_TIME"])));

    Щоб ми знали, якого користувача слід активувати, в таблицю записуватимемо і логін користувача. У прикладі, більш адаптованому для реального сайту, можете скористатися ID користувача.

    Тепер, коли ми маємо всю необхідну інформацію, можемо створити тимчасову url адресу:

    $url = "http://example.com/activate.php?token=$token";

    $message =<<

    Перевірка

    Тепер нам потрібен скрипт, завдяки якому ми здійснюватимемо перевірку. Все, що нам потрібно зробити, це порівняти токен з url адреси і токен з бази. Якщо така є, і час її життя не минув, то все ОК.

    // отримуємо токен if (isset($_GET["token"]) && preg_match("/^(40)$/i", $_GET["token"])) ( $token = $_GET["token"] ; ) else ( throw new Exception("токен не валідний."); ) // перевіряємо токен $query = $db->prepare("SELECT username, tstamp FROM pending_users WHERE token = ?"); $query->execute(array($token)); $row = $query->fetch(PDO::FETCH_ASSOC); $query->closeCursor(); if ($row) ( extract($row); ) else ( throw new Exception("токен не валідний."); ) // активуємо акаунт користувача // ... // видаляємо токен з бази $query = $db- >prepare("DELETE FROM pending_users WHERE username = ? AND token = ? AND tstamp = ?",); $query->execute(array($username, $token, $tstamp));

    Також нам потрібно передбачити перевірку токенів, час життя яких минув.

    // 1 день на секундах = 60 секунд * 60 хвилин * 24 години $delta = 86400; // перевірка if ($_SERVER["REQUEST_TIME"] - $tstamp > $delta) ( throw new Exception("час життя токена минув."); ) // активуємо обліковий запис користувача // ...

    Таким чином, у нас будуть здійснюватись дві перевірки: одна на валідність токена, інша на час його існування.

    Підсумок

    Цей метод можна застосувати не тільки для активації облікових записівкористувачів, а й у інших потребах: наприклад, надання одноразового чи тимчасового доступу до якогось ресурсу чи послузі.

    До того ж, ви можете створити скрипт, який видалятиме токени, якими жодного разу не користувалися. Даний скрипт можна запускати самим іноді або застосувати для цього cron.