<?php
|
namespace JVBase\managers;
|
|
use JVBase\meta\MetaManager;
|
use WP_Error;
|
use WP_User;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
|
class LoginManager
|
{
|
private array|null $invitation_data = null;
|
protected array $inviteData = [];
|
private array $allowed_file_types = [
|
'image/jpeg',
|
'image/png',
|
'image/gif',
|
'application/pdf'
|
];
|
private int $max_file_size = 5242880; // 5MB in bytes
|
|
public function __construct()
|
{
|
// Common login page customization
|
add_action('login_enqueue_scripts', array($this, 'loginStyles'));
|
add_action('login_header', array($this, 'loginHeader'), 0);
|
add_action('login_footer', array($this, 'loginFooter'));
|
|
// Login page filters
|
add_filter('login_headerurl', array($this, 'logoUrl'));
|
add_filter('login_headertext', array($this, 'logoTitle'));
|
add_filter('login_message', array($this, 'loginMessage'));
|
add_filter('login_errors', array($this, 'loginErrors'));
|
|
// Login success handling
|
add_action('wp_login', [$this, 'handleSuccessfulLogin'], 10, 2);
|
|
// Registration-specific hooks
|
if ($this->isRegistrationPage()) {
|
$this->initRegistrationHooks();
|
}
|
}
|
|
/**
|
* Check if we're on the registration page
|
*/
|
private function isRegistrationPage(): bool
|
{
|
return isset($_GET['action']) && $_GET['action'] === 'register';
|
}
|
|
/**
|
* Initialize registration-specific hooks
|
*/
|
private function initRegistrationHooks(): void
|
{
|
add_action('register_form', array($this, 'addRegistrationFields'));
|
add_action('login_header', array($this, 'addRegistrationScript'));
|
add_filter('registration_errors', array($this, 'registrationErrorsFilter'), 10, 3);
|
add_action('user_register', array($this, 'saveRegistrationFields'), 999, 2);
|
add_action('login_head', array($this, 'modifyRegistrationForm'));
|
add_action('register_form', array($this, 'addUploadSupport'));
|
add_filter('pre_user_login', array($this, 'setUserLogin'), 1);
|
add_filter('pre_user_email', array($this, 'setUserEmail'), 1);
|
add_filter('register_message', array($this, 'customRegisterMessage'));
|
add_filter('wp_login_errors', array($this, 'registrationSuccessMessage'), 10, 2);
|
add_filter('login_form_top', array($this, 'loginFormTop'));
|
add_filter('login_form_bottom', array($this, 'loginFormBottom'));
|
add_filter('login_form_middle', array($this, 'loginFormMiddle'));
|
|
// Remove default username requirement for registration
|
remove_filter('registration_errors', 'registration_auth_pass_filter', 10);
|
}
|
|
/**
|
* Combined login styles for both login and registration
|
*/
|
public function loginStyles(): void
|
{
|
do_action('jvbLoginStyles');
|
}
|
|
/**
|
* Login header - used for both login and registration
|
*/
|
public function loginHeader(): void
|
{
|
?>
|
<script type="text/javascript">
|
document.addEventListener('DOMContentLoaded', function() {
|
let loginLabel = document.querySelector('label[for="user_login"');
|
loginLabel.innerHTML = '<?= jvbIcon('email', ['size' => 20]); ?> Your Email';
|
|
let passwordLabel = document.querySelector('label[for="user_pass"');
|
passwordLabel.innerHTML = '<?= jvbIcon('password', ['size' => 20]); ?> Your Password';
|
|
document.querySelector('form').classList.add('loaded');
|
});
|
|
</script>
|
<?php
|
}
|
|
/**
|
* Login footer with donate section
|
*/
|
public function loginFooter(): void
|
{
|
do_action('jvbLoginFooter');
|
|
}
|
|
/**
|
* Logo URL
|
*/
|
public function logoUrl(): string
|
{
|
return home_url();
|
}
|
|
/**
|
* Logo title
|
*/
|
public function logoTitle(): string
|
{
|
return get_bloginfo('name');
|
}
|
|
/**
|
* Login message - handles both login and registration
|
*/
|
public function loginMessage(string $message): string
|
{
|
if ($this->isRegistrationPage()) {
|
if (jvbSiteHasInvitations() && $this->fromInvite()) {
|
$data = JVB()->routes('invites')->verifyInvitation(sanitize_text_field($_GET['invite']), sanitize_email($_GET['email']));
|
$name = $data->name;
|
$inviters = json_decode($data->inviters, true);
|
$names = [];
|
foreach ($inviters as $inviter) {
|
$artist = jvbContentFromUser((int)$inviter['user_id']);
|
$names[] = ($artist['name'] === '') ? $artist['display_name'] : $artist['name'];
|
}
|
$message = (count($names) > 1) ? 'are already here, and have invited you to join in!' : ' is already here, and invited you to join in!';
|
return '<h2>Join the Scene, '.$name.'</h2>
|
<p style="text-align:center;">'.jvbCommaList($names).$message.'</p>';
|
}
|
if (jvbSiteHasFavourites() && $this->fromFavourites()) {
|
return '<h2>'.JVB_LOGIN['login_from_favourite_header']??'Save your Favourites'.'</h2>';
|
}
|
return '<h2>'.JVB_LOGIN['join_header'].'</h2>';
|
} else {
|
if (jvbSiteHasFavourites()) {
|
$login = (!$this->fromFavourites()) ? '<h2>'.JVB_LOGIN['login_header'].'</h2>' : '<h2>'.JVB_LOGIN['login_from_favourite_header'].'</h2>';
|
} else {
|
$login = '<h2>'.JVB_LOGIN['login_header'].'</h2>';
|
}
|
|
return (empty($message)) ? $login : $login.$message;
|
}
|
}
|
|
protected function fromFavourites():bool
|
{
|
return array_key_exists('type', $_GET) && $_GET['type'] === 'favourites';
|
}
|
|
/**
|
* Customize login error messages
|
*/
|
public function loginErrors(string $error): string
|
{
|
return str_replace(
|
array(
|
'The password you entered for the username',
|
'Invalid username',
|
'Unknown username',
|
'Unknown email address'
|
),
|
array(
|
'Wrong password',
|
'We can\'t find that username',
|
'We can\'t find that username',
|
'We can\'t find that email'
|
),
|
$error
|
);
|
}
|
|
/**
|
* Handle successful login
|
*/
|
public function handleSuccessfulLogin(string $username, WP_User $user): void
|
{
|
if (isOurPeople() && !user_can($user, 'manage_options')) {
|
wp_redirect(get_home_url(null, '/dash'));
|
exit;
|
}
|
}
|
|
// ===== REGISTRATION-SPECIFIC METHODS =====
|
|
/**
|
* Set user login for registration
|
*/
|
public function setUserLogin(string $login): string
|
{
|
$user_type = isset($_POST['user_type']) ? $_POST['user_type'] : '';
|
if (!empty($user_type)) {
|
$email_field = $user_type . '_email';
|
if (isset($_POST[$email_field])) {
|
$email = sanitize_email($_POST[$email_field]);
|
if (is_email($email)) {
|
return $email;
|
}
|
}
|
}
|
return $login;
|
}
|
|
/**
|
* Set user email for registration
|
*/
|
public function setUserEmail(string $email): string
|
{
|
$user_type = isset($_POST['user_type']) ? $_POST['user_type'] : '';
|
if (!empty($user_type)) {
|
$email_field = $user_type . '_email';
|
if (isset($_POST[$email_field])) {
|
$email = sanitize_email($_POST[$email_field]);
|
if (is_email($email)) {
|
return $email;
|
}
|
}
|
}
|
return $email;
|
}
|
|
/**
|
* Modify registration form
|
*/
|
public function modifyRegistrationForm(): void
|
{
|
if (!$this->isRegistrationPage()) {
|
return;
|
}
|
|
?>
|
<script type="text/javascript">
|
document.addEventListener('DOMContentLoaded', function() {
|
// Hide default fields
|
const defaultFields = document.getElementById('registerform').querySelectorAll('p');
|
defaultFields.forEach(field => {
|
if (field.querySelector('label[for="user_login"]') ||
|
field.querySelector('label[for="user_email"]')) {
|
field.remove();
|
}
|
});
|
|
// Hide the default registration info text
|
const regInfo = document.querySelector('.message.register');
|
if (regInfo) {
|
regInfo.style.display = 'none';
|
}
|
|
<?php
|
if ($this->fromInvite()) {
|
$this->handleArtistInvitation();
|
}
|
?>
|
|
// Move submit button to the end of the form
|
const submitButton = document.getElementById('registerform').querySelector('.submit');
|
if (submitButton) {
|
document.getElementById('registerform').appendChild(submitButton);
|
}
|
});
|
</script>
|
<?php
|
}
|
|
/**
|
* Handle artist invitation pre-fill
|
*/
|
protected function handleArtistInvitation(): void
|
{
|
$token = sanitize_text_field($_GET['invite']);
|
$email = sanitize_email($_GET['email']);
|
$data = JVB()->routes('invites')->verifyInvitation($token, $email);
|
|
?>
|
document.querySelector('input#artist').checked = true;
|
document.querySelector('#artist_first_name').value = '<?=$data->name?>';
|
document.querySelector('#artist_email').value = '<?=$email?>';
|
<?php
|
if ($data->to_shop) {
|
?>
|
document.querySelector('#artist_shop').value = '<?=$data->shop?>';
|
<?php
|
}
|
?>
|
let form = document.getElementById('registerform')
|
let input = document.createElement('input');
|
let email = input.cloneNode(true);
|
input.type = 'hidden';
|
input.name = 'invite_token';
|
input.value = '<?= $token ?>';
|
email.type = 'hidden';
|
email.name = 'invite_email';
|
email.value = '<?= $email?>';
|
form.append(input);
|
form.append(email);
|
<?php
|
}
|
|
/**
|
* Add upload support for registration
|
*/
|
public function addUploadSupport(): void
|
{
|
?>
|
<script>
|
document.addEventListener('DOMContentLoaded', function() {
|
const form = document.getElementById('registerform');
|
if (form) {
|
form.enctype = 'multipart/form-data';
|
}
|
});
|
</script>
|
<?php
|
}
|
|
/**
|
* Add registration script
|
*/
|
public function addRegistrationScript(): void
|
{
|
if (!$this->isRegistrationPage()) {
|
return;
|
}
|
?>
|
<script>
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize user type selection
|
function initUserTypeSelection() {
|
const userTypeRadios = document.querySelectorAll('input[name="user_type"]');
|
const fieldGroups = document.querySelectorAll('.field-group');
|
|
userTypeRadios.forEach(radio => {
|
radio.addEventListener('change', function() {
|
fieldGroups.forEach(group => group.classList.remove('active'));
|
const selectedType = this.value;
|
const targetGroup = document.querySelector(`.field-group[data-type="${selectedType}"]`);
|
if (targetGroup) {
|
targetGroup.classList.add('active');
|
}
|
});
|
});
|
|
const checkedRadio = document.querySelector('input[name="user_type"]:checked');
|
if (checkedRadio) {
|
const targetGroup = document.querySelector(`.field-group[data-type="${checkedRadio.value}"]`);
|
if (targetGroup) {
|
targetGroup.classList.add('active');
|
}
|
}
|
}
|
|
// Initialize shop selection
|
function initShopSelection() {
|
let form = document.getElementById('registerform');
|
form.addEventListener('change', (e) => {
|
if(e.target.id === 'artist_shop' || e.target.id === 'artist_city'){
|
let next = e.target.parentNode.nextElementSibling;
|
let input = next.querySelector('input');
|
|
if(e.target.value === 'other'){
|
next.style.display = 'block';
|
next.style.animation = 'fadeIn 0.3s ease';
|
input.required = true;
|
input.focus();
|
}else{
|
input.required = false;
|
input.value = '';
|
}
|
}
|
});
|
}
|
|
// Initialize file upload handling
|
function initFileUpload() {
|
const fileInput = document.getElementById('certification_file');
|
const filePreview = document.querySelector('.file-preview');
|
const filePreviewName = document.querySelector('.file-preview-name');
|
const fileError = document.querySelector('.file-error');
|
const removeButton = document.querySelector('.file-preview-remove');
|
|
if (!fileInput || !filePreview || !filePreviewName || !fileError || !removeButton) {
|
return;
|
}
|
|
const maxSize = parseInt(fileInput.dataset.maxSize || 5242880);
|
|
fileInput.addEventListener('change', function(e) {
|
const file = e.target.files[0];
|
fileError.classList.remove('active');
|
|
if (file) {
|
const validTypes = ['.jpg','.jpeg','.png','.gif','.pdf'];
|
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
|
|
if (!validTypes.includes(fileExtension)) {
|
showError('Please upload a valid file type (JPG, PNG, GIF, or PDF)');
|
fileInput.value = '';
|
return;
|
}
|
|
if (file.size > maxSize) {
|
showError('File size must be less than 5MB');
|
fileInput.value = '';
|
return;
|
}
|
|
filePreviewName.textContent = file.name;
|
filePreview.classList.add('active');
|
} else {
|
filePreview.classList.remove('active');
|
}
|
});
|
|
removeButton.addEventListener('click', function() {
|
fileInput.value = '';
|
filePreview.classList.remove('active');
|
fileError.classList.remove('active');
|
});
|
|
function showError(message) {
|
fileError.textContent = message;
|
fileError.classList.add('active');
|
filePreview.classList.remove('active');
|
}
|
}
|
|
// Initialize all components
|
initUserTypeSelection();
|
initShopSelection();
|
initFileUpload();
|
});
|
</script>
|
<?php
|
}
|
|
/**
|
* Add registration fields
|
*/
|
public function addRegistrationFields(): void
|
{
|
echo '<input type="hidden" name="user_pass" value="' . wp_generate_password() . '">';
|
?>
|
<div class="registration-intro">
|
<?php
|
foreach (JVB_LOGIN['join_intro']??[] as $intro) {
|
echo '<p>'.$intro.'</p>';
|
}
|
?>
|
|
<?php if ($this->fromFavourites()): ?>
|
<div class="favourites-login-message">
|
<ul class="benefits-list">
|
<?php
|
foreach (JVB_LOGIN['from_favourites_benefits']??[] as $benefit) {
|
echo '<li>'.$benefit.'</li>';
|
}
|
?>
|
</ul>
|
</div>
|
<?php endif; ?>
|
</div>
|
|
<?php
|
if (array_key_exists('choose', JVB_LOGIN)) {
|
?>
|
<h3><?= JVB_LOGIN['choose']?></h3>
|
<?php
|
}
|
?>
|
|
<?php
|
if (count(JVB_USER) > 1) {
|
$this->renderUserTypeSelection();
|
} else {
|
?>
|
<p>
|
<label for="first_name" class="required-field">First Name</label>
|
<input type="text" id="first_name" name="first_name" class="input">
|
</p>
|
<p>
|
<label for="email" class="required-field">Email</label>
|
<input type="email" id="email" name="email" class="input">
|
</p>
|
<?php
|
}
|
if ($this->invitation_data) {
|
?>
|
<script>
|
document.addEventListener('DOMContentLoaded', function() {
|
const artistRadio = document.getElementById('artist');
|
if (artistRadio) {
|
artistRadio.checked = true;
|
artistRadio.dispatchEvent(new Event('change'));
|
}
|
|
const emailField = document.getElementById('artist_email');
|
if (emailField) {
|
emailField.value = '<?= esc_js($this->invitation_data['email']); ?>';
|
emailField.readOnly = true;
|
}
|
|
const shopSelect = document.getElementById('artist_shop');
|
if (shopSelect) {
|
shopSelect.value = '<?= esc_js($this->invitation_data['shop_id']); ?>';
|
shopSelect.readOnly = true;
|
}
|
});
|
</script>
|
<input type="hidden" name="invitation_token" value="<?= sanitize_text_field($_GET['invite']) ?>">
|
<input type="hidden" name="invitation_email" value="<?= sanitize_email($_GET['email']) ?>">
|
<?php
|
}
|
}
|
|
protected function renderUserTypeSelection():void
|
{
|
|
|
// Get list of tattoo shops and cities
|
$shops = get_terms(array(
|
'taxonomy' => 'jvb_shop',
|
'hide_empty' => true
|
));
|
|
$cities = get_terms(array(
|
'taxonomy' => 'jvb_city',
|
'hide_empty' => false,
|
));
|
?>
|
<div class="user-type-section">
|
|
<?php
|
$i = 1;
|
$radio = '<input type="radio" id="user0" name="user_type" value="subscriber" required checked>
|
<label for="user0"></label>';
|
$descriptions = '';
|
foreach (JVB_USER as $role => $config) {
|
if (jvbCheck('can_register', $config)) {
|
$radio .= '<input type="radio" id="user'.$i.'" name="user_type" value="'.$role.'" required';
|
$radio .= ($role === 'enthusiast' && $this->fromFavourites()) ? 'checked' : '';
|
$radio .= '><label for="user'.$i.'">'.jvbIcon($role, ['title' =>$config['label'], 'size'=>40]).'<h4>'.$config['label'].'</h4><p>';
|
$radio .= $config['join_text']??'';
|
$radio .= '</p></label>';
|
|
$descriptions .= '<div class="user'.$i.'">'.is_array($config['join_description']) ? implode('', array_map(function ($item) { return '<p>'.$item.'</p>'; }, $config['join_description'])) : '<p>'.$config['join_description'].'</p>'.'</div>';
|
|
$i++;
|
}
|
}
|
|
echo $radio;
|
echo $descriptions;
|
?>
|
<input type="radio" id="enthusiast" name="user_type" value="enthusiast" required <?= ($this->fromFavourites()) ? 'checked' : '' ?>>
|
<label for="enthusiast"><?=jvbIcon('heart', ['title' =>'Enthusiast', 'size'=>40])?><h4>Enthusiast</h4><p>Start here.</p></label>
|
<input type="radio" id="artist" name="user_type" value="artist" required>
|
<label for="artist"><?=jvbIcon('tattoo', ['title'=> 'Artist', 'size'=> 40])?><h4>Artist</h4><p>Show your talent.</p></label>
|
<input type="radio" id="partner" name="user_type" value="partner" required>
|
<label for="partner"><?=jvbIcon('partner', ['title'=>'Partner', 'size' => 40])?><h4>Partner</h4><p>Support the community.</p></label>
|
<p class="enthusiast">Save your favourites. Get notified.</p>
|
<p class="artist">Show off your work.</p>
|
<p class="partner">Support the community.</p>
|
</div>
|
|
<!-- Enthusiast Fields -->
|
<div class="field-group" data-type="enthusiast">
|
<h4>Welcome to the scene.</h4>
|
<p>Sign up with your email to:</p>
|
<ul>
|
<li>Save your favourites for easy access</li>
|
<li>Get notified when your favourite artists add new content</li>
|
<li>Stay in the loop with local flash days and events</li>
|
<li>Discover styles and artists that match your vision</li>
|
</ul>
|
<p>
|
<label for="enthusiast_first_name" class="required-field">First Name</label>
|
<input type="text" id="enthusiast_first_name" name="enthusiast_first_name" class="input">
|
</p>
|
<p>
|
<label for="enthusiast_email" class="required-field">Email</label>
|
<input type="email" id="enthusiast_email" name="enthusiast_email" class="input">
|
</p>
|
<div><p><b>BONUS</b>: Everything's free. And always will be. We work with partners chosen by and for the community to keep the lights on.</p></div>
|
</div>
|
|
<!-- Artist Fields -->
|
<div class="field-group" data-type="artist">
|
<h4>Welcome to the scene!</h4>
|
<p>We'll start small, with the basics. Before your profile goes live, we need to verify:</p>
|
<ul>
|
<li>you are who you say you are</li>
|
<li>you work at the shop you listed</li>
|
<li>your certification</li>
|
</ul>
|
<p>
|
<label for="artist_first_name" class="required-field">First Name</label>
|
<input type="text" id="artist_first_name" name="artist_first_name" class="input">
|
</p>
|
<p>
|
<label for="artist_last_name" class="required-field">Last Name</label>
|
<input type="text" id="artist_last_name" name="artist_last_name" class="input">
|
</p>
|
<p>
|
<label for="artist_email" class="required-field">Email</label>
|
<input type="email" id="artist_email" name="artist_email" class="input">
|
</p>
|
<p>
|
<label for="artist_shop" class="required-field">Shop</label>
|
<select id="artist_shop" name="artist_shop" class="input">
|
<option value="">Select a shop</option>
|
<option value="other">Add New Shop</option>
|
<?php foreach ($shops as $shop) : ?>
|
<option value="<?= esc_attr($shop->term_id); ?>"><?= esc_html($shop->name); ?></option>
|
<?php endforeach; ?>
|
</select>
|
</p>
|
<p id="other_shop_field" style="display: none;">
|
<label for="artist_shop_other" class="required-field">Shop Name</label>
|
<input type="text" id="artist_shop_other" name="artist_shop_other" class="input" placeholder="Shop name">
|
</p>
|
|
<p>
|
<label for="artist_type" class="required-field">Type</label>
|
<input type="radio" id="type-tattoo-artist" name="artist_type" value="tattoo-artist">
|
<label for="type-tattoo-artist">Tattoo Artist</label>
|
<input type="radio" id="type-piercer" name="artist_type" value="piercer">
|
<label for="type-piercer">Piercer</label>
|
<input type="radio" id="type-other" name="artist_type" value="other">
|
<label for="type-other">Other</label>
|
</p>
|
<p>
|
<label for="artist_city" class="required-field">City</label>
|
<select id="artist_city" name="artist_city" class="input">
|
<option value="">Select a city</option>
|
<option value="other">Add New City</option>
|
<?php foreach ($cities as $city) : ?>
|
<option value="<?= esc_attr($city->term_id); ?>"><?= esc_html($city->name); ?></option>
|
<?php endforeach; ?>
|
</select>
|
</p>
|
<p id="other_city_field" style="display: none;">
|
<label for="artist_city_other" class="required-field">City Name</label>
|
<input type="text" id="artist_city_other" name="artist_city_other" class="input" placeholder="City">
|
</p>
|
|
<div class="file-upload-container">
|
<label class="file-upload-label">Certification or Training Documents</label>
|
<p><i>Optional</i> — If you've been certified in bloodborne pathogen safety, or any other tattoo safety course, pass along your certificate. This just eases the verification process.</p>
|
<div class="file-upload-wrapper">
|
<input type="file" name="certification_file" id="certification_file" accept=".jpg,.jpeg,.png,.gif,.pdf" data-max-size="<?= $this->max_file_size; ?>">
|
<p class="file-upload-text">
|
<strong>Click to upload</strong> or drag and drop<br>
|
JPG, PNG, GIF or PDF (max. 5MB)
|
</p>
|
</div>
|
<div class="file-preview">
|
<div class="file-preview-content">
|
<span class="file-preview-name"></span>
|
<button type="button" class="file-preview-remove">Remove</button>
|
</div>
|
</div>
|
<div class="file-error"></div>
|
</div>
|
<p>Once you click register:</p>
|
<ul>
|
<li>We'll start looking into your information (usually within 24-48 hours)</li>
|
<li>You'll get a password reset email</li>
|
<li>Upon setting your password, you can start filling in your profile - but it won't go live until we've verified your information.</li>
|
</ul>
|
<p>If you have any questions or concerns - or anything you'd like to follow up on - email us at get@edmonton.ink or message us on <a target="_blank" href="https://www.instagram.com/edmonton.ink/" title="@edmonton.ink on Instagram">Instagram</a>.</p>
|
<div><p><b>BONUS</b>: Everything's free. And always will be. We work with partners chosen by and for the community to keep the lights on.</p></div>
|
</div>
|
|
<!-- Partner Fields -->
|
<div class="field-group" data-type="partner">
|
<h4>Howdy, partner!</h4>
|
<p>We appreciate your interest!</p>
|
<p>edmonton.ink is a great place to showcase what you do, whether you:</p>
|
<ul>
|
<li>provide goods or services that tattoo artists could use</li>
|
<li>provide goods or services that are tattoo adjacent (such as art, merch, etc)</li>
|
<li>provide goods or services that folks who love tattoos could also love</li>
|
</ul>
|
|
<p>We'll start with some basics, then we'll reach out to follow up (usually within 24-48 hours).</p>
|
<p>
|
<label for="partner_name" class="required-field">Contact Name</label>
|
<input type="text" id="partner_name" name="partner_name" class="input">
|
</p>
|
<p>
|
<label for="partner_email" class="required-field">Email</label>
|
<input type="email" id="partner_email" name="partner_email" class="input">
|
</p>
|
<p>
|
<label for="partner_business" class="required-field">Business Name</label>
|
<input type="text" id="partner_business" name="partner_business" class="input">
|
</p>
|
<p>
|
<label for="partner_website">Business Website</label>
|
<input type="url" id="partner_website" name="partner_website" class="input">
|
</p>
|
<p>
|
<label for="partner_description">Why would you be a good fit?</label>
|
<textarea id="partner_description" name="partner_description" rows="8"></textarea>
|
</p>
|
<p><i>Note:</i> — you must have good standing in the tattoo community to stay a partner of edmonton.ink.</p>
|
<p>If we receive multiple requests to terminate a partnership with you from member artists, we reserve the right to cancel your listings.</p>
|
</div>
|
<?php
|
}
|
|
/**
|
* Registration errors filter
|
*/
|
public function registrationErrorsFilter(WP_Error $errors, string $sanitized_user_login, string $user_email): WP_Error
|
{
|
error_log('Registration Data: '.print_r($_POST, true));
|
$user_type = isset($_POST['user_type']) ? $_POST['user_type'] : '';
|
|
if (empty($user_type)) {
|
$errors->add('user_type_error', 'Please select your user type.');
|
return $errors;
|
}
|
|
// Get email based on user type
|
$email_field = $user_type . '_email';
|
$email = isset($_POST[$email_field]) ? sanitize_email($_POST[$email_field]) : '';
|
|
// Remove WordPress's default username error
|
$errors = new WP_Error();
|
|
// If this is an invited artist, validate the invitation
|
$invite = (array_key_exists('invite_token', $_POST)) ? sanitize_text_field($_POST['invite_token']) : false;
|
if ($invite && array_key_exists('role', $_POST)) {
|
$handler = JVB()->routes('invites');
|
$invitation = $handler->verifyInvitation($invite, sanitize_email($_POST['invite_email']), sanitize_text_field($_POST['role']));
|
|
if (!$invitation) {
|
$errors->add('invalid_invitation', 'Invalid invitation token.');
|
} elseif (strtotime($invitation->expires_at) < current_time('timestamp')) {
|
$errors->add('expired_invitation', 'This invitation has expired.');
|
}
|
}
|
|
// Validate email first
|
if (empty($email)) {
|
$errors->add('email_error', 'Email is required.');
|
} elseif (!is_email($email)) {
|
$errors->add('email_error', 'Please enter a valid email address.');
|
} elseif (email_exists($email)) {
|
$errors->add('email_error', 'This email is already registered.');
|
}
|
|
switch ($user_type) {
|
case 'enthusiast':
|
if (empty($_POST['enthusiast_first_name'])) {
|
$errors->add('first_name_error', 'First name is required.');
|
}
|
break;
|
|
case 'artist':
|
$required_fields = array(
|
'artist_first_name' => 'First name',
|
'artist_last_name' => 'Last name',
|
'artist_shop' => 'Shop',
|
'artist_city' => 'City',
|
'artist_type' => 'Type',
|
);
|
foreach ($required_fields as $field => $label) {
|
if (empty($_POST[$field])) {
|
$errors->add($field . '_error', $label . ' is required.');
|
}
|
}
|
break;
|
|
case 'partner':
|
$required_fields = array(
|
'partner_name' => 'Contact name',
|
'partner_business' => 'Business name'
|
);
|
|
foreach ($required_fields as $field => $label) {
|
if (empty($_POST[$field])) {
|
$errors->add($field . '_error', $label . ' is required.');
|
}
|
}
|
break;
|
}
|
|
if (isset($_POST['user_type']) && $_POST['user_type'] === 'artist' && !empty($_FILES['certification_file']['name'])) {
|
$file = $_FILES['certification_file'];
|
|
// Validate file type
|
if (!in_array($file['type'], $this->allowed_file_types)) {
|
$errors->add('file_type_error', 'Please upload a valid file type (JPG, PNG, GIF, or PDF)');
|
}
|
|
// Validate file size
|
if ($file['size'] > $this->max_file_size) {
|
$errors->add('file_size_error', 'File size must be less than 5MB');
|
}
|
}
|
|
return $errors;
|
}
|
|
/**
|
* Save registration fields
|
*/
|
public function saveRegistrationFields(int $user_id, array $userdata): void
|
{
|
$user_type = isset($_POST['user_type']) ? $_POST['user_type'] : false;
|
if (!$user_type) {
|
return;
|
}
|
|
// Set user role based on type
|
$user = new WP_User($user_id);
|
$caps = JVB()->roles();
|
$email = false;
|
$upload_dir = wp_upload_dir();
|
$base_dir = $upload_dir['basedir'];
|
|
switch ($user_type) {
|
case 'artist':
|
$user->set_role('jvb_artist');
|
$user->remove_role('subscriber');
|
|
$email = sanitize_email($_POST['artist_email']);
|
$first = sanitize_text_field($_POST['artist_first_name']);
|
$last = sanitize_text_field($_POST['artist_last_name']);
|
$display_name = $first . ' ' . $last;
|
|
// Save artist fields
|
$temp = wp_update_user([
|
'ID' => $user_id,
|
'first_name' => $first,
|
'last_name' => $last,
|
'display_name' => $display_name
|
]);
|
$user = get_userdata($temp);
|
|
$link = $caps->addUserLink($user, 'artist');
|
$meta = new MetaManager($link, 'post');
|
$meta->setAll([
|
'first_name' => $first,
|
'email' => $email
|
]);
|
|
// If this was an invited artist, handle the invitation
|
if (array_key_exists('invite_token', $_POST)) {
|
$handler = JVB()->routes('invites');
|
$handler->acceptInvitation(sanitize_text_field($_POST['invite_token']), sanitize_email($_POST['invite_email']), $user->ID);
|
}
|
|
if (absint($_POST['artist_shop']) > 0) {
|
JVB()->routes('shop')->requestShopAdmission($user_id, absint($_POST['artist_shop']));
|
}
|
if (absint($_POST['artist_city']) > 0) {
|
wp_set_post_terms($link, (int)absint($_POST['artist_city']), BASE.'city');
|
}
|
|
//Create approval request and notify verified users
|
JVB()->routes('approvals')->createArtistApprovalRequest($user_id);
|
|
//Make base directories
|
$artist_dir = $base_dir . '/artists/' . $user_id;
|
wp_mkdir_p($artist_dir);
|
wp_mkdir_p($artist_dir . '/artwork');
|
wp_mkdir_p($artist_dir . '/events');
|
wp_mkdir_p($artist_dir . '/profile');
|
wp_mkdir_p($artist_dir . '/temp');
|
|
switch ($_POST['artist_type']) {
|
case 'tattoo-artist':
|
$caps->setUserAs($user, 'tattoo-artist');
|
$term = get_term_by('name', 'Tattoo Artists', BASE.'type');
|
if ($term && !is_wp_error($term)) {
|
wp_set_post_terms($link, $term->term_id, BASE.'type');
|
}
|
wp_mkdir_p($artist_dir . '/tattoos');
|
break;
|
case 'piercer':
|
$caps->setUserAs($user, 'piercer');
|
$term = get_term_by('name', 'Piercers', BASE.'type');
|
if ($term && !is_wp_error($term)) {
|
wp_set_post_terms($link, $term->term_id, BASE.'type');
|
}
|
wp_mkdir_p($artist_dir . '/piercings');
|
break;
|
}
|
break;
|
|
case 'partner':
|
$user->set_role('jvb_partner');
|
$user->remove_role('subscriber');
|
$name = sanitize_text_field($_POST['partner_name']);
|
$email = sanitize_email($_POST['partner_email']);
|
|
$caps->setUserAs($user, 'partner');
|
$link = $caps->addUserLink($user, 'partner');
|
|
// Save partner fields
|
update_user_meta($user_id, 'contact_name', sanitize_text_field($_POST['partner_name']));
|
update_user_meta($user_id, 'business_name', sanitize_text_field($_POST['partner_business']));
|
update_user_meta($user_id, 'business_website', esc_url_raw($_POST['partner_website']));
|
|
// Create partner base directory
|
$partner_dir = $base_dir . '/partners/' . $user_id;
|
wp_mkdir_p($partner_dir);
|
wp_mkdir_p($partner_dir . '/offers');
|
wp_mkdir_p($partner_dir . '/events');
|
wp_mkdir_p($partner_dir . '/profile');
|
wp_mkdir_p($partner_dir . '/temp');
|
break;
|
|
case 'enthusiast':
|
$user->set_role('jvb_enthusiast');
|
$user->remove_role('subscriber');
|
$caps->setUserAs($user, 'enthusiast');
|
$name = sanitize_text_field($_POST['enthusiast_first_name']);
|
$email = sanitize_email($_POST['enthusiast_email']);
|
|
// Save enthusiast fields
|
$temp = wp_update_user([
|
'ID' => $user_id,
|
'first_name' => $name,
|
'user_email' => $email,
|
]);
|
break;
|
}
|
|
// Handle file upload for artists
|
if (isset($_POST['user_type']) && $_POST['user_type'] === 'artist' && !empty($_FILES['certification_file']['name'])) {
|
$file = $_FILES['certification_file'];
|
|
// Setup upload directory
|
$upload_dir = wp_upload_dir();
|
$user_directory = 'artist-certifications/' . $user_id;
|
$target_dir = $upload_dir['basedir'] . '/' . $user_directory;
|
|
// Create directory if it doesn't exist
|
wp_mkdir_p($target_dir);
|
|
// Generate unique filename
|
$file_extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
$filename = 'certification-' . time() . '.' . $file_extension;
|
$target_file = $target_dir . '/' . $filename;
|
|
// Move uploaded file
|
if (move_uploaded_file($file['tmp_name'], $target_file)) {
|
// Save file information in user meta
|
update_user_meta($user_id, 'certification_file', array(
|
'url' => $upload_dir['baseurl'] . '/' . $user_directory . '/' . $filename,
|
'file' => $target_file,
|
'type' => $file['type'],
|
'original_name' => $file['name']
|
));
|
}
|
}
|
|
// Handle list invitation acceptance
|
if (isset($_GET['list_token']) && !empty($_GET['list_token']) && isset($_GET['email'])) {
|
$token = sanitize_text_field($_GET['list_token']);
|
$email = sanitize_email($_GET['email']);
|
|
if ($email) {
|
JVB()->routes('favourites')->acceptListInvitation($token, $email, $user_id);
|
}
|
}
|
}
|
|
/**
|
* Registration success message
|
*/
|
public function registrationSuccessMessage(WP_Error $errors, string $redirect_to): WP_Error
|
{
|
if (isset($errors->errors['registered']) && isset($_POST['invitation_token'])) {
|
// Custom message for invited artists
|
$message = "WELCOME ABOARD!<br><br>" .
|
"Password setup is in your inbox. <br>" .
|
"Since you were invited by a shop, you can skip the verification wait and start building your profile right away! ♡";
|
|
unset($errors->errors['registered']);
|
$errors->add('registered', $message, 'message');
|
}
|
|
if (isset($errors->errors['registered'])) {
|
$user_type = isset($_POST['user_type']) ? $_POST['user_type'] : 'user';
|
|
switch ($user_type) {
|
case 'enthusiast':
|
$message = "YOU'RE IN!<br><br>Check your inbox - we've sent password setup details.<br>Get ready to build your dream artist collection! ♡";
|
break;
|
|
case 'artist':
|
$message = "HELL YEAH!<br><br>Password setup is in your inbox. <br>While we verify your info (24-48hrs), you can start building your profile. <br>Just remember - it stays underground until you're cleared. ♡";
|
break;
|
|
case 'partner':
|
$message = "ROCK ON!<br><br>Check your inbox - we've sent password setup details.<br>We'll check out your pitch in the next 24-48hrs. <br><br>Meanwhile, you can start prepping your presence - but you won't hit the streets until we give the nod. ♡";
|
break;
|
|
default:
|
$message = "YOU'RE ON THE LIST!<br><br>Check your inbox for the next steps. ♡";
|
}
|
|
// Replace the default message
|
unset($errors->errors['registered']);
|
$errors->add('registered', $message, 'message');
|
}
|
|
return $errors;
|
}
|
|
/**
|
* Check if registration is from invite
|
*/
|
protected function fromInvite(): bool
|
{
|
return isset($_GET['invite']) && isset($_GET['email']);
|
}
|
|
/**
|
* Custom register message
|
*/
|
public function customRegisterMessage(string $message): string
|
{
|
return "Join Edmonton's tattoo community";
|
}
|
}
|
|
// Initialize the consolidated auth manager
|
new LoginManager();
|