Drupal 8 stable release

This commit is contained in:
Mevaser of Yehudah 2023-12-14 11:37:49 -06:00
commit 8fa0070201
12 changed files with 705 additions and 0 deletions

5
composer.json Normal file
View File

@ -0,0 +1,5 @@
{
"require": {
"mmucklo/email-parse": "*"
}
}

View File

@ -0,0 +1,3 @@
resend_invitation:
body: "[user:display-name],\n\nRemember that invitation?\nYou were invited to help with our website at [site:name]. We created an account for you. You may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in by going to [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\nYou can also scroll to the bottom of the website and look for 'Disciple Login'.\n\n-- Welcome to the [site:name] website team!"
subject: 'Please activate your account at [site:name]'

View File

@ -0,0 +1,7 @@
recruiter.mail:
type: config_object
label: 'Email settings'
mapping:
resend_invitation:
type: mail
label: 'Resend invitation mail.'

6
recruiter.info.yml Normal file
View File

@ -0,0 +1,6 @@
name: Above All - Disciple Recruiter
type: module
description: Site architects can invite coverings, covering can invite disciples to join and contribute
core: 8.x
core_version_requirement: ^8 || ^9 || ^10
package: Above All - Twelve Tribes

58
recruiter.install Normal file
View File

@ -0,0 +1,58 @@
<?php
use Symfony\Component\Yaml\Yaml;
/**
* Create the table recruiter_users and create the recruiter.mail.yml config
*/
function recruiter_update_8001(&$sandbox) {
$schema['recruiter_users'] = [
'description' => 'Keep track of the users invited using the Delegater module',
'fields' => [
'uid' => [
'description' => 'Primary key: {users}.uid for user.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
],
'invitation_date' => [
'description' => 'The identifier of the data.',
'type' => 'varchar_ascii',
'length' => 128,
'not null' => TRUE,
'default' => '',
],
],
'primary key' => ['uid'],
'foreign keys' => [
'uid' => ['users' => 'uid'],
],
];
\Drupal::database()->schema()->createTable('mytable2', $schema['recruiter_users']);
// Drupal 9
// $config_path = drupal_get_path('module', 'recruiter') . '/config/install/recruiter.mail.yml';
// Drupal 10
$extension_path_resolver = Drupal::service('extension.path.resolver');
$config_path = $extension_path_resolver->getPath('module', 'recruiter') . '/config/install/recruiter.mail.yml';
// end of changes
$data = Yaml::parse(file_get_contents($config_path));
\Drupal::configFactory()->getEditable('recruiter.mail')->setData($data)->save(TRUE);
}
/**
* Rename the mytable2 to the correct name.
*/
function recruiter_update_8002(&$sandbox) {
\Drupal::database()->schema()->renameTable('mytable2', 'recruiter_users');
}
/**
* Delete the recruiter_users table.
*/
function recruiter_update_8005(&$sandbox) {
if (\Drupal::database()->schema()->tableExists('recruiter_users')) {
\Drupal::database()->schema()->dropTable('recruiter_users');
}
}

6
recruiter.links.menu.yml Normal file
View File

@ -0,0 +1,6 @@
admin.recruiter.link:
title: Delegater
description: Delegate to disciples to help.
route_name: recruiter.form
parent: user.admin_index
weight: 4

8
recruiter.links.task.yml Normal file
View File

@ -0,0 +1,8 @@
recruiter.invite:
title: 'Disciple Delegater'
route_name: recruiter.form
base_route: recruiter.form
recruiter.InvitedUsersList:
title: 'Pending Users'
route_name: recruiter.invited_users_controller_list
base_route: recruiter.form

101
recruiter.module Normal file
View File

