Virtual user page and change template depending on role

Hello everyone,
First of all, sorry if there are any mistakes, I am French.
I am working on a social network project in a reduced format.

I do not know how to proceed for the registration of an account…

In my head the roles are the following:

  • Administrator
  • Profile (both a single person and a couple of two people)
  • Establishment

Let me explain, so I have an account registration page with the following form (for a single person and a couple of two people):

Organization of the final form

Here is an overview (organization) of all the fields in the form, describing their layout and associated behaviors.

Registration Form

  1. Username

    • Type: Text field (input type="text")
    • ID: username
    • Required: Yes
  2. Email Address

    • Type: Email field (input type="email")
    • ID: email
    • Required: Yes
  3. Password

    • Type: Password field (input type="password")
    • ID: password
    • Required: Yes
  4. City

    • Type: Text field (input type="text")
    • ID: city
    • Required: Yes
  5. Status

    • Type: Radio buttons (input type="radio")
    • Options: Single, Couple
    • IDs:
      • single (Single)
      • couple (Couple)
    • Behavior: Displays different fields based on the selected status.

If “Single” is selected

  1. Date of Birth

    • Type: Date field (input type="date")
    • ID: dob_single
  2. Gender

    • Type: Dropdown list (select)
    • ID: gender_single
    • Options:
      • Male
      • Female
      • Transgender
      • Transvestite
      • Non-binary
      • Other
    • Behavior: If “Other” is selected, an additional text field appears to enter a custom gender.
    • Associated text field:
      • ID: other_gender_single
      • Only displayed if “Other” is selected.
  3. Sexual Orientation

    • Type: Dropdown list (select)
    • ID: orientation_single
    • Options:
      • Heterosexual
      • Homosexual
      • Bisexual
      • Pansexual
      • Other
    • Behavior: If “Other” is selected, an additional text field appears to enter a custom sexual orientation.
    • Associated text field:
      • ID: other_orientation_single
      • Only displayed if “Other” is selected.

If “Couple” is selected

  1. Partner 1

    • Date of Birth
      • Type: Date field (input type="date")
      • ID: dob_couple_1
    • Gender
      • Type: Dropdown list (select)
      • ID: gender_couple_1
      • Options:
        • Male
        • Female
        • Transgender
        • Transvestite
        • Non-binary
        • Other
      • Behavior: If “Other” is selected, an additional text field appears to enter a custom gender.
      • Associated text field:
        • ID: other_gender_couple_1
        • Only displayed if “Other” is selected.
    • Sexual Orientation
      • Type: Dropdown list (select)
      • ID: orientation_couple_1
      • Options:
        • Heterosexual
        • Homosexual
        • Bisexual
        • Pansexual
        • Other
      • Behavior: If “Other” is selected, an additional text field appears to enter a custom sexual orientation.
      • Associated text field:
        • ID: other_orientation_couple_1
        • Only displayed if “Other” is selected.
  2. Partner 2

    • Date of Birth
      • Type: Date field (input type="date")
      • ID: dob_couple_2
    • Gender
      • Type: Dropdown list (select)
      • ID: gender_couple_2
      • Options:
        • Male
        • Female
        • Transgender
        • Transvestite
        • Non-binary
        • Other
      • Behavior: If “Other” is selected, an additional text field appears to enter a custom gender.
      • Associated text field:
        • ID: other_gender_couple_2
        • Only displayed if “Other” is selected.
    • Sexual Orientation
      • Type: Dropdown list (select)
      • ID: orientation_couple_2
      • Options:
        • Heterosexual
        • Homosexual
        • Bisexual
        • Pansexual
        • Other
      • Behavior: If “Other” is selected, an additional text field appears to enter a custom sexual orientation.
      • Associated text field:
        • ID: other_orientation_couple_2
        • Only displayed if “Other” is selected.

Terms and Conditions (T&C)

  1. Accept T&C
    • Type: Checkbox (input type="checkbox")
    • ID: cgu
    • Required: Yes
    • Behavior: The “Sign Up” button only becomes clickable if this checkbox is checked.

