<?php
|
namespace JVBase\users;
|
|
use JVBase\JVB;
|
use JVBase\registry\ContentRegistry;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
|
class UserSettings extends ContentRegistry
|
{
|
protected array $fields;
|
protected array $sections = [
|
'account' => [
|
'title' => 'Account',
|
'icon' => 'settings',
|
],
|
'notifications' => [
|
'title' => 'Notifications',
|
'icon' => 'email'
|
],
|
'newsletter' => [
|
'title' => 'Newsletter',
|
'icon' => 'logo'
|
],
|
'invitations' => [
|
'title' => 'Invite Artists',
|
'icon' => 'new-user',
|
]
|
];
|
protected array $frequency = [
|
'never' => 'Never',
|
'daily' => 'Daily',
|
'weekly' => 'Weekly',
|
'monthly' => 'Monthly',
|
];
|
|
public function initType():void
|
{
|
$this->object_type = 'user';
|
$this->userFields();
|
}
|
|
public function getFields():array
|
{
|
return $this->fields;
|
}
|
public function getSections():array
|
{
|
return $this->sections;
|
}
|
public function userFields():void
|
{
|
|
$this->fields = [
|
'display_name' => [
|
'type' => 'text',
|
'label' => 'Name',
|
'description' => 'Optional. Only used if you allow artists to see your info when you favourite them.',
|
'default' => '',
|
'section' => 'account',
|
'role' => ['ei_enthusiast']
|
],
|
'user_email' => [
|
'type' => 'email',
|
'label' => 'Email',
|
'description' => 'Required for any notifications you subscribe to.',
|
'default' => '',
|
'section' => 'account',
|
'role' => ['ei_enthusiast']
|
],
|
'city' => [
|
'type' => 'text',
|
'label' => 'City',
|
'description' => 'Optional. Just helps us know where people who use this site are located.',
|
'default' => '',
|
'section' => 'account',
|
'role' => ['ei_enthusiast']
|
],
|
'notify' => [
|
'type' => 'true_false',
|
'label' => 'Notify artists when you favourite their work',
|
'description' => 'If you select this, artists will see your name when you favourite their work, else they will just get an anonymous notification.',
|
'default' => false,
|
'section' => 'account',
|
],
|
'dark_mode' => [
|
'type' => 'true_false',
|
'label' => 'Dark Mode',
|
'default' => false,
|
],
|
'email_digest' => [
|
'type' => 'radio',
|
'label' => 'Email Summary Frequency',
|
'options' => $this->frequency,
|
'default' => 'never',
|
'section' => 'notifications'
|
],
|
'digest_override' => [
|
'type' => 'true_false',
|
'label' => 'Apply to all notifications',
|
'description' => 'Override individual notification settings and apply this frequency to all your notifications',
|
'default' => false,
|
'section' => 'notifications'
|
],
|
'notifications' => [
|
'type' => 'callback',
|
'callback' => 'renderNotifications',
|
'label' => 'Notifications',
|
'section' => 'notifications',
|
'default' => '',
|
],
|
'site_updates' => [
|
'type' => 'true_false',
|
'label' => 'Site Updates',
|
'description' => 'Get notified when new features are added to edmonton.ink',
|
'default' => false,
|
'section' => 'account',
|
],
|
'newsletter' => [
|
'type' => 'true_false',
|
'label' => 'Newsletter',
|
'description' => 'Sign up for our monthly-ish newsletter, featuring upcoming events, featured artists, new artists, and their work.',
|
'default' => false,
|
'section' => 'newsletter',
|
],
|
'owner_of' => [
|
'type' => 'selector',
|
'subtype'=> 'taxonomy',
|
'label' => __('Owner of', 'jvb'),
|
'isReference' => true,
|
'taxonomy' => BASE. 'shop',
|
'quickEdit' => true,
|
'default' => '',
|
],
|
'manager_of' => [
|
'type' => 'selector',
|
'subtype' => 'taxonomy',
|
'label' => __('Manager of', 'jvb'),
|
'isReference' => true,
|
'taxonomy' => BASE. 'shop',
|
'hidden' => true,
|
'default' => '',
|
],
|
|
'invitations' => [
|
'type' => 'callback',
|
'callback' => 'handleInvitationsManagement',
|
'section' => 'invitations',
|
'label' => __('Invite Artists', 'jvb'),
|
'role' => ['ei_artist']
|
],
|
];
|
}
|
|
/**
|
* Render custom notification preferences UI
|
* @param int $user_id
|
*
|
* @return string
|
*/
|
public function renderNotifications(int $user_id):string
|
{
|
$favourites = $this->getUserFavourites($user_id);
|
$preferences = $this->getUserNotificationPreferences($user_id);
|
global $notify;
|
|
ob_start();
|
?>
|
<div class="notification-preferences">
|
<?php foreach ($favourites as $type => $items) : ?>
|
<details class="notification-group">
|
<summary class="notification-group-header row x-btw">
|
<span class="type-label"><?=jvbIcon('heart', ['style' => 'fill'])?> <?= $notify[$type] ?></span>
|
<span class="type-count">( <?= count($items); ?> )</span>
|
</summary>
|
|
<?php if (!empty($items)) : ?>
|
<table class="notification-table">
|
<thead>
|
<tr>
|
<th>Name</th>
|
<th>Frequency</th>
|
</tr>
|
</thead>
|
<tbody>
|
<?php foreach ($items as $item) : ?>
|
<?php
|
$current = isset($preferences['preferences'][$type][$item['id']])
|
? $preferences['preferences'][$type][$item['id']]
|
: $preferences['default_frequency'];
|
?>
|
<tr>
|
<td><?= esc_html($item['name']); ?></td>
|
<td>
|
<div class="radio-options row">
|
<?php foreach (['never', 'daily', 'weekly', 'monthly'] as $freq) : ?>
|
<input type="radio"
|
name="notification_preferences[<?= esc_attr($type); ?>][<?= esc_attr($item['id']); ?>]"
|
id="<?= esc_attr("{$type}_{$item['id']}_$freq"); ?>"
|
value="<?= esc_attr($freq); ?>"
|
<?php checked($current, $freq); ?>>
|
<label for="<?= esc_attr("{$type}_{$item['id']}_$freq"); ?>">
|
<?= ucfirst($freq); ?>
|
</label>
|
<?php endforeach; ?>
|
</div>
|
</td>
|
</tr>
|
<?php endforeach; ?>
|
</tbody>
|
<tfoot>
|
<tr>
|
|
<th>Set all <?= $type ?>s to: </th>
|
<th>
|
<div class="radio-options row">
|
<?php foreach (['never', 'daily', 'weekly', 'monthly'] as $freq) : ?>
|
<input type="radio"
|
name="toggle-all-<?=$type?>"
|
id="toggle-all-<?=$type?>-<?=$freq?>"
|
value="<?= esc_attr($freq); ?>"
|
class="toggle-all">
|
<label for="toggle-all-<?=$type?>-<?=$freq?>">
|
<?= ucfirst($freq); ?>
|
</label>
|
<?php endforeach; ?>
|
</div>
|
</th>
|
|
</tr>
|
</tfoot>
|
</table>
|
<?php else : ?>
|
<p class="no-items">No favourites yet</p>
|
<?php endif; ?>
|
</details>
|
<?php endforeach; ?>
|
</div>
|
<script>
|
function initGroupOverrides() {
|
document.querySelectorAll('.toggle-all').forEach(toggle => {
|
toggle.addEventListener('change', e => {
|
e.preventDefault();
|
if(toggle.checked){
|
const table = e.target.closest('table');
|
console.log(table);
|
const value = toggle.value;
|
|
// Enable/disable individual controls
|
table.querySelectorAll('input[value="'+value+'"]').forEach(radio => {
|
radio.checked = true;
|
});
|
}
|
|
});
|
});
|
|
// Listen for changes to default frequency
|
document.querySelector('input[name="digest_override"]').addEventListener('change', e=>{
|
let value = document.querySelector('input[name="email_digest"]:checked').value;
|
|
console.log(value);
|
document.querySelectorAll('input[value="'+value+'"]').forEach(toggle => {
|
toggle.checked = true;
|
});
|
|
setTimeout(function () {
|
e.target.checked = false;
|
}, 1000)
|
|
});
|
|
}
|
initGroupOverrides();
|
</script>
|
<?php
|
return ob_get_clean();
|
}
|
|
/**
|
* Get user's favourites grouped by type
|
*/
|
protected function getUserFavourites(int $user_id):array
|
{
|
global $wpdb;
|
$table = $wpdb->prefix . BASE.'favourites';
|
global $notify;
|
global $types;
|
|
|
$favourites = [];
|
foreach (array_keys($notify) as $type) {
|
$favourites[$type] = [];
|
}
|
|
// Fetch favourites from database
|
$results = $wpdb->get_results($wpdb->prepare(
|
"SELECT * FROM $table WHERE user_id = %d",
|
$user_id
|
));
|
|
foreach ($results as $result) {
|
$type = str_replace(BASE, '', $result->type);
|
|
|
if (!array_key_exists($type, $favourites)) {
|
continue;
|
}
|
// Get proper name based on type
|
switch ($types[BASE.$type]['table']) {
|
case 'post':
|
$name = get_the_title($result->target_id);
|
break;
|
case 'term':
|
$term = get_term($result->target_id, BASE.$type);
|
$name = $term ? html_entity_decode($term->name) : '';
|
break;
|
default:
|
$name = '';
|
}
|
|
if ($name) {
|
$favourites[$type][] = [
|
'id' => $result->target_id,
|
'name' => $name
|
];
|
}
|
}
|
|
return $favourites;
|
}
|
|
/**
|
* Get current notification preferences
|
*/
|
protected function getNotificationPreferences(int $user_id):array
|
{
|
return get_user_meta($user_id, 'notification_preferences', true) ?: [];
|
}
|
|
protected function getUserNotificationPreferences(int $user_id):array
|
{
|
global $wpdb;
|
$table = $wpdb->prefix . BASE.'notification_preferences';
|
|
// error_log('Getting notification preferences for user: ' . $user_id);
|
|
// Get user's preferences from the table
|
$raw_preferences = $wpdb->get_results($wpdb->prepare(
|
"SELECT notification_type, item_id, frequency
|
FROM {$table}
|
WHERE user_id = %d",
|
$user_id
|
), ARRAY_A);
|
|
// error_log('Raw preferences from database: ' . print_r($raw_preferences, true));
|
|
// Structure the preferences
|
$preferences = [];
|
if (!empty($raw_preferences)) {
|
foreach ($raw_preferences as $pref) {
|
$type = $pref['notification_type'];
|
$item_id = intval($pref['item_id']);
|
$frequency = $pref['frequency'];
|
|
if (!isset($preferences[$type])) {
|
$preferences[$type] = [];
|
}
|
$preferences[$type][$item_id] = $frequency;
|
}
|
}
|
|
// Get default frequency
|
$default_frequency = get_user_meta($user_id, BASE.'email_digest', true) ?: 'never';
|
|
// error_log('Structured preferences: ' . print_r($preferences, true));
|
// error_log('Default frequency: ' . $default_frequency);
|
|
return [
|
'preferences' => $preferences,
|
'default_frequency' => $default_frequency
|
];
|
}
|
|
public function handleInvitationsManagement(int $userID):void
|
{
|
$name = 'artist_invite';
|
?>
|
<div class="field">
|
<script type="text/javascript">
|
function addArtist(button){
|
let template = window.getTemplate('artistInvite');
|
let container = button.closest('.field').querySelector('.invite-artists');
|
|
let nameLabel = template.querySelector('label[for="invited-artist-name"]'),
|
nameInput = template.querySelector('input#invited-artist-name'),
|
emailLabel = template.querySelector('label[for="invited-artist-email"]'),
|
emailInput = template.querySelector('input#invited-artist-email'),
|
shopLabel = template.querySelector('label[for="invited-add-to-shop"]'),
|
shopInput = template.querySelector('input#invited-add-to-shop');
|
|
|
let count = container.childNodes.length;
|
console.log(container.childNodes);
|
[
|
nameLabel.htmlFor,
|
nameInput.id,
|
nameInput.name,
|
emailLabel.htmlFor,
|
emailInput.id,
|
emailInput.name,
|
shopLabel.htmlFor,
|
shopInput.id,
|
shopInput.name
|
] =
|
[
|
`${nameLabel.getAttribute('for')}-${count}`,
|
`${nameInput.id}-${count}`,
|
`${nameInput.name}-${count}`,
|
`${emailLabel.htmlFor}-${count}`,
|
`${emailInput.id}-${count}`,
|
`${emailInput.name}-${count}`,
|
`${shopLabel.htmlFor}-${count}`,
|
`${shopInput.id}-${count}`,
|
`${shopInput.name}-${count}`
|
];
|
container.append(template);
|
}
|
function removeArtist(button){
|
button.closest('.invited-artist').remove();
|
}
|
|
async function revokeInvite(button){
|
try {
|
let artist = button.closest('tr');
|
let id = artist.id;
|
|
const headers = {
|
'X-WP-Nonce': eiSettings.nonce,
|
'action_nonce': eiSettings.dash,
|
'Content-Type': 'application/json'
|
};
|
|
let body = {
|
user: eiSettings.currentUser,
|
revoke: id
|
};
|
let request = {
|
method: 'POST',
|
headers: headers,
|
body: JSON.stringify(body)
|
};
|
console.log(request);
|
|
|
const response = await fetch(`${eiSettings.api}invitations`, request);
|
|
console.log(response);
|
if (!response.ok) {
|
const errorData = await response.json().catch(() => ({}));
|
}
|
|
const data = await response.json();
|
if(data.success){
|
artist.remove();
|
}
|
}catch (error) {
|
}
|
}
|
|
async function resendInvite(button){
|
try {
|
let artist = button.closest('tr');
|
let id = artist.id;
|
|
const headers = {
|
'X-WP-Nonce': eiSettings.nonce,
|
'action_nonce': eiSettings.dash,
|
'Content-Type': 'application/json'
|
};
|
|
let body = {
|
user: eiSettings.currentUser,
|
refresh: id
|
};
|
let request = {
|
method: 'POST',
|
headers: headers,
|
body: JSON.stringify(body)
|
};
|
console.log(request);
|
|
|
const response = await fetch(`${eiSettings.api}invitations`, request);
|
|
console.log(response);
|
if (!response.ok) {
|
const errorData = await response.json().catch(() => ({}));
|
}
|
|
const data = await response.json();
|
|
}catch (error) {
|
}
|
}
|
|
function sendInvites(button){
|
if(document.querySelector('.invited-artist')){
|
let newArtists = [];
|
document.querySelectorAll('.invited-artist').forEach(invited => {
|
let name = invited.querySelector('input[type="text"]'),
|
email = invited.querySelector('input[type="email"]'),
|
toShop = invited.querySelector('input[type="checkbox"]');
|
if(name.value === '' || email.value === ''){
|
invited.classList.add('required');
|
return;
|
}
|
|
newArtists.push({
|
name: name.value,
|
email: email.value,
|
toShop: toShop.checked
|
});
|
invited.remove();
|
});
|
if(newArtists.length > 0){
|
window.queueManager.addToQueue({
|
type: 'invite_artist',
|
data: newArtists
|
});
|
}
|
}
|
}
|
</script>
|
|
<section class="invite-artists">
|
<div class="invited-artist">
|
<button type="button" onclick="removeArtist(this)" title="Remove Invite"><?=jvbIcon('trash')?></button>
|
<div>
|
<label for="invited-artist-name-1">First Name</label>
|
<input type="text" id="invited-artist-name-1" name="invited-artist-name-1">
|
</div>
|
<div>
|
<label for="invited-artist-email-1">Email</label>
|
<input type="email" id="invited-artist-email-1" name="invited-artist-email-1">
|
</div>
|
<div>
|
<input type="checkbox" name="invited-add-to-shop-1" id="invited-add-to-shop-1">
|
<label for="invited-add-to-shop-1">Add to shop?</label>
|
</div>
|
</div>
|
</section>
|
<div class="actions">
|
<button type="button" onclick="addArtist(this)" class="add-artist"><?= jvbIcon('plus-square')?>Add Artist</button>
|
<button type="button" onclick="sendInvites(this)" class="send-invites"><?=jvbIcon('envelope')?>Send Invites</button>
|
</div>
|
|
<template class="artistInvite">
|
<div class="invited-artist">
|
<button type="button" onclick="removeArtist(this)" title="Remove Invite"><?=jvbIcon('trash')?></button>
|
<div>
|
<label for="invited-artist-name">First Name</label>
|
<input type="text" id="invited-artist-name" name="invited-artist-name">
|
</div>
|
<div>
|
<label for="invited-artist-email">Email</label>
|
<input type="email" id="invited-artist-email" name="invited-artist-email">
|
</div>
|
<div>
|
<input type="checkbox" name="invited-add-to-shop" id="invited-add-to-shop">
|
<label for="invited-add-to-shop">Add to shop?</label>
|
</div>
|
</div>
|
</template>
|
</div>
|
<?php
|
|
$invites = JVB()->routes('invites')->getUserInvitations($userID);
|
|
?>
|
<table class="artist-invite">
|
<thead>
|
<tr>
|
<th scope="col" style="width:100%;">Name</th>
|
<th scope="col" style="padding:0 .5rem">Email</th>
|
<th scope="col" style="padding:0 .5rem">Status</th>
|
<th scope="col" style="padding:0 .5rem">Expires</th>
|
<th scope="col" style="padding:0 .5rem">Actions</th>
|
</tr>
|
</thead>
|
<tbody>
|
<?php
|
if (!empty($invites->invitations)) {
|
foreach ($invites->invitations as $invite) {
|
?>
|
<tr id="<?=$invite['id']?>">
|
<td>
|
<?=$invite['name']?>
|
</td>
|
<td>
|
<?=$invite['email']?>
|
</td>
|
<td>
|
<?=$invite['status']?>
|
</td>
|
<td>
|
<?=$invite['expires_at']?>
|
</td>
|
<td class="actions">
|
<button type="button" data-action="revoke" title="Revoke Invitation" onclick="revokeInvite(this)">
|
<?=jvbIcon('trash')?>
|
<span class="screen-reader-text">Revoke invite</span>
|
</button>
|
<button type="button" data-action="resend"<?=($invite['status'] === 'exipired') ? '' : ' disabled'?> title="Resend Invitation" onclick="resendInvite(this)">
|
<?=jvbIcon('arrows-clockwise')?>
|
<span class="screen-reader-text">Try Again</span>
|
</button>
|
</td>
|
|
</tr>
|
<?php
|
}
|
} else {
|
?>
|
<tr><td><i>No invites yet</i></td><td></td><td></td><td></td><td></td></tr>
|
<?php
|
}
|
?>
|
</tbody>
|
</table>
|
<?php
|
}
|
}
|