@ -0,0 +1,101 @@
<?php
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_form_alter()
*/
function recruiter_form_user_admin_settings_alter(&$form, $form_state) {
$mail_config = \Drupal::config('recruiter.mail');
$form['email_resend_invite'] = [
'#type' => 'details',
'#title' => t('Resend invitation'),
'#open' => FALSE,
'#description' => t('Send a reminder to the users invited via the Delegate module.'),
'#group' => 'email',
];
$form['email_resend_invite']['resend_invitation_subject'] = [
'#type' => 'textfield',
'#title' => t('Subject'),
'#default_value' => $mail_config->get('resend_invitation.subject'),
'#maxlength' => 180,
];
$form['email_resend_invite']['resend_invitation_body'] = [
'#type' => 'textarea',
'#title' => t('Body'),
'#default_value' => $mail_config->get('resend_invitation.body'),
'#rows' => 15,
];
$form['#submit'][] = 'recruiter_save_email_resend_invite';
}
/**
* Save the changes made to the recruiter email.
*/
function recruiter_save_email_resend_invite(&$form, $form_state) {
\Drupal::configFactory()->getEditable('recruiter.mail')
->set('resend_invitation.body', $form_state->getValue('resend_invitation_body'))
->set('resend_invitation.subject', $form_state->getValue('resend_invitation_subject'))
->save();
}
/**
* Implements hook_mail().
*/
function recruiter_mail($key, &$message, $params) {
$token_service = \Drupal::token();
$language_manager = \Drupal::languageManager();
$langcode = $message['langcode'];
$variables = ['user' => $params['account']];
$language = \Drupal::languageManager()->getLanguage($params['account']->getPreferredLangcode());
$original_language = $language_manager->getConfigOverrideLanguage();
$language_manager->setConfigOverrideLanguage($language);
$mail_config = \Drupal::config('recruiter.mail');
$token_options = [
'langcode' => $langcode,
'callback' => 'recruiter_mail_tokens',
'clear' => TRUE,
];
$message['subject'] .= PlainTextOutput::renderFromHtml($token_service->replace($mail_config->get($key . '.subject'), $variables, $token_options));
$message['body'][] = $token_service->replace($mail_config->get($key . '.body'), $variables, $token_options);
$language_manager->setConfigOverrideLanguage($original_language);
}
/**
* Replace the tokens with data.
*
* @param array $replacements
* Tokens.
* @param array $data
* Data where the tokens will be replaced.
* @param array $options
* Options.
*/
function recruiter_mail_tokens(&$replacements, $data, $options = []) {
if (isset($data['user'])) {
$replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options);
$replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options);
}
}
function recruiter_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.recruiter':
$helpText = <<< EOT
<h2>Disciple Delegater</h2>
<p>This custom module allows disciples to be signed up and assigned to the website team. A Site Architect can invite people to become
Coverings of community pages, and coverings can invite disciples to their communities' pages.<p>
<p>The email template can be <a href="/admin/config/people/accounts#edit-email-admin-created" target="_blank">edited here (new user created
by administrator)</a></p>
EOT;
return($helpText);
}
}

View File

@ -0,0 +1,4 @@
administer recruiter:
title: 'Administer Disciple Delegater'
description: 'Administer the Disciple Delegater system'
restrict access: true

35
recruiter.routing.yml Normal file
View File

@ -0,0 +1,35 @@
recruiter.form:
path: /admin/config/people/recruiter
defaults:
_form: Drupal\recruiter\Form\RecruiterForm
_title: 'Delegate to Disciples'
requirements:
_permission: 'administer recruiter'
recruiter.invited_users_controller_list:
path: '/admin/config/people/recruiter/list'
defaults:
_controller: '\Drupal\recruiter\Controller\UsersInvitedController::invitedUsersList'
_title: "Delegated users who haven't used their account yet."
requirements:
_permission: 'administer recruiter'
recruiter.re_send_invitation:
path: '/admin/config/people/recruiter/re_send_invitation/{user}'
defaults:
_controller: '\Drupal\recruiter\Controller\UsersInvitedController::reSendInvitation'
_title: 'Resend Invitation'
requirements:
_permission: 'administer recruiter'
options:
parameters:
user:
type: entity:user
recruiter.re_send_invitation_all:
path: '/admin/config/people/recruiter/re_send_invitation_all'
defaults:
_controller: '\Drupal\recruiter\Controller\UsersInvitedController::reSendInvitationAll'
_title: 'Resend Invitation All'
requirements:
_permission: 'administer recruiter'

View File