Submit Button

  1. Sign Up
    • Type: Button (button type="submit")
    • ID: submitBtn
    • Behavior: Disabled until the T&C checkbox is checked.

Conditional Behaviors:

  • Dynamic Display: Additional fields for “Other” appear only when “Other” is selected in the gender or sexual orientation dropdowns.
  • Validation: Fields specific to either a single person or a couple are disabled and not submitted based on the selected status.

I cannot project myself and imagine the structure of the “profile.yml” file

So I would like to know how you would imagine such a structure.

What’s the purpose of the profile.yml? Your setup describes a frontend form, so the profile.yml is not really needed, and if it is, it can be much simpler, no? Or you can even use two profile types, profile_single.yml and profile_couple.yml with a lot less logic.

1 Like

I also thought about putting two roles for single and couple.
Ideally I would have liked to have all the registration information in a single role “profile”

That’s why I would like to know what others think about it.

Knowing that a user can visit the profile of another user thanks to a virtual page (named profile/UUID) which displays in addition to the information collected during registration, a biography and media (photo and video) belonging to the registrant

I finally used two roles “profile_single” and “profile_couple”.


My registration form looks like this:

template/registration.php
<form method="post" action="<?= $page->url() ?>">
    <input type="hidden" name="csrf" value="<?= csrf() ?>">
    <div>
        <label for="username">Nom d'utilisateur</label>
        <input required type="text" id="username" name="username" value="<?= esc($data['username'] ?? '', 'attr') ?>">
    </div>
    <div>
        <label for="email">Adresse email</label>
        <input required type="email" id="email" name="email" value="<?= esc($data['email'] ?? '', 'attr') ?>">
    </div>
    <div>
        <label for="password">Mot de passe</label>
        <input required type="password" id="password" name="password" value="<?= esc($data['password'] ?? '', 'attr') ?>">
    </div>
    <div>
        <label for="password_confirm">Confirmer le mot de passe</label>
        <input required type="password" id="password_confirm" name="password_confirm" value="<?= esc($data['password_confirm'] ?? '', 'attr') ?>">
    </div>
    <div>
        <label for="city">Ville</label>
        <input required type="text" id="city" name="city" value="<?= esc($data['city'] ?? '', 'attr') ?>">
    </div>
    <div>
        <label>Statut</label>
        <div>
            <input type="radio" id="single" name="status" value="single" <?= (isset($data['status']) && $data['status'] === 'single') ? 'checked' : '' ?>>
            <label for="single">Célibataire</label>
        </div>
        <div>
            <input type="radio" id="couple" name="status" value="couple" <?= (isset($data['status']) && $data['status'] === 'couple') ? 'checked' : '' ?>>
            <label for="couple">Couple</label>
        </div>
    </div>
    <div>
        <label for="cgu">
            <input required type="checkbox" id="cgu" name="cgu" value="1" <?= isset($data['cgu']) ? 'checked' : '' ?>>
            J'accepte les Conditions Générales d'Utilisation
        </label>
    </div>
    <div>
        <input type="submit" id="submitBtn" name="register" value="S'inscrire" <?= isset($data['cgu']) ? '' : 'disabled' ?>>
    </div>
</form>

the choice between “single” and “couple” allows the choice of the role “profile_single” and “profile_couple”


My registration controller looks like this:

controllers/registration.php
<?php

use Kirby\Exception\PermissionException;

