<?php
|
namespace JVBase\managers;
|
|
use JVBase\meta\MetaManager;
|
use WP_Error;
|
use WP_User;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
|
class RegisterManager
|
{
|
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()
|
{
|
// Hide default fields and add our customizations
|
add_action('login_enqueue_scripts', array($this, 'registrationStyles'));
|
add_action('register_form', array($this, 'addRegistrationFields'));
|
add_action('login_header', array($this, 'addRegistrationScript'));
|
|
// Handle the registration
|
add_filter('registration_errors', array($this, 'registrationErrorsFilter'), 10, 3);
|
add_action('user_register', array($this, 'saveRegistrationFields'), 999, 2);
|
|
// Modify default registration fields
|
add_action('login_head', array($this, 'modifyRegistrationForm'));
|
|
// Add support for file uploads in the form
|
add_action('register_form', array($this, 'addUploadSupport'));
|
|
add_filter('login_message', array($this, 'loginMessage'), 999, 1);
|
|
|
add_filter('pre_user_login', array($this, 'setUserLogin'), 1);
|
add_filter('pre_user_email', array($this, 'setUserEmail'), 1);
|
|
// Remove the default username requirement
|
remove_filter('registration_errors', 'registration_auth_pass_filter', 10);
|
|
// Add this new filter for registration message
|
add_filter('register_message', array($this, 'customRegisterMessage'));
|
add_filter('wp_login_errors', array($this, 'registrationSuccessMessage'), 10, 2);
|
}
|
|
|
/**
|
* @param string $message
|
*
|
* @return string
|
*/
|
public function loginMessage(string $message):string
|
{
|
if (array_key_exists('action', $_GET) && $_GET['action'] == 'register') {
|
if ($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 inivited you to join in!';
|
return '<h2>Join the Scene, '.$name.'</h2>
|
<p style="text-align:center;">'.jvbCommaList($names).$message.'</p>';
|
}
|
if ($this->fromFavourites()) {
|
return '<h2>Join the scene; keep your collection.</h2>';
|
}
|
return '<h2>Join the Scene</h2>';
|
} else {
|
return '<h2>Enter the Scene</h2>';
|
}
|
|
}
|
|
/**
|
* @param string $login
|
*
|
* @return string
|
*/
|
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;
|
}
|
|
/**
|
* @param string $email
|
*
|
* @return string
|
*/
|
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;
|
}
|
|
/**
|
* @return void
|
*/
|
public function modifyRegistrationForm():void
|
{
|
if (!isset($_GET['action']) || $_GET['action'] !== 'register') {
|
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
|
}
|
|
/**
|
* @return void
|
*/
|
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
|
}
|
|
/**
|
* @return void
|
*/
|
public function addUploadSupport():void
|
{
|
// Add enctype to the form for file uploads
|
?>
|
<script>
|
document.addEventListener('DOMContentLoaded', function() {
|
const form = document.getElementById('registerform');
|
if (form) {
|
form.enctype = 'multipart/form-data';
|
}
|
});
|
</script>
|
<?php
|
}
|
|
/**
|
* @return void
|
*/
|
public function registrationStyles():void
|
{
|
if (isset($_GET['action']) && $_GET['action'] === 'favourites') {
|
?>
|
<style>
|
.benefits {
|
background: rgba(255, 0, 128, 0.05);
|
padding: 1.5rem;
|
border-radius: 4px;
|
margin: 1rem 0 2rem;
|
}
|
|
.benefits h3 {
|
color: #FF0080;
|
margin: 0 0 1rem;
|
font-size: 1.1rem;
|
}
|
|
.benefits ul {
|
margin: 0;
|
padding-left: 1.5rem;
|
}
|
|
.benefits li {
|
margin-bottom: 0.5rem;
|
color: #666;
|
}
|
|
.benefits li:last-child {
|
margin-bottom: 0;
|
}
|
|
/* Make the form more focused for this flow */
|
.field-group {
|
max-width: 400px;
|
margin: 0 auto;
|
}
|
</style>
|
<?php
|
}
|
if (!isset($_GET['action']) || $_GET['action'] !== 'register') {
|
return;
|
}
|
?>
|
<style>
|
/* Hide the default registration fields initially */
|
#registerform > p:not(.submit) {
|
display: none;
|
}
|
|
/* Registration form specific styles */
|
#registerform {
|
padding: 2rem !important;
|
}
|
|
.registration-intro {
|
text-align: center;
|
margin-bottom: 2rem !important;
|
}
|
|
.registration-intro h2 {
|
margin: 0 0 1rem !important;
|
font-size: 1.5rem !important;
|
}
|
|
.registration-intro p {
|
color: #666 !important;
|
margin: 0 !important;
|
}
|
|
.user-type-section {
|
margin-bottom: 2rem !important;
|
}
|
.user-type-section p {
|
font-size: 1.4rem;
|
line-height: 1.4;
|
font-weight: bolder;
|
}
|
|
.user-type-selection {
|
display: flex !important;
|
gap: 1rem !important;
|
margin-bottom: 1rem !important;
|
}
|
|
.user-type-option {
|
flex: 1 !important;
|
text-align: center !important;
|
padding: 1rem !important;
|
border: 2px solid #ddd !important;
|
border-radius: 4px !important;
|
cursor: pointer !important;
|
transition: all 0.3s ease !important;
|
}
|
|
.user-type-option:hover {
|
border-color: var(--primary) !important;
|
transform: translateY(-2px) !important;
|
}
|
|
.user-type-option.selected {
|
border-color: var(--primary) !important;
|
background: var(--primary) !important;
|
color: white !important;
|
}
|
|
.user-type-option h3 {
|
margin: 0 0 0.5rem !important;
|
font-size: 1.2rem !important;
|
}
|
|
.user-type-option p {
|
margin: 0 !important;
|
font-size: 0.9rem !important;
|
}
|
|
.field-group {
|
display: none;
|
animation: fadeIn 0.3s ease;
|
}
|
|
.field-group.active {
|
display: block !important;
|
}
|
|
@keyframes fadeIn {
|
from { opacity: 0; transform: translateY(10px); }
|
to { opacity: 1; transform: translateY(0); }
|
}
|
|
/* Field styles */
|
.login form textarea,
|
.login form .input,
|
.login select {
|
font-size: 16px !important;
|
padding: 12px !important;
|
border: 2px solid #ddd !important;
|
border-radius: 4px !important;
|
margin: 5px 0 15px !important;
|
width: 100% !important;
|
box-sizing: border-box !important;
|
background: white !important;
|
}
|
|
.login form textarea,
|
.login form .input:focus,
|
.login select:focus {
|
border-color: var(--primary) !important;
|
outline: none !important;
|
}
|
|
.login select {
|
height: auto !important;
|
padding-right: 30px !important;
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='https://www.w3.org/2000/svg'%3E%3Cpath d='M6 9l6 6 6-6' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
|
background-repeat: no-repeat !important;
|
background-position: right 8px center !important;
|
background-size: 16px !important;
|
-webkit-appearance: none !important;
|
-moz-appearance: none !important;
|
appearance: none !important;
|
}
|
|
/* Required field indicator */
|
.required-field::after {
|
content: '*' !important;
|
color: var(--error) !important;
|
margin-left: 4px !important;
|
}
|
|
/* Submit button styling */
|
.wp-core-ui .button-primary {
|
width: 100% !important;
|
margin-top: 1rem !important;
|
text-transform: uppercase !important;
|
}
|
|
/* Error messages */
|
#login_error {
|
border-left-color: var(--error) !important;
|
}
|
|
/* Responsive adjustments */
|
@media screen and (max-width: 480px) {
|
.user-type-selection {
|
flex-direction: column !important;
|
}
|
}
|
|
/* File upload styling */
|
.file-upload-container {
|
margin-bottom: 1.5rem !important;
|
}
|
|
.file-upload-label {
|
display: block !important;
|
margin-bottom: 0.5rem !important;
|
}
|
|
.file-upload-wrapper {
|
position: relative !important;
|
border: 2px dashed #ddd !important;
|
border-radius: 4px !important;
|
padding: 1.5rem !important;
|
text-align: center !important;
|
transition: all 0.3s ease !important;
|
background: #f9f9f9 !important;
|
}
|
|
.file-upload-wrapper:hover {
|
border-color: var(--primary) !important;
|
background: #fff !important;
|
}
|
|
.file-upload-wrapper input[type="file"] {
|
position: absolute !important;
|
left: 0 !important;
|
top: 0 !important;
|
width: 100% !important;
|
height: 100% !important;
|
opacity: 0 !important;
|
cursor: pointer !important;
|
}
|
|
.file-upload-text {
|
color: #666 !important;
|
margin: 0 !important;
|
}
|
|
.file-upload-text strong {
|
color: var(--primary) !important;
|
text-decoration: underline !important;
|
}
|
|
.file-preview {
|
display: none;
|
margin-top: 1rem !important;
|
}
|
|
.file-preview.active {
|
display: block !important;
|
}
|
|
.file-preview-content {
|
display: flex !important;
|
align-items: center !important;
|
padding: 0.5rem !important;
|
background: #fff !important;
|
border: 1px solid #ddd !important;
|
border-radius: 4px !important;
|
}
|
|
.file-preview-name {
|
flex-grow: 1 !important;
|
margin-right: 1rem !important;
|
}
|
|
.file-preview-remove {
|
background: none !important;
|
border: none !important;
|
color: var(--error) !important;
|
cursor: pointer !important;
|
padding: 0.25rem 0.5rem !important;
|
font-size: 0.9rem !important;
|
}
|
|
.file-error {
|
color: var(--error) !important;
|
font-size: 0.9rem !important;
|
margin-top: 0.5rem !important;
|
display: none;
|
}
|
|
.file-error.active {
|
display: block !important;
|
}
|
|
.user-type-section {
|
display: flex;
|
justify-content: space-between;
|
align-items: stretch;
|
gap: .5rem;
|
flex-wrap: wrap;
|
}
|
.user-type-section input[type=radio] {
|
position: absolute;
|
left: -300vw;
|
}
|
.user-type-section p {
|
width: 100%;
|
max-height: 0;
|
transform: scaleY(0);
|
transform-origin: top;
|
visibility: hidden;
|
transition: max-height var(--timing) var(--function);
|
transition-property: max-height, transform;
|
position: absolute;
|
text-align: center;
|
}
|
.user-type-section input#enthusiast:checked ~ p.enthusiast {
|
max-height: 100%;
|
transform: scaleY(1);
|
visibility: visible;
|
transition: max-height var(--timing) var(--function);
|
transition-property: max-height, transform;
|
position: relative;
|
}
|
.user-type-section input#artist:checked ~ p.artist {
|
max-height: 100%;
|
transform: scaleY(1);
|
visibility: visible;
|
transition: max-height var(--timing) var(--function);
|
transition-property: max-height, transform;
|
position: relative;
|
}
|
.user-type-section input#partner:checked ~ p.partner {
|
max-height: 100%;
|
transform: scaleY(1);
|
visibility: visible;
|
transition: max-height var(--timing) var(--function);
|
transition-property: max-height, transform;
|
position: relative;
|
}
|
.login .user-type-section label:not([for="subscriber"]) {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
cursor: pointer;
|
border: 2px solid transparent;
|
border-radius: 1rem;
|
padding: .5rem;
|
flex: 1;
|
text-align: center;
|
}
|
.login .user-type-section label h4 {
|
font-size: 1.1rem;
|
font-weight: normal;
|
}
|
.login .user-type-section label:hover,
|
.login .user-type-section :checked + label {
|
border-color: #FF0080;
|
/*background-color: #222222!important;*/
|
/*color: #f9f9f9!important;*/
|
}
|
</style>
|
<?php
|
}
|
|
/**
|
* @return void
|
*/
|
public function addRegistrationScript():void
|
{
|
if (!isset($_GET['action']) || $_GET['action'] !== 'register') {
|
return;
|
}
|
?>
|
<script>
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize user type selection
|
function initUserTypeSelection() {
|
// Get all radio buttons with name="user_type"
|
const userTypeRadios = document.querySelectorAll('input[name="user_type"]');
|
const fieldGroups = document.querySelectorAll('.field-group');
|
|
userTypeRadios.forEach(radio => {
|
radio.addEventListener('change', function() {
|
// Hide all field groups first
|
fieldGroups.forEach(group => group.classList.remove('active'));
|
|
// Show the selected field group
|
const selectedType = this.value;
|
const targetGroup = document.querySelector(`.field-group[data-type="${selectedType}"]`);
|
if (targetGroup) {
|
targetGroup.classList.add('active');
|
}
|
});
|
});
|
|
// Show initial field group if a radio is already selected (e.g., on form reload)
|
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
|
}
|
|
/**
|
* @return void
|
*/
|
public function addRegistrationFields():void
|
{
|
// Get list of tattoo shops from your custom post type
|
$shops = get_terms(array(
|
'taxonomy' => 'jvb_shop',
|
'hide_empty' => true
|
));
|
|
// Get list of cities from your taxonomy
|
$cities = get_terms(array(
|
'taxonomy' => 'jvb_city',
|
'hide_empty' => false,
|
));
|
|
echo '<input type="hidden" name="user_pass" value="' . wp_generate_password() . '">';
|
?>
|
<div class="registration-intro">
|
|
<p><b>No algorithm.</b> <b>No BS.</b> <b>Just Art.</b></p>
|
<p>Drop by. Get Lost. Find your next artist.</p>
|
|
<?php
|
if ($this->fromFavourites()) {
|
?>
|
|
<div class="favourites-login-message">
|
<ul class="benefits-list">
|
<li>Save designs you love</li>
|
<li>Get personalized recommendations</li>
|
<li>Connect with artists</li>
|
<li>Build your inspiration collection</li>
|
<li>Bonus: It's all free!</li>
|
</ul>
|
</div>
|
<?php
|
}
|
?>
|
</div>
|
<h3 style="font-size:1rem;font-weight:normal;text-align:center;color:#ff0080;">Choose how you wish to interact with the community:</h3>
|
<div class="user-type-section">
|
<input type="radio" id="subscriber" name="user_type" value="subscriber" required checked>
|
<label for="subscriber"></label>
|
<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('drop-simple', ['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('currency-circle-dollar', ['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
|
|
if ($this->invitation_data) {
|
// Pre-select artist type and populate email
|
?>
|
<script>
|
document.addEventListener('DOMContentLoaded', function() {
|
// Auto-select artist radio button
|
const artistRadio = document.getElementById('artist');
|
if (artistRadio) {
|
artistRadio.checked = true;
|
artistRadio.dispatchEvent(new Event('change'));
|
}
|
|
// Pre-fill email
|
const emailField = document.getElementById('artist_email');
|
if (emailField) {
|
emailField.value = '<?= esc_js($this->invitation_data['email']); ?>';
|
emailField.readOnly = true;
|
}
|
|
// Pre-select shop
|
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
|
}
|
}
|
|
/**
|
* @param WP_Error $errors
|
* @param string $sanitized_user_login
|
* @param string $user_email
|
*
|
* @return WP_Error
|
*/
|
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&& $user_type === 'artist') {
|
$handler = JVB()->routes('invites');
|
$invitation = $handler->validateInvitation($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;
|
}
|
|
/**
|
* @param int $user_id
|
* @param array $userdata
|
*
|
* @return void
|
*/
|
public function saveRegistrationFields(int $user_id, array $userdata):void
|
{
|
$user_type = isset($_POST['user_type']) ? $_POST['user_type'] : false;
|
if (!$user_type) {
|
return;
|
}
|
$shop_id = $_POST['shop_id'] ?? false;
|
|
// 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->updateValue('first_name', $first);
|
$meta->updateValue('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);
|
$user->add_cap('skip_moderation', true);
|
}
|
|
|
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);
|
// Directories for all artists
|
wp_mkdir_p($artist_dir . '/artwork');
|
wp_mkdir_p($artist_dir . '/events');
|
// Add a directory for profile images
|
wp_mkdir_p($artist_dir . '/profile');
|
|
// Add a temp directory for uploads in progress
|
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);
|
|
// Partner subdirectories
|
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 artist fields
|
$temp = wp_update_user([
|
'ID' => $user_id,
|
'first_name' => $name,
|
'user_email' => $email,
|
]);
|
break;
|
default:
|
break;
|
}
|
|
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']
|
));
|
}
|
}
|
|
if (isset($_GET['list_token']) && !empty($_GET['list_token']) && isset($_GET['email'])) {
|
$token = sanitize_text_field($_GET['list_token']);
|
$email = sanitize_email($_GET['email']);
|
|
// Accept the list invitation for this new user
|
if ($email) {
|
JVB()->routes('favourites')->acceptListInvitation(
|
$token,
|
$email,
|
$user_id
|
);
|
}
|
}
|
}
|
|
/**
|
* @param WP_Error $errors
|
* @param string $redirect_to
|
*
|
* @return WP_Error
|
*/
|
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;
|
}
|
|
/**
|
* @return bool
|
*/
|
protected function fromFavourites():bool
|
{
|
return isset($_GET['type']) && $_GET['type'] === 'favourites';
|
}
|
|
/**
|
* @return bool
|
*/
|
protected function fromInvite():bool
|
{
|
return isset($_GET['invite']) && isset($_GET['email']);
|
}
|
|
/**
|
* @param string $message
|
*
|
* @return string
|
*/
|
public function customRegisterMessage(string $message):string
|
{
|
return "Join Edmonton's tattoo community";
|
}
|
}
|
|
// Initialize the registration customizer
|
new RegisterManager();
|