@ -0,0 +1,196 @@
<?php
namespace Drupal\recruiter\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Link;
use Drupal\Core\Mail\MailManagerInterface;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\user\Entity\User;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Class UsersInvitedController.
*
* @package Drupal\recruiter\Controller
*/
class UsersInvitedController extends ControllerBase {
/**
* Mail Manager.
*
* @var \Drupal\Core\Mail
*/
protected $mailManager;
/**
* Date Formatter.
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* UsersInvitedController constructor.
*
* @param \Drupal\Core\Mail\MailManagerInterface $mail_manager
* Mail Manager Instance.
*
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
*/
public function __construct(MailManagerInterface $mail_manager, DateFormatterInterface $date_formatter) {
$this->mailManager = $mail_manager;
$this->dateFormatter = $date_formatter;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.mail'),
$container->get('date.formatter')
);
}
/**
* Invited Users List.
*
* @return string
* Return Hello string.
*/
public function invitedUsersList() {
$header = [
['data' => $this->t('UID'), 'field' => 'ufd.uid'],
['data' => $this->t('Name'), 'field' => 'ufd.name'],
['data' => $this->t('Email'), 'field' => 'ufd.mail'],
['data' => $this->t('Actions'), 'field' => 'actions'],
];
$query = $this->getDatabase()->select('users_field_data', 'ufd')
->fields('ufd', ['uid', 'name', 'mail'])
->where('ufd.pass IS NULL')
->where( 'ufd.uid != 0')
->where( 'ufd.status = 1')
->extend('Drupal\Core\Database\Query\TableSortExtender')
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
->orderByHeader($header);
$data = $query->execute();
$rows = [];
foreach ($data as $row) {
$row = (array) $row;
$link = Link::createFromRoute('Re-Send Invitation', 'recruiter.re_send_invitation', ['user' => $row['uid']]);
$row['actions'] = $link;
$row['name'] = Link::createFromRoute($row['name'], 'entity.user.canonical', ['user' => $row['uid']]);
$rows[] = ['data' => $row];
}
$build['table_pager'][] = [
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
];
$build['table_pager'][] = [
'#type' => 'pager',
];
$resend_all = Link::createFromRoute(
'Re-Send invitation to all the pending users',
'recruiter.re_send_invitation_all',
[],
['attributes' => ['class' => ['button', 'button--primary', 'button--small']]]
);
$build['resend_all'] = $resend_all->toRenderable();
return $build;
}
/**
* Send a email to an specific user.
*
* @param \Drupal\user\UserInterface $user
* User instance.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect response object that may be returned by the controller.
*/
public function reSendInvitation(UserInterface $user) {
$to = $user->getEmail();
$langcode = $user->getPreferredLangcode();
$result = $this->mailManager->mail('recruiter', 'resend_invitation', $to, $langcode, ['account' => $user]);
if (!$result['result']) {
$this->messenger()->addError($this->t('There was a problem sending the email, please try again.'));
}
else {
$this->messenger()->addMessage($this->t('The message has been sent.'));
}
return $this->redirect('recruiter.invited_users_controller_list');
}
/**
* Add an email to all the pending users.
*
* @todo Use the batch API or create a queue.
*/
public function reSendInvitationAll() {
$query = $this->getDatabase()->select('users_field_data', 'ufd')
->fields('ufd', ['uid', 'name', 'mail'])
->where('ufd.pass IS NULL')
->where( 'ufd.uid != 0')
->where( 'ufd.status = 1');
$data = $query->execute();
foreach ($data as $row) {
$row = (array) $row;
$user = User::load($row['uid']);
$to = $user->getEmail();
$langcode = $user->getPreferredLangcode();
$result = $this->mailManager->mail('recruiter', 'resend_invitation', $to, $langcode, ['account' => $user]);
$mail_sent = 0;
$mail_errors = 0;
if (!$result['result']) {
$mail_errors += 0;
}
else {
$mail_sent += 1;
}
if ($mail_errors > 0) {
$message = $this->formatPlural($mail_errors, 'There was a problem sending %count email', 'There was a problem sending %count emails', ['%count' => $mail_errors]);
$this->messenger()->addError($message);
}
if ($mail_sent > 0) {
$message = $this->formatPlural($mail_sent, '%count mail was sent', '%count mails were sent.', ['%count' => $mail_sent]);
$this->messenger()->addMessage($message);
}
}
return $this->redirect('recruiter.invited_users_controller_list');
}
/**
* Get a database instance.
*
* Accord with http://drupal.stackexchange.com/a/213657/4362 is not yet
* possible to inject the database connection.
*
* The idea of this method is having a way to mock the db connection on tests.
*
* @return \Drupal\Core\Database\Connection
* Database connection.
*/
public function getDatabase() {
return \Drupal::database();
}
}