return function ($kirby) {
    // Rediriger les utilisateurs déjà connectés
    if ($kirby->user()) {
        go('home');
    }

    // Créer une liste d'erreurs vide
    $errors = [];

    // Le formulaire a été soumis
    if (get('register') && $kirby->request()->is('POST')) {
        // Valider le token CSRF
        if (csrf(get('csrf')) === true) {
            // Récupérer les données du formulaire
            $data = [
                'email' => get('email'),
                'username' => get('username'),
                'password' => get('password'),
                'password_confirm' => get('password_confirm'),
                'city' => get('city'),
                'status' => get('status'),
            ];
            
            // Déterminer le rôle basé sur le statut
            $role = ($data['status'] === 'single') ? 'profile_single' : 'profile_couple';

            // Définir les règles de validation
            $rules = [
                'email' => ['required', 'email'],
                'username' => ['required', 'minLength' => 3],
                'password' => ['required', 'minLength' => 8],
                'password_confirm' => ['required'],
                'city' => ['required'],
                'status' => ['required'],
            ];

            // Définir les messages d'erreur
            $messages = [
                'email' => 'Veuillez entrer une adresse email valide.',
                'username' => 'Le nom d\'utilisateur doit comporter au moins 3 caractères.',
                'password' => 'Le mot de passe doit comporter au moins 8 caractères.',
                'password_confirm' => 'Veuillez confirmer votre mot de passe.',
                'city' => 'Veuillez entrer votre ville.',
                'status' => 'Veuillez sélectionner votre statut.',
            ];

            // Vérifier si les données sont valides
            if ($invalid = invalid($data, $rules, $messages)) {
                $errors = $invalid;
            } elseif ($data['password'] !== $data['password_confirm']) {
                // Vérifier si les mots de passe correspondent
                $errors[] = 'Les mots de passe ne correspondent pas.';
            } else {

                // Authentifier en tant qu'utilisateur Kirby
                $kirby->impersonate('kirby');

                try {
                    // Créer un nouvel utilisateur
                    $user = $kirby->users()->create([
                        'email' => $data['email'],
                        'role' => $role,
                        'language' => 'fr',
                        'name' => $data['username'],
                        'password' => $data['password'],
                    ]);

                    if (isset($user) === true) {
                        // Créer le défi d'authentification
                        try {
                            $status = $kirby->auth()->createChallenge($user->email(), false, 'login');
                            go('login');
                        } catch (PermissionException $e) {
                            $errors[] = $e->getMessage();
                        }
                    }
                } catch (Exception $e) {
                    $errors[] = $e->getMessage();
                }
            }
        } else {
            $errors[] = 'Invalid CSRF token.';
        }
    }

    return [
        'errors' => $errors,
        'data'   => $data ?? false,
    ];
};

Now I have a problem to display a virtual page for each registered user (using the UUID)

I try to use two separate templates “profile_single.php” and “profile_couple.php” to display the user information in the virtual page “profile/user_UUID”

controllers/profile.php
<?php

return function ($site, $kirby, $pages, $page) {
    // Récupérer l'utilisateur connecté
    $user = $kirby->user();

    // Vérifier si l'utilisateur est connecté
    if (!$user) {
        return $site->visit('login'); // Rediriger vers la page de connexion si non connecté
    }

    // Récupérer le rôle de l'utilisateur
    $role = $user->role()->name();

    // Charger le template approprié en fonction du rôle
    $template = ($role === 'couple') ? 'profile_couple' : 'profile_single';

    // Retourner les informations pour le template
    return [
        'user' => $user,
        'role' => $role,
        'template' => $template,
    ];
};

So I have two templates:

  • profile_single.php : Displays the single person’s information.
  • profile_couple.php : Displays the couple’s information.

Example of a template:

<?php
/* template profile_single */
$user = $page->user();
?>

<h1>Profil Célibataire</h1>
<p>Nom : <?= $user->name() ?></p>
<?php
/* template profile_couple */
$user = $page->user();
?>

<h1>Profil Couple</h1>
<p>Nom du partenaire 1 : <?= $user->partner1() ?></p>
<p>Nom du partenaire 2 : <?= $user->partner2() ?></p>

My route looks like this:

return [
    'routes' => [
        [
            'pattern' => 'profile/(:any)',
            'action'  => function ($uuid) {
                $user = kirby()->users()->findBy('uuid', $uuid);

                if (!$user) {
                    return site()->visit('error'); // Rediriger vers une page d'erreur si l'utilisateur n'existe pas
                }

                return page('profile')->render([
                    'user' => $user,
                ]);
            }
        ]
    ]
];

When I go to a profile with the link: /profile/user_UUID
I simply have an error page.