276
src/Form/RecruiterForm.php Normal file
View File

@ -0,0 +1,276 @@
<?php
namespace Drupal\recruiter\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Email\Parse;
use Drupal\user\Entity\User;
use Drupal\Core\Routing\TrustedRedirectResponse;
/**
* Class RecruiterForm.
*/
class RecruiterForm extends FormBase {
protected $invalid_emails;
protected $invitations_sent = [];
protected $emails;
protected $connections;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'recruiter_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Communities List: if admin, show all. Else, show the current users' communities.
$community_names = [];
$current_user_roles = \Drupal::currentUser()->getRoles();
if (in_array('administrator', $current_user_roles)) {
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$query = \Drupal::entityQuery('node')
->condition('status', 1)
// Drupal 10 addition
->accessCheck(TRUE)
->condition('type', 'community')
->execute();
$community_list = $node_storage->loadMultiple($query);
foreach ($community_list as $community) {
$community_names[]= array($community->id() => $community->title->value);
}
} else {
$user = \Drupal\user\Entity\User::load(\Drupal::currentUser()->id());
$cxn2 = $user->get('field_connections')->referencedEntities();
foreach ($cxn2 as $community) {
$community_names[$community->id()] = $community->title->value;
}
}
$help = <<< EOT
<div class="container">
<div class="row mb-2">
<div class="col">
<h3>Workers</h3>
<p><em>Matthew 9:38 'Ask the Lord of the harvest, therefore, to send out workers into his harvest field.'</em></p>
</div>
</div>
<div class="row p-5 color-primary-light">
<div class="col">
<img src="/sites/default/files/theme/workers-1.jpg">
</div>
<div class="col">
<p>Your community can work together to freshen your page. Under your covering, disciples can:</p>
<ul style="columns: 2;-webkit-columns: 2;-moz-columns: 2;">
<li>Add photos</li><li>Create Events</li><li>Write Posts</li><li>Discover Who's Reading</li>
</ul>
</div>
</div>
<div class="row p-5 color-body">
<div class="col">
<p>Invite your team members to make an account. They'll receive and email with a link, and they'll be added to your community's team under your covering.</p>
<p>Type the first name and clan or tribe, then their email address in angle brackets.<BR> <strong>Note: Mezimmah emails will not work.</strong></p>
EOT;
if (in_array('administrator', $current_user_roles)) {
$help .= "<p>Since you are Site Architect, these invitees will be Coverings in role, but you must set the Covering field for each Community they cover.</p>";
}
$help .= <<< EOT
</div>
<div class="col">
<img src="/sites/default/files/theme/workers-2.jpg">
</div>
</div>
<div class="row p-5 justify-content-center color-primary-light">
<div class="col-4">
<p><small>Need to adjust the team? Made a mistake? Questions?</small></p>
<a class="btn btn-primary" href="/contact/user_adjustment">Ask for Help</a>
</div>
<div class="col-4">
<p><small>If there's no response</small></p>
<a class="btn btn-primary" href="/admin/config/people/recruiter/list">Follow Up/Remind</a>
</div>
</div>
<div class="row p-5 color-body">
<div class="col">
EOT;
$help_close = <<< EOT
</div>
</div>
</div>
EOT;
$form['text_header'] = array(
'#prefix' => '',
'#suffix' => '',
'#markup' => $help,
'#weight' => -100,
);
$form['mails'] = array(
'#type' => 'textarea',
'#title' => $this->t('Names and email addresses'),
'#description' => $this->t('Enter one person per line, like this: <code>Username &lt;user@email.com&gt;</code>. Remember, no mezimmah addresses.')
);
// Create the select list
$form['connections'] = array(
'#type' => 'select',
'#title' => t('Add a community to connect to:'),
'#options' => $community_names,
'#description' => t('The requested disciple will be connected to these communities.'),
'#size' => 6,
'#required' => FALSE,
'#sort_options' => TRUE,
'#multiple' => TRUE,
'#attributes' => array(
'id' => 'edit-select-connection',
'style' => 'width:600px',
)
);
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#prefix' => '<div class=force-button-width>',
'#suffix' => '</div>',
'#type' => 'submit',
'#value' => $this->t('Send'),
'#button_type' => 'primary',
);
$form['text_footer'] = array(
'#prefix' => '',
'#suffix' => '',
'#markup' => $help_close,
'#weight' => 500
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$emails = Parse::getInstance()->parse($form_state->getValue('mails'));
foreach ($emails['email_addresses'] as $email) {
if ($email['invalid']) {
$this->invalid_emails[] = "The {$email['original_address']} is not valid because: {$email['invalid_reason']}";
} else {
$this->emails[] = $email;
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->connections=$form_state->getValue('connections');
$number_invitations_sent = $this->createUsers($this->emails,$this->connections);
if (!empty($this->invalid_emails)) {
foreach ($this->invalid_emails as $invalid) {
$this->messenger()->addError($invalid);
}
}
if (!empty($this->invitations_sent)) {
$this->messenger()->addMessage('The invitations were sent to the following mails:');
foreach ($this->invitations_sent as $invitation) {
$this->messenger()->addMessage($invitation);
}
}
$response = new TrustedRedirectResponse('/intro');
$form_state->setResponse($response);
}
/**
* Create new users.
* @return integer the number of sent invitations.
*/
protected function createUsers($emails, $connections) {
if (empty($emails)) {
return 0;
}
$number_invitations_sent = 0;
foreach ($emails as $email) {
$name = (isset($email['name_parsed']) && !empty($email['name_parsed'])) ? $email['name_parsed'] : $email['local_part_parsed'];
// Check if the email hasn't been registered yet.
$query = \Drupal::entityQuery('user', 'OR');
$query->condition('init', $email['simple_address']);
$query->condition('mail', $email['simple_address']);
$query->condition('name', $name);
$entity_ids = $query->execute();
if (!empty($entity_ids)) {
$this->invalid_emails[] = $this->t('The mail: @mail or the user name: @username is already in use.', ['@mail' => $email['simple_address'], '@username' => $name]);
continue;
}
$edit = [];
$edit['name'] = $name;
$edit['mail'] = $email['simple_address'];
$edit['init'] = $email['simple_address'];
$edit['status'] = 1;
$account = User::create($edit);
$current_user_roles = \Drupal::currentUser()->getRoles();
if (in_array('administrator', $current_user_roles)) {
$account->addRole('elder');
}
if (!empty($connections)) {
foreach ($connections as $connection) {
$account->field_connections[] = $connection;
}
}
$account->set("field_bio", "Lives in the Community in ");
$account->save();
$newuserid = $account->id();
if (in_array('administrator', $current_user_roles)) {
// now set all covering fields of these communities to this user
if (!empty($connections)) {
foreach ($connections as $connection) {
$entitiesS = \Drupal::entityTypeManager()->getStorage('node');
$entitiesQ = $entitiesS->getQuery()
->condition('nid',$connection)
// Drupal 10 add this line
->accessCheck(TRUE)
->condition('field_covering',NULL, 'IS')
->execute();
$entities = $entitiesS->loadMultiple($entitiesQ);
if (!empty($entities)) {
$entity = reset($entities);
$entity->set('field_covering', $newuserid);
$entity->save();
}
}
}
}
$params['account'] = $account;
$langcode = $account->getPreferredLangcode();
// Get the custom site notification email to use as the from email address
// if it has been set.
$site_mail = \Drupal::config('system.site')->get('mail_notification');
// If the custom site notification email has not been set, we use the site
// default for this.
if (empty($site_mail)) {
$site_mail = \Drupal::config('system.site')->get('mail');
}
if (empty($site_mail)) {
$site_mail = ini_get('sendmail_from');
}
\Drupal::service('plugin.manager.mail')->mail('user', 'register_admin_created', $account->getEmail(), $langcode, $params, $site_mail);
$number_invitations_sent += 1;
$this->invitations_sent[] = $this->t("@email (username: @username) ", ['@username' => $edit['name'], '@email' => $edit['mail']]);
}
return $number_invitations_sent;
}
}