I don’t know if my code is correct and if the double template can work…

After a day of hard work :exploding_head:, here is a solution that works perfectly with two separate templates for a virtual profile page. The code is simple and I wrapped it in a plugin.

• Template files are separated by role, allowing easy customization of the display based on user type.
• The plugin in plugins/ handles dynamic creation of virtual pages based on user UUIDs, selecting the appropriate template based on the role.


File tree:

site/
├── plugins/
│   └── virtual-profile-pages/
│       └── index.php           # Plugin pour les pages virtuelles de profil
├── blueprints/
│   └── users/
│       ├── profile_single.yml  # Blueprint pour les utilisateurs célibataires
│       └── profile_couple.yml  # Blueprint pour les utilisateurs en couple
└── templates/
    ├── profile.php             # Template générique (optionnel)
    ├── profile-single.php      # Template pour les profils célibataires
    └── profile-couple.php      # Template pour les profils en couple

Here is the content of each file:

plugins/virtual-profile-pages/index.php
<?php

Kirby::plugin('starckio/virtual-profile-pages', [
	'routes' => [
		[
			'pattern' => 'profile/(:any)',
			'action'  => function ($uuid) {
				// Rechercher l'utilisateur par son UUID
				$user = kirby()->users()->findBy('id', $uuid);

				$content = [];
				$template = 'profile'; // Template par défaut

				// Vérifier si l'utilisateur existe
				if (!$user) {
					$content['error'] = 'Utilisateur non trouvé.';
				} 
				// Vérifier si l'utilisateur a le rôle approprié et définir le template en conséquence
				elseif ($user->role()->name() === 'profile_single') {
					$template = 'profile-single';
				} elseif ($user->role()->name() === 'profile_couple') {
					$template = 'profile-couple';
				} else {
					$content['error'] = 'Cet utilisateur n\'a pas le rôle approprié pour accéder à cette page.';
				}

				if (empty($content['error'])) {
					// Récupérer toutes les informations du profil utilisateur
					foreach ($user->content()->data() as $key => $value) {
						$content[$key] = $value;
					}

					// Ajouter des informations supplémentaires si nécessaire
					$content['title'] = $user->name()->value();
					$content['uuid']  = $user->uuid();
				}

				// Créer la page virtuelle en utilisant Page::factory()
				return Page::factory([
					'slug'     => $uuid,
					'template' => $template,
					'model'    => 'ProfilePage',
					'content'  => $content,
				]);
			}
		]
	],
	'pageModels' => [
		'profile' => 'ProfilePage'
	]
]);

class ProfilePage extends Page
{
	public function user()
	{
		return kirby()->users()->findBy('id', $this->uuid());
	}
}
blueprints/users/profile_single.yml
title: Profile célibataire
blueprints/users/profile_couple.yml
title: Profile couple
templates/profile.php
  <?php if ($page->content()->has('error')): ?>
   <h1 class="h1">Erreur</h1>
   <div class="text">
    <p><?= esc($page->error()) ?></p>
   </div>
  <?php else: ?>
   <h1 class="h1"><?= esc($page->title()) ?> (Profil)</h1>
   <div class="text">
    <p>UUID: <?= esc($page->uuid()) ?></p>
    <p>Ce profil n'a pas de rôle spécifique.</p>
   </div>
  <?php endif; ?>
templates/profile-single.php
<article>
  <h1 class="h1"><?= $page->title()->esc() ?></h1>
  <div class="text">
    <h2>Partenaire 1</h2>
     <p><strong>Date de naissance:</strong> <?= esc($page->dob_couple_1()) ?></p>

     <h2>Partenaire 2</h2>
     <p><strong>Date de naissance:</strong> <?= esc($page->dob_couple_2()) ?></p>
  </div>
</article>
templates/profile-couple.php
<article>
  <h1 class="h1"><?= $page->title()->esc() ?></h1>
  <div class="text">
    <p><strong>Date de naissance:</strong> <?= esc($page->dob_single()) ?></p>
  </div>
</article>

Enjoy :blush: