sample code
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
name: AMCPR User Profile
|
||||
type: module
|
||||
description: 'Handles AMCPR user profiles.'
|
||||
core_version_requirement: ^8.9 || ^9
|
||||
package: 'AMCPR Profile'
|
||||
dependencies:
|
||||
- profile:profile
|
||||
- amcpr_content:amcpr_content
|
||||
- amcpr_components:amcpr_components
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file contains installation and update hooks.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function amcpr_user_profile_install($is_syncing) {
|
||||
$rids = ['screener', 'reviewer'];
|
||||
$roles = Role::loadMultiple($rids);
|
||||
|
||||
foreach ($rids as $rid) {
|
||||
if (!isset($roles[$rid])) {
|
||||
try {
|
||||
/** @var \Drupal\user\RoleInterface $role */
|
||||
$role = Role::create([
|
||||
'id' => 'reviewer',
|
||||
'label' => 'Reviewer'
|
||||
]);
|
||||
$role->save();
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
supplier-wizard:
|
||||
version: 1.x
|
||||
css:
|
||||
theme:
|
||||
css/supplier-wizard.css: {}
|
||||
@@ -0,0 +1,7 @@
|
||||
amcpr_user_profile.admin_config:
|
||||
title: 'AMCPR Supplier Generic Email Servers'
|
||||
description: 'Configure AMCPR Supplier Email Server settings'
|
||||
parent: amcpr_components.admin_config_section
|
||||
route_name: amcpr_user_profile.manage_supplier_settings
|
||||
weight: -100
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Personal Profile View Tab
|
||||
amcpr_user_profile.profile_view:
|
||||
title: 'View'
|
||||
route_name: amcpr_user_profile.profile
|
||||
base_route: amcpr_user_profile.profile
|
||||
|
||||
# Personal Profile Edit Tab
|
||||
amcpr_user_profile.profile_edit:
|
||||
title: 'Edit'
|
||||
route_name: amcpr_user_profile.profile_edit
|
||||
base_route: amcpr_user_profile.profile
|
||||
|
||||
|
||||
# Supplier Profile View Tab
|
||||
amcpr_user_profile.profile_supplier_view:
|
||||
title: 'View'
|
||||
route_name: amcpr_user_profile.profile_supplier
|
||||
base_route: amcpr_user_profile.profile_supplier
|
||||
|
||||
# Supplier Profile Edit Tab
|
||||
amcpr_user_profile.profile_supplier_edit:
|
||||
title: 'Edit'
|
||||
route_name: amcpr_user_profile.profile_supplier_edit
|
||||
base_route: amcpr_user_profile.profile_supplier
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Main module file of the AMCPR User Profile module.
|
||||
*
|
||||
* This module helps implement AMCPR user profiles.
|
||||
*/
|
||||
|
||||
use Drupal\amcpr_user_profile\Controller\SupplierProfileWizardController;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\UserInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Drupal\amcpr_audit\Utility\AuditUtility;
|
||||
|
||||
const AMCPR_SUPPLIER_REPO = 'amcprqrepo';
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_build().
|
||||
*/
|
||||
function amcpr_user_profile_entity_type_build(array &$entity_types) {
|
||||
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
|
||||
$entity_types['profile']->setFormClass('dashboard', 'Drupal\profile\Form\ProfileForm');
|
||||
$entity_types['profile']->setFormClass('wizard', 'Drupal\profile\Form\ProfileForm');
|
||||
$entity_types['profile']->setFormClass('wizard_step_1', 'Drupal\profile\Form\ProfileForm');
|
||||
$entity_types['profile']->setFormClass('wizard_step_2', 'Drupal\profile\Form\ProfileForm');
|
||||
$entity_types['profile']->setFormClass('wizard_step_3', 'Drupal\profile\Form\ProfileForm');
|
||||
$entity_types['profile']->setFormClass('wizard_step_4', 'Drupal\profile\Form\ProfileForm');
|
||||
$entity_types['profile']->setFormClass('wizard_step_5', 'Drupal\profile\Form\ProfileForm');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
* Redirect users to front page after registration.
|
||||
*/
|
||||
function amcpr_user_profile_form_user_register_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
$form['#attributes']['autocomplete'] = 'off';
|
||||
$form['actions']['submit']['#submit'][] = 'amcpr_user_profile_user_register_form_submit_redirect';
|
||||
}
|
||||
|
||||
/**
|
||||
* User register form callback.
|
||||
*/
|
||||
function amcpr_user_profile_user_register_form_submit_redirect(&$form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirect('<front>');
|
||||
$form_state->disableRedirect(FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_personal_wizard_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
|
||||
$form['field_business_category']['#states'] = [
|
||||
'visible' => [
|
||||
':input[name="field_represent_company[value]"]' => ['checked' => TRUE],
|
||||
],
|
||||
];
|
||||
/*
|
||||
$form['field_company_name']['#states'] = [
|
||||
'visible' => [
|
||||
':input[name="field_represent_company[value]"]' => ['checked' => TRUE],
|
||||
],
|
||||
];
|
||||
*/
|
||||
|
||||
foreach (array_keys($form['actions']) as $action) {
|
||||
if ($action == 'submit' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
|
||||
array_unshift($form['actions'][$action]['#submit'], 'amcpr_user_profile_form_profile_personal_wizard_form_submit_before');
|
||||
$form['actions'][$action]['#submit'][] = 'amcpr_user_profile_form_profile_personal_wizard_form_submit_after';
|
||||
|
||||
$form['actions'][$action]['#value'] = t('Next');
|
||||
$form['actions'][$action]['#weight'] = 10;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$form['actions']['previous'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Back'),
|
||||
'#submit' => [
|
||||
'amcpr_user_profile_form_profile_personal_wizard_form_submit_previous'
|
||||
],
|
||||
'#weight' => 0,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous/Back button submit handler callback.
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_personal_wizard_form_submit_previous(array $form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirect('amcpr_user_profile.profile_wizard');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save/Next button submit handler callback.
|
||||
* Executes before other submit handlers.
|
||||
* @see \Drupal\profile\Form\ProfileForm::save()
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_personal_wizard_form_submit_before(array $form, FormStateInterface $form_state) {
|
||||
// Disables all redirects initiated by saving the profile entity.
|
||||
$form_state->disableRedirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save/Next button submit handler callback.
|
||||
* Executes last after all other submit handlers.
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_personal_wizard_form_submit_after(array $form, FormStateInterface $form_state) {
|
||||
$errors = $form_state->getErrors();
|
||||
// We disabled all previous redirects so that we can inject our own.
|
||||
|
||||
if (!$errors) {
|
||||
if ($form_state->get('skip_supplier') == null || !$form_state->get('skip_supplier')) {
|
||||
// Advance form to Step 3: Supplier Prompt
|
||||
$form_state->setRedirect('amcpr_user_profile.profile_wizard_supplier_prompt');
|
||||
} else {
|
||||
// The user is already associated with a supplier (based on email domain)
|
||||
// Advance form to Step 4: Profile wizard end
|
||||
$form_state->setRedirect('amcpr_user_profile.profile_wizard_finish');
|
||||
}
|
||||
$form_state->disableRedirect(FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Implements hook_user_login().
|
||||
*/
|
||||
function amcpr_user_profile_user_insert(Drupal\user\Entity\User $entity){
|
||||
AuditUtility::audit('User Registration','INFO', $entity->get('uid')->getString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_user_login().
|
||||
*/
|
||||
function amcpr_user_profile_user_login(UserInterface $account) {
|
||||
|
||||
// Don't force the user to go through the Profile wizard on password resets.
|
||||
$route = RouteMatch::createFromRequest(\Drupal::request());
|
||||
if ($route->getRouteName() === 'user.reset.login') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Redirect the user to the Profile wizard if their profile is not complete.
|
||||
// A flag to indicate incompleteness is set at the time of registration.
|
||||
$isIncompleteFlag = $account->get('field_profile_incomplete')->getValue();
|
||||
|
||||
// See if the flag value is set to ON (numerical 1).
|
||||
// It means the user has not completed their profile.
|
||||
if (!empty($isIncompleteFlag) && $isIncompleteFlag[0]['value']) {
|
||||
$response = new RedirectResponse(URL::fromRoute('amcpr_user_profile.profile_wizard')
|
||||
->toString());
|
||||
//$response->send();
|
||||
}
|
||||
// A destination was set, probably on an exception controller,
|
||||
elseif (!\Drupal::request()->query->has('destination')) {
|
||||
$response = new RedirectResponse(URL::fromRoute('<front>')->toString());
|
||||
}
|
||||
else {
|
||||
$response = new RedirectResponse(\Drupal::request()->query->get('destination'));
|
||||
}
|
||||
|
||||
//$response->send();
|
||||
// Updating to 9.2 broke sending redirect response from hook_user_login.
|
||||
// See https://www.drupal.org/project/drupal/issues/2852657#comment-14151263.
|
||||
\Drupal::service('request_stack')
|
||||
->getCurrentRequest()
|
||||
->query->set('destination', $response->getTargetUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_alter().
|
||||
*/
|
||||
function amcpr_user_profile_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
// For the ability to change the supplier profile based on email server, restrict user's ability to alter their email address once set
|
||||
if ($form_id == 'user_form'){
|
||||
$user = \Drupal::currentUser();
|
||||
if (!$user->hasPermission('amcpr repo admin')){
|
||||
$form['account']['mail']['#disabled'] = true;
|
||||
$form['account']['mail']['#description'] = t('Only AMCPR Administrators can change an email address.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!preg_match('/^profile_supplier_wizard_step_(\d+)_form$/', $form_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$step = $form_state->get('step');
|
||||
|
||||
foreach (array_keys($form['actions']) as $action) {
|
||||
if ($action == 'submit' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
|
||||
array_unshift($form['actions'][$action]['#submit'], 'amcpr_user_profile_form_profile_supplier_wizard_form_submit_disable');
|
||||
$form['actions'][$action]['#submit'][] = 'amcpr_user_profile_form_profile_supplier_wizard_form_submit_next';
|
||||
|
||||
$form['actions'][$action]['#value'] = ($step < SupplierProfileWizardController::MAX_STEPS)
|
||||
? t('Next')
|
||||
: t('Submit');
|
||||
|
||||
if ($step >= SupplierProfileWizardController::MAX_STEPS) {
|
||||
$form['actions'][$action]['#submit'][] = 'amcpr_user_profile_create_supplier_in_repo';
|
||||
}
|
||||
|
||||
$form['actions'][$action]['#weight'] = 10;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($step > 1) {
|
||||
$form['actions']['previous'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Back'),
|
||||
'#submit' => [
|
||||
'amcpr_user_profile_form_profile_supplier_wizard_form_submit_previous'
|
||||
],
|
||||
'#weight' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$form['#attached']['library'][] = 'amcpr_user_profile/supplier-wizard';
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_supplier_dashboard_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
// Add an additional submit handler to disable redirects after this form
|
||||
// is submitted. Make sure our submit handler is first to execute.
|
||||
array_unshift($form['actions']['submit']['#submit'], 'amcpr_user_profile_submit_handler_disable_redirect');
|
||||
|
||||
$form['actions']['submit']['#submit'][] = 'amcpr_user_profile_form_profile_supplier_dashboard_redirect';
|
||||
$form['actions']['submit']['#submit'][] = 'amcpr_user_profile_push_to_qex';
|
||||
}
|
||||
|
||||
function amcpr_user_profile_form_profile_supplier_dashboard_redirect(array $form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirect('amcpr_user_profile.profile_supplier_edit');
|
||||
$form_state->disableRedirect(FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous/Back button submit handler callback.
|
||||
*
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_supplier_wizard_form_submit_previous(array $form, FormStateInterface $form_state) {
|
||||
$step = $form_state->get('step');
|
||||
$form_state->setRedirect(SupplierProfileWizardController::getPreviousRoute($step));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save/Next button submit handler callback.
|
||||
* Executes before other submit handlers.
|
||||
* @see \Drupal\profile\Form\ProfileForm::save()
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_supplier_wizard_form_submit_disable(array $form, FormStateInterface $form_state) {
|
||||
// Disables all redirects initiated by saving the profile entity.
|
||||
$form_state->disableRedirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appended to the end of callbacks if it was the last step of the supplier
|
||||
*/
|
||||
function amcpr_user_profile_create_supplier_in_repo(array $form, FormStateInterface $form_state) {
|
||||
$errors = $form_state->getErrors();
|
||||
|
||||
if (!$errors) {
|
||||
amcpr_user_profile_create_supplier_from_current_user();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save/Next button submit handler callback.
|
||||
* Executes last after all other submit handlers.
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_supplier_wizard_form_submit_next(array $form, FormStateInterface $form_state) {
|
||||
$errors = $form_state->getErrors();
|
||||
|
||||
// We disabled all previous redirects so that we can inject our own.
|
||||
// Advance form to next step.
|
||||
if (!$errors) {
|
||||
$step = $form_state->get('step');
|
||||
$form_state->setRedirect(SupplierProfileWizardController::getNextRoute($step));
|
||||
$form_state->disableRedirect(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function amcpr_user_profile_form_profile_personal_dashboard_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
|
||||
$form['field_business_category']['#states'] = [
|
||||
'visible' => [
|
||||
':input[name="field_represent_company[value]"]' => ['checked' => TRUE],
|
||||
],
|
||||
];
|
||||
/*
|
||||
$form['field_company_name']['#states'] = [
|
||||
'visible' => [
|
||||
':input[name="field_represent_company[value]"]' => ['checked' => TRUE],
|
||||
],
|
||||
];
|
||||
*/
|
||||
$form_state->setRedirect('amcpr_user_profile.profile');
|
||||
$form['actions']['submit']['#submit'][] = 'amcpr_user_profile_form_profile_personal_dashboard_redirect';
|
||||
}
|
||||
|
||||
function amcpr_user_profile_form_profile_personal_dashboard_redirect(array $form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirect('amcpr_user_profile.profile');
|
||||
$form_state->disableRedirect(FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function amcpr_user_profile_form_user_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
|
||||
/** @var Drupal\user\Entity\User $user */
|
||||
$user = User::load(\Drupal::currentUser()->id());
|
||||
$passResetToken = \Drupal::request()->query->get('pass-reset-token');
|
||||
|
||||
// User could be resetting password using a one-tine link. Tweak the form accordingly.
|
||||
if ($passResetToken) {
|
||||
_amcpr_user_profile_form_user_form_alter_scenario_reset($form, $form_state, $user);
|
||||
}
|
||||
|
||||
// Add an additional submit handler to disable redirects after this form
|
||||
// is submitted. Make sure our submit handler is first to execute.
|
||||
array_unshift($form['actions']['submit']['#submit'], 'amcpr_user_profile_submit_handler_disable_redirect');
|
||||
}
|
||||
|
||||
function _amcpr_user_profile_form_user_form_alter_scenario_reset(&$form, FormStateInterface $form_state, Drupal\user\Entity\User $user) {
|
||||
|
||||
$form['#title'] = t('Password reset');
|
||||
$form['actions']['submit']['#value'] = t('Update');
|
||||
|
||||
// Remove some form items when the form is being accessed via one-time link.
|
||||
unset($form['field_user_avatar']);
|
||||
unset($form['account']['mail']);
|
||||
|
||||
// Determine whether the User is resetting password during the very first
|
||||
// log in (post registration).
|
||||
// A flag is set automatically at the time of registration to indicate
|
||||
// the user has not set their password yet.
|
||||
|
||||
// Fetch the value of the password flag.
|
||||
$isNotSetFlag = $user->get('field_password_not_set')->getValue();
|
||||
|
||||
// See if the field value is set to ON (numerical 1).
|
||||
// It means the user has not set their first password.
|
||||
if (!empty($isNotSetFlag) && $isNotSetFlag[0]['value']) {
|
||||
// Add an additional submit handler to remove the flag once the password is set.
|
||||
$form['actions']['submit']['#submit'][] = 'amcpr_user_profile_submit_handler_remove_password_flag';
|
||||
}
|
||||
|
||||
// Log the user out after they reset their password.
|
||||
$form['actions']['submit']['#submit'][] = 'amcpr_user_profile_submit_handler_logout';
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler to disable redirect after form submission.
|
||||
*/
|
||||
function amcpr_user_profile_submit_handler_disable_redirect(array $form, FormStateInterface $form_state) {
|
||||
// Disables all redirects initiated by the form's default submit handler.
|
||||
$form_state->disableRedirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler callback for User Form.
|
||||
* Removes password flag once the user sets their password.
|
||||
* @throws EntityStorageException
|
||||
*/
|
||||
function amcpr_user_profile_submit_handler_remove_password_flag(array $form, FormStateInterface $form_state) {
|
||||
$errors = $form_state->getErrors();
|
||||
|
||||
if (!$errors) {
|
||||
$user = User::load(\Drupal::currentUser()->id());
|
||||
$user->set('field_password_not_set', 0);
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler callback for User Form.
|
||||
* Logs the user out.
|
||||
*/
|
||||
function amcpr_user_profile_submit_handler_logout(array $form, FormStateInterface $form_state) {
|
||||
$errors = $form_state->getErrors();
|
||||
|
||||
if (!$errors) {
|
||||
$form_state->setRedirect(
|
||||
'user.logout',
|
||||
['logout_method' => 'reset']
|
||||
);
|
||||
|
||||
$form_state->disableRedirect(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function amcpr_user_profile_form_user_login_form_alter(&$form, FormStateInterface $form_state) {
|
||||
$form['reset_password_link'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => t('Reset Password'),
|
||||
'#url' => Url::fromRoute('user.pass'),
|
||||
'#weight' => 100,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_local_tasks_alter().
|
||||
*/
|
||||
function amcpr_user_profile_menu_local_tasks_alter(&$data, $route_name) {
|
||||
if (!empty($data['tabs'])) {
|
||||
if ($route_name == 'user.login'
|
||||
|| $route_name == 'user.register'
|
||||
|| $route_name == 'user.pass') {
|
||||
|
||||
if ( isset($data['tabs'][0]['user.login']) ) {
|
||||
unset($data['tabs'][0]['user.login']);
|
||||
}
|
||||
if ( isset($data['tabs'][0]['user.register']) ) {
|
||||
unset($data['tabs'][0]['user.register']);
|
||||
}
|
||||
if ( isset($data['tabs'][0]['user.pass']) ) {
|
||||
unset($data['tabs'][0]['user.pass']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($route_name == 'entity.user.canonical'
|
||||
|| $route_name == 'entity.user.edit_form'
|
||||
|| $route_name == 'profile.user_page.single') {
|
||||
|
||||
$tabs = array_keys($data['tabs'][0]);
|
||||
foreach ($tabs as $tab) {
|
||||
unset($data['tabs'][0][$tab]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_inline_entity_form_table_fields_alter().
|
||||
*/
|
||||
function amcpr_user_profile_inline_entity_form_table_fields_alter(&$fields, $context) {
|
||||
|
||||
if ($context['entity_type'] == 'node') {
|
||||
$fields['label']['label'] = t('Make');
|
||||
$fields['field_equipment_m'] = [
|
||||
'type' => 'field',
|
||||
'label' => t('Model'),
|
||||
'weight' => 101,
|
||||
];
|
||||
$fields['field_equipment_quantity'] = [
|
||||
'type' => 'field',
|
||||
'label' => t('Quantity'),
|
||||
'weight' => 110,
|
||||
];
|
||||
|
||||
unset($fields['status']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
|
||||
amcpr_user_profile.manage_supplier_settings:
|
||||
path: '/admin/config/supplier_settings'
|
||||
defaults:
|
||||
_form: '\Drupal\amcpr_user_profile\Form\SupplierSettingsForm'
|
||||
_title: 'Manage Supplier Rules'
|
||||
requirements:
|
||||
_permission: 'amcpr repo admin'
|
||||
|
||||
amcpr_user_profile.profile_wizard_exit:
|
||||
path: '/profile/wizard/exit'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\PersonalProfileWizardController::exitCompleteProfileWizard'
|
||||
_title: 'Exit Profile Wizard'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Wizard: Step 1
|
||||
amcpr_user_profile.profile_wizard:
|
||||
path: '/profile/wizard'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\PersonalProfileWizardController::showCompleteProfileWizard'
|
||||
_title: 'Complete Your Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Wizard: Step 2
|
||||
amcpr_user_profile.profile_wizard_additional_info:
|
||||
path: '/profile/wizard/additional-info'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\PersonalProfileWizardController::showAdditionalProfileInfoWizard'
|
||||
_title: 'Complete Your Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Wizard: Step 3
|
||||
amcpr_user_profile.profile_wizard_supplier_prompt:
|
||||
path: '/profile/wizard/supplier_prompt'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\PersonalProfileWizardController::showSupplierPromptWizard'
|
||||
_title: 'Complete Your Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Wizard: Step 4a
|
||||
amcpr_user_profile.profile_wizard_finish:
|
||||
path: '/profile/wizard/finish'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\PersonalProfileWizardController::finishCompleteProfileWizard'
|
||||
_title: 'Complete Your Profile'
|
||||
continue_supplier: FALSE
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Wizard: Step 4b
|
||||
amcpr_user_profile.profile_wizard_continue_supplier:
|
||||
path: '/profile/wizard/supplier-info'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\PersonalProfileWizardController::finishCompleteProfileWizard'
|
||||
_title: 'Complete Your Profile'
|
||||
continue_supplier: 'TRUE'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
|
||||
|
||||
# Supplier Profile Wizard: Step 1
|
||||
amcpr_user_profile.supplier_profile_wizard:
|
||||
path: '/profile/supplier/wizard'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\SupplierProfileWizardController::business'
|
||||
_title: 'Create Supplier Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Supplier Profile Wizard: Step 2
|
||||
amcpr_user_profile.supplier_profile_wizard_certifications:
|
||||
path: '/profile/supplier/wizard/certifications'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\SupplierProfileWizardController::certifications'
|
||||
_title: 'Create Supplier Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Supplier Profile Wizard: Step 3
|
||||
amcpr_user_profile.supplier_profile_wizard_materials:
|
||||
path: '/profile/supplier/wizard/materials'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\SupplierProfileWizardController::materials'
|
||||
_title: 'Create Supplier Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Supplier Profile Wizard: Step 4
|
||||
amcpr_user_profile.supplier_profile_wizard_equipments:
|
||||
path: '/profile/supplier/wizard/equipments'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\SupplierProfileWizardController::equipments'
|
||||
_title: 'Create Supplier Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Supplier Profile Wizard: Step 5
|
||||
amcpr_user_profile.supplier_profile_wizard_specialities:
|
||||
path: '/profile/supplier/wizard/specialities'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\SupplierProfileWizardController::specialities'
|
||||
_title: 'Create Supplier Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Supplier Profile Wizard: Step 6
|
||||
amcpr_user_profile.supplier_profile_wizard_finish:
|
||||
path: '/profile/supplier/wizard/finish'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\SupplierProfileWizardController::finishWizard'
|
||||
_title: 'Create Supplier Profile'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Account Settings Form
|
||||
amcpr_user_profile.account_preferences:
|
||||
path: '/account/preferences'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::accountPreferences'
|
||||
_title: 'Account Preferences'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Personal Profile Page
|
||||
amcpr_user_profile.profile:
|
||||
path: '/account/profile'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::personal'
|
||||
_title: 'Personal Profile'
|
||||
requirements:
|
||||
_custom_access: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::profileAccess'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Personal Profile Form
|
||||
amcpr_user_profile.profile_edit:
|
||||
path: '/account/profile/edit'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::personalForm'
|
||||
_title: 'Personal Profile'
|
||||
requirements:
|
||||
_custom_access: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::profileAccess'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Supplier Profile Page
|
||||
amcpr_user_profile.profile_supplier:
|
||||
path: '/account/profile/supplier'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::supplier'
|
||||
_title: 'Supplier Profile'
|
||||
requirements:
|
||||
_custom_access: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::supplierProfileAccess'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
|
||||
# Supplier Profile Form
|
||||
amcpr_user_profile.profile_supplier_edit:
|
||||
path: '/account/profile/supplier/edit'
|
||||
defaults:
|
||||
_controller: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::supplierForm'
|
||||
_title: 'Supplier Profile'
|
||||
requirements:
|
||||
_custom_access: '\Drupal\amcpr_user_profile\Controller\ProfileDashboardController::supplierProfileAccess'
|
||||
options:
|
||||
no_cache: 'TRUE'
|
||||
@@ -0,0 +1,18 @@
|
||||
# Register our Route Subscriber
|
||||
services:
|
||||
amcpr_user_profile.route_subscriber:
|
||||
class: Drupal\amcpr_user_profile\Routing\ProfileRouteSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
|
||||
amcpr_user_profile.request_event_subscriber:
|
||||
class: Drupal\amcpr_user_profile\EventSubscriber\RequestEventSubscriber
|
||||
arguments: ['@current_user']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
|
||||
amcpr.user.utility:
|
||||
class: Drupal\amcpr_user_profile\Service\UserProfileService
|
||||
arguments: ['@entity_type.manager', '@current_user']
|
||||
tags:
|
||||
- { name: amcpr }
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
/**
|
||||
*Primary Material Experience Step
|
||||
*/
|
||||
#profile-supplier-wizard-step-3-form .layout__region.layout__region--second {
|
||||
margin: 0 1.5em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 40em) {
|
||||
#profile-supplier-wizard-step-3-form .layout__region.layout__region--first,
|
||||
#profile-supplier-wizard-step-3-form .layout__region.layout__region--third {
|
||||
flex: 0 1 31%;
|
||||
}
|
||||
|
||||
#profile-supplier-wizard-step-3-form .layout__region.layout__region--second {
|
||||
flex: 0 1 32%;
|
||||
}
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Controller;
|
||||
|
||||
use Drupal\amcpr_user_profile\UserProfileServiceInterface;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
class PersonalProfileWizardController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\amcpr_user_profile\UserProfileServiceInterface
|
||||
*/
|
||||
protected $amcprUserService;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\TempStore\PrivateTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* Constructs a new PersonalProfileWizardController.
|
||||
*
|
||||
* @param \Drupal\amcpr_user_profile\UserProfileServiceInterface $user_profile_service
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The temp store factory.
|
||||
*/
|
||||
public function __construct(UserProfileServiceInterface $user_profile_service, PrivateTempStoreFactory $temp_store_factory) {
|
||||
// Initialize private members.
|
||||
$this->currentUser();
|
||||
$this->messenger();
|
||||
$this->entityTypeManager();
|
||||
$this->entityFormBuilder();
|
||||
|
||||
$this->amcprUserService = $user_profile_service;
|
||||
$this->tempStore = $temp_store_factory->get('amcpr_profile_controller');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('amcpr.user.utility'),
|
||||
$container->get('tempstore.private')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a wizard to complete profile.
|
||||
*
|
||||
* @param bool $show_exit_meesage
|
||||
* Show a message if the flag is set to TRUE.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The name of the route to which to redirect.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function exitCompleteProfileWizard($show_exit_meesage = TRUE, $route_name = '<front>') {
|
||||
$this->tempStore->delete('step');
|
||||
|
||||
if ($show_exit_meesage) {
|
||||
$this->messenger->addMessage($this->t('You have exited from completing the profile.'));
|
||||
}
|
||||
|
||||
return $this->redirect($route_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1:
|
||||
* Present a wizard to complete profile.
|
||||
*
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function showCompleteProfileWizard() {
|
||||
|
||||
if ($this->amcprUserService->isProfileIncomplete()) {
|
||||
$this->tempStore->set('step', 1);
|
||||
}
|
||||
else {
|
||||
$this->messenger->addMessage($this->t('Your profile has been completed already.'));
|
||||
return $this->exitCompleteProfileWizard(FALSE);
|
||||
}
|
||||
|
||||
return [
|
||||
'#title' => $this->t("Let's Complete Your Profile"),
|
||||
'#theme' => 'amcpr_profile_wizard',
|
||||
'#profiles' => $this->amcprUserService->getProfileTypes(),
|
||||
'#lead_text' => $this->t('Completing your profile will allow you to be a...'),
|
||||
'#next_step_path' => Url::fromRoute('amcpr_user_profile.profile_wizard_additional_info')->toString(),
|
||||
'#exit_wizard_path' => Url::fromRoute('amcpr_user_profile.profile_wizard_exit')->toString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2:
|
||||
* Present the Additional Information step in the Complete Profile wizard.
|
||||
*
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function showAdditionalProfileInfoWizard() {
|
||||
$step = $this->tempStore->get('step');
|
||||
|
||||
// Need to start at step 1.
|
||||
if (!$step && $step != 1) {
|
||||
return $this->redirect('amcpr_user_profile.profile_wizard');
|
||||
}
|
||||
|
||||
|
||||
// get user personal profile
|
||||
$personalProfile = $this->amcprUserService->getPersonalProfile();
|
||||
|
||||
if (!$personalProfile) {
|
||||
$this->messenger->addError($this->t('Registration error... Error code 1200-01.'));
|
||||
return $this->exitCompleteProfileWizard(FALSE);
|
||||
}
|
||||
|
||||
// Advance current step.
|
||||
if ($this->amcprUserService->getSupplierProfile(FALSE) == null) {
|
||||
//User's email domain is not associated with a supplier, ask if they want to create supplier profile
|
||||
$this->tempStore->set('step', 2);
|
||||
$skipSupplier = false;
|
||||
} else {
|
||||
//Email is already associated with a supplier, skip supplier step (handled in amcpr_user_profile_form_profile_personal_wizard_form_submit_after)
|
||||
$this->tempStore->set('step', 3);
|
||||
$skipSupplier = true;
|
||||
}
|
||||
$form = $this->entityFormBuilder->getForm($personalProfile, 'wizard', ['skip_supplier' => $skipSupplier]);
|
||||
|
||||
/** @var \Drupal\Core\Render\Renderer $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
return [
|
||||
'#title' => $this->t("Let's Complete Your Profile"),
|
||||
'#theme' => 'amcpr_profile_wizard_form',
|
||||
'#form_title' => $this->t("Additional Profile Information"),
|
||||
//'#form' => $renderer->render($form),
|
||||
'#form' => $form,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3:
|
||||
* Present the Supplier Prompt step in the Complete Profile wizard.
|
||||
*
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function showSupplierPromptWizard() {
|
||||
$step = $this->tempStore->get('step');
|
||||
|
||||
// Should come from step 2.
|
||||
if (!$step && $step != 2) {
|
||||
return $this->redirect('amcpr_user_profile.profile_wizard');
|
||||
}
|
||||
|
||||
// Advance current step.
|
||||
$this->tempStore->set('step', 3);
|
||||
|
||||
return [
|
||||
'#title' => $this->t('Thank you for updating your information!'),
|
||||
'#theme' => 'amcpr_profile_wizard_prompt',
|
||||
'#prompt_title' => $this->t("Do you represent a manufacturing organization (supplier)?"),
|
||||
'#prompt_text' => $this->t("If so, we need some additional information before we can help connect you with organizations in need."),
|
||||
'#confirm' => [
|
||||
'text' => $this->t('Yes'),
|
||||
'path' => Url::fromRoute('amcpr_user_profile.profile_wizard_continue_supplier')->toString(),
|
||||
],
|
||||
'#cancel' => [
|
||||
'text' => $this->t('No'),
|
||||
'path' => Url::fromRoute('amcpr_user_profile.profile_wizard_finish')->toString(),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4
|
||||
* End of complete profile wizard. Update flags accordingly.
|
||||
*
|
||||
* @param bool $continue_supplier
|
||||
* A boolean flag indicating whether to advance to supplier profile info.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function finishCompleteProfileWizard(bool $continue_supplier) {
|
||||
$step = $this->tempStore->get('step');
|
||||
|
||||
// Should come from step 3.
|
||||
if (!$step && $step != 3) {
|
||||
return $this->redirect('amcpr_user_profile.profile_wizard');
|
||||
}
|
||||
|
||||
// Unset incomplete profile flag.
|
||||
$this->amcprUserService->removeProfileIncompleteFlag();
|
||||
$this->amcprUserService->killUserPrimaryNavBlockCache();
|
||||
$this->messenger->addMessage($this->t('You have have completed your profile.'));
|
||||
|
||||
if (!$continue_supplier) {
|
||||
return $this->exitCompleteProfileWizard(FALSE);
|
||||
}
|
||||
else {
|
||||
return $this->exitCompleteProfileWizard(FALSE, 'amcpr_user_profile.supplier_profile_wizard');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Controller;
|
||||
|
||||
use Drupal\amcpr_user_profile\UserProfileServiceInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ProfileDashboardController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\amcpr_user_profile\UserProfileServiceInterface
|
||||
*/
|
||||
protected $amcprUserService;
|
||||
|
||||
/**
|
||||
* Constructs a new ProfileDashboardController.
|
||||
*
|
||||
* @param \Drupal\amcpr_user_profile\UserProfileServiceInterface $user_profile_service
|
||||
*/
|
||||
public function __construct(UserProfileServiceInterface $user_profile_service) {
|
||||
// Initialize private members.
|
||||
$this->currentUser();
|
||||
$this->messenger();
|
||||
$this->entityTypeManager();
|
||||
$this->entityFormBuilder();
|
||||
|
||||
$this->amcprUserService = $user_profile_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('amcpr.user.utility')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @return array
|
||||
* Renderable array.
|
||||
*/
|
||||
public function account() {
|
||||
return [
|
||||
'#markup' => '<h3>Deprecated.</h3>',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* Renderable array.
|
||||
*/
|
||||
public function accountPreferences() {
|
||||
$user = $this->amcprUserService->getCurrentUser();
|
||||
if ($user) {
|
||||
$form = $this->entityFormBuilder->getForm($user, 'default');
|
||||
|
||||
// These elements should only be rendered on the admin interface.
|
||||
unset($form['account']['name']);
|
||||
unset($form['account']['status']);
|
||||
unset($form['account']['roles']);
|
||||
unset($form['account']['notify']);
|
||||
|
||||
return $form;
|
||||
}
|
||||
else {
|
||||
return [
|
||||
'#markup' => '<h3>Something went wrong.</h3>',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* Renderable array.
|
||||
*/
|
||||
public function personal() {
|
||||
if (empty($this->currentUser()->id())){
|
||||
return $this->redirect('user.login');
|
||||
}
|
||||
$personalProfile = $this->amcprUserService->getPersonalProfile();
|
||||
$builder = $this->amcprUserService->getProfileViewBuilder();
|
||||
$build = [];
|
||||
$build['entity'] = [
|
||||
'entity' => $builder->view($personalProfile, 'default')
|
||||
];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* Renderable array.
|
||||
*/
|
||||
public function personalForm() {
|
||||
$personalProfile = $this->amcprUserService->getPersonalProfile(TRUE);
|
||||
return $this->entityFormBuilder->getForm($personalProfile, 'dashboard');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* Renderable array.
|
||||
*/
|
||||
public function supplier() {
|
||||
$supplierProfile = $this->amcprUserService->getSupplierProfile();
|
||||
$builder = $this->amcprUserService->getProfileViewBuilder();
|
||||
$build = [];
|
||||
$build['entity'] = [
|
||||
'entity' => $builder->view($supplierProfile, 'default')
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* Renderable array.
|
||||
*/
|
||||
public function supplierForm() {
|
||||
$currUserEmail = \Drupal::currentUser()->getEmail();
|
||||
$currUserServer = strtolower(substr($currUserEmail, strpos($currUserEmail, "@") + 1));
|
||||
$configName = (\Drupal\amcpr_user_profile\Form\SupplierSettingsForm::CONFIG_NAME);
|
||||
$config = \Drupal::config($configName);
|
||||
$serverListString = $config->get('server_list');
|
||||
$serverListArray = preg_split('/(,|\r\n|\n|\r| )/', $serverListString);
|
||||
$isInServerList = FALSE;
|
||||
foreach($serverListArray as $indivServer){
|
||||
if (strtolower($indivServer) == $currUserServer){
|
||||
$isInServerList = TRUE;
|
||||
}
|
||||
}
|
||||
if (!$isInServerList){
|
||||
\Drupal::messenger()->addStatus(t('This is the supplier information for all users who have the email ending in @:thing.', [':thing' => $currUserServer]));
|
||||
}
|
||||
$supplierProfile = $this->amcprUserService->getSupplierProfile();
|
||||
return $this->entityFormBuilder->getForm($supplierProfile, 'dashboard');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for personal profile request.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function profileAccess() {
|
||||
return AccessResult::allowedIf(!$this->amcprUserService->isProfileIncomplete());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for supplier profile request.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function supplierProfileAccess() {
|
||||
return AccessResult::allowedIf(!$this->amcprUserService->isProfileIncomplete() && !$this->amcprUserService->isSupplierProfileIncomplete());
|
||||
}
|
||||
|
||||
private function completeProfilePrompt() {
|
||||
return [
|
||||
'#theme' => 'amcpr_profile_wizard_prompt',
|
||||
'#prompt_title' => $this->t("Your profile is incomplete."),
|
||||
'#prompt_text' => $this->t("Do you wish to complete it now?"),
|
||||
'#confirm' => [
|
||||
'text' => $this->t('Yes'),
|
||||
'path' => Url::fromRoute('amcpr_user_profile.profile_wizard')->toString(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function completeSupplierProfilePrompt() {
|
||||
return [
|
||||
'#theme' => 'amcpr_profile_wizard_prompt',
|
||||
'#prompt_title' => $this->t("You have not created a supplier profile."),
|
||||
'#prompt_text' => $this->t("Do you wish to create it now?"),
|
||||
'#confirm' => [
|
||||
'text' => $this->t('Yes'),
|
||||
'path' => Url::fromRoute('amcpr_user_profile.supplier_profile_wizard')->toString(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\user\Controller\UserController;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* Controller routines for overridden user routes.
|
||||
* @see \Drupal\amcpr_user_profile\Routing\ProfileRouteSubscriber
|
||||
*/
|
||||
class ProfileUserController extends UserController {
|
||||
|
||||
/**
|
||||
* @param string $logout_method
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function customLogout($logout_method = 'user') {
|
||||
$this->logout();
|
||||
|
||||
$logout_method = strtolower($logout_method);
|
||||
if ($logout_method === 'reset') {
|
||||
return $this->redirect('<front>', [], ['query' => ['method' => 'reset']]);
|
||||
}
|
||||
else {
|
||||
return $this->redirect('<front>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function userPage() {
|
||||
return $this->redirect('amcpr_user_profile.account_preferences');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetPassLogin($uid, $timestamp, $hash) {
|
||||
//@TODO reduce duplicated code by calling parent method.
|
||||
|
||||
// The current user is not logged in, so check the parameters.
|
||||
$current = REQUEST_TIME;
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = $this->userStorage->load($uid);
|
||||
|
||||
// Verify that the user exists and is active.
|
||||
if ($user === NULL || !$user->isActive()) {
|
||||
// Blocked or invalid user ID, so deny access. The parameters will be in
|
||||
// the watchdog's URL for the administrator to check.
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
// Time out, in seconds, until login URL expires.
|
||||
$timeout = $this->config('user.settings')->get('password_reset_timeout');
|
||||
// No time out for first time login.
|
||||
if ($user->getLastLoginTime() && $current - $timestamp > $timeout) {
|
||||
$this->messenger()->addError($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
|
||||
return $this->redirect('user.pass');
|
||||
}
|
||||
elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && hash_equals($hash, user_pass_rehash($user, $timestamp))) {
|
||||
user_login_finalize($user);
|
||||
$this->logger->notice('User %name used one-time login link at time %timestamp.', ['%name' => $user->getDisplayName(), '%timestamp' => $timestamp]);
|
||||
$this->messenger()->addStatus($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'));
|
||||
// Let the user's password be changed without the current password
|
||||
// check.
|
||||
$token = Crypt::randomBytesBase64(55);
|
||||
$_SESSION['pass_reset_' . $user->id()] = $token;
|
||||
// Clear any flood events for this user.
|
||||
$this->flood->clear('user.password_request_user', $uid);
|
||||
return $this->redirect(
|
||||
'amcpr_user_profile.account_preferences',
|
||||
[],
|
||||
[
|
||||
'query' => ['pass-reset-token' => $token],
|
||||
'absolute' => TRUE,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->messenger()->addError($this->t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'));
|
||||
return $this->redirect('user.pass');
|
||||
}
|
||||
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Controller;
|
||||
|
||||
use Drupal\amcpr_user_profile\UserProfileServiceInterface;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class SupplierProfileWizardController extends ControllerBase {
|
||||
|
||||
const MAX_STEPS = 5;
|
||||
|
||||
const ROUTES = [
|
||||
1 => 'amcpr_user_profile.supplier_profile_wizard',
|
||||
2 => 'amcpr_user_profile.supplier_profile_wizard_certifications',
|
||||
3 => 'amcpr_user_profile.supplier_profile_wizard_materials',
|
||||
4 => 'amcpr_user_profile.supplier_profile_wizard_equipments',
|
||||
5 => 'amcpr_user_profile.supplier_profile_wizard_specialities',
|
||||
6 => 'amcpr_user_profile.supplier_profile_wizard_finish',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\amcpr_user_profile\UserProfileServiceInterface
|
||||
*/
|
||||
protected $amcprUserService;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\TempStore\PrivateTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* Constructs a new SupplierProfileWizardController.
|
||||
*
|
||||
* @param \Drupal\amcpr_user_profile\UserProfileServiceInterface $user_profile_service
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The temp store factory.
|
||||
*/
|
||||
public function __construct(UserProfileServiceInterface $user_profile_service, PrivateTempStoreFactory $temp_store_factory) {
|
||||
// Initialize private members.
|
||||
$this->currentUser();
|
||||
$this->messenger();
|
||||
$this->entityTypeManager();
|
||||
$this->entityFormBuilder();
|
||||
|
||||
$this->amcprUserService = $user_profile_service;
|
||||
$this->tempStore = $temp_store_factory->get('amcpr_supplier_profile_controller');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('amcpr.user.utility'),
|
||||
$container->get('tempstore.private')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit from the Supplier Profile wizard.
|
||||
*
|
||||
* @param bool $show_exit_meesage
|
||||
* Show a message if the flag is set to TRUE.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function exitWizard($show_exit_meesage = TRUE) {
|
||||
$this->tempStore->delete('step');
|
||||
|
||||
if ($show_exit_meesage) {
|
||||
$this->messenger->addMessage($this->t('You have exited from completing the supplier profile.'));
|
||||
}
|
||||
|
||||
return $this->redirect('<front>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: Show supplier profile wizard (begin wizard).
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function business() {
|
||||
if ($this->amcprUserService->isSupplierProfileIncomplete()) {
|
||||
return $this->form(1, 'Manufacturing Business Type');
|
||||
}
|
||||
else {
|
||||
return $this->redirect('amcpr_user_profile.profile_supplier_edit');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Certifications
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function certifications() {
|
||||
return $this->form(2, 'Manufacturing Certificates');
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: Materials
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function materials() {
|
||||
return $this->form(3, 'Primary Material Experience');
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4: Equipments
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function equipments() {
|
||||
return $this->form(4, 'Manufacturing Equipment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 5: Specialities
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function specialities() {
|
||||
return $this->form(5, 'Other Specialities (select all that apply)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 6: Profile completed (end wizard).
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
public function finishWizard(Request $request) {
|
||||
$step = $this->tempStore->get('step');
|
||||
|
||||
// Should be at step 5.
|
||||
if ($step != 5 && $request->getMethod() == 'GET') {
|
||||
return $this->redirect('amcpr_user_profile.supplier_profile_wizard');
|
||||
}
|
||||
|
||||
$this->messenger->addMessage($this->t('Thank you! Your Supplier profile has been saved.'));
|
||||
$this->amcprUserService->removeSupplierProfileIncompleteFlag();
|
||||
$this->amcprUserService->killUserPrimaryNavBlockCache();
|
||||
return $this->exitWizard(FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route for the next step;
|
||||
*
|
||||
* @param int $step
|
||||
* The current step.
|
||||
*
|
||||
* @return string
|
||||
* Return the route.
|
||||
*/
|
||||
public static function getNextRoute($step) {
|
||||
switch($step) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
return static::ROUTES[$step + 1];
|
||||
default:
|
||||
return static::ROUTES[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route for the previous step;
|
||||
*
|
||||
* @param int $step
|
||||
* The current step.
|
||||
*
|
||||
* @return string
|
||||
* Return the route.
|
||||
*/
|
||||
public static function getPreviousRoute($step) {
|
||||
switch($step) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
return static::ROUTES[$step - 1];
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method controller's action methods.
|
||||
*
|
||||
* @param int $step
|
||||
* @param string $form_title
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* Return renderable build array. User maybe redirected on error.
|
||||
* @throws \Drupal\Core\TempStore\TempStoreException
|
||||
*/
|
||||
private function form($step, $form_title) {
|
||||
|
||||
// Get user supplier profile.
|
||||
$supplierProfile = $this->amcprUserService->getSupplierProfile(TRUE);
|
||||
|
||||
if (!$supplierProfile) {
|
||||
$this->messenger->addError($this->t('Registration error... Error code 1200-02.'));
|
||||
return $this->exitWizard(FALSE);
|
||||
}
|
||||
|
||||
// Save current step.
|
||||
$this->tempStore->set('step', $step);
|
||||
|
||||
// Display form according the the current step.
|
||||
$viewMode = 'wizard_step_' . $step;
|
||||
$form = $this->entityFormBuilder->getForm(
|
||||
$supplierProfile,
|
||||
$viewMode,
|
||||
['step' => $step]
|
||||
);
|
||||
|
||||
return [
|
||||
'#title' => $this->t("Let's Complete Your Supplier Profile"),
|
||||
'#theme' => 'amcpr_profile_wizard_form',
|
||||
'#form_title' => $this->t($form_title),
|
||||
'#form' => $form,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
|
||||
/**
|
||||
* Event Subscriber UserLogoutEventSubscriber.
|
||||
*/
|
||||
class RequestEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The current account.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Constructs a new RequestEventSubscriber.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(AccountInterface $account) {
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set message when the user is logged out after password reset.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onKernelRequest(RequestEvent $event) {
|
||||
$request = $event->getRequest();
|
||||
$route_match = RouteMatch::createFromRequest($request);
|
||||
|
||||
// Don't let the user wander around the site if they used
|
||||
// a one-time login link.
|
||||
if ($this->account->isAuthenticated()) {
|
||||
|
||||
$resetTokenKey = 'pass_reset_' . $this->account->id();
|
||||
if (isset($_SESSION[$resetTokenKey]) && !$request->query->get('pass-reset-token') && $route_match->getRouteName() !== 'user.logout' ) {
|
||||
|
||||
// Redirect user back to password reset form.
|
||||
\Drupal::messenger()->addMessage(t('You must reset your password.'));
|
||||
$event->setResponse(
|
||||
new RedirectResponse(Url::fromRoute(
|
||||
'amcpr_user_profile.account_preferences',
|
||||
[],
|
||||
[
|
||||
'query' => ['pass-reset-token' => $_SESSION[$resetTokenKey]],
|
||||
'absolute' => TRUE,
|
||||
]
|
||||
)->toString())
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ($route_match->getRouteName() === 'entity.node.canonical') {
|
||||
$queryParam = $request->query->get('method');
|
||||
if ($queryParam === 'reset') {
|
||||
\Drupal::messenger()->addMessage(t('You have been logged out. Please log back in using the new password.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return [
|
||||
KernelEvents::REQUEST => 'onKernelRequest',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Configure TDP submission settings.
|
||||
*/
|
||||
class SupplierSettingsForm extends ConfigFormBase {
|
||||
|
||||
public const FORM_ID = 'amcpr_user_profile_settings_form';
|
||||
public const CONFIG_NAME = 'amcpr_user_profile.settings';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return self::FORM_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return [
|
||||
SupplierSettingsForm::CONFIG_NAME,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config(SupplierSettingsForm::CONFIG_NAME);
|
||||
|
||||
$serverList = $config->get('server_list');
|
||||
$form['server_list'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Server Listing'),
|
||||
'#description' => t('Users that use the listed email servers will have the ability to create their own supplier, and will not have a shared supplier for the email server. Separate servers by commas or new line.'),
|
||||
'#default_value' => ($serverList) ? $serverList : '',
|
||||
'#format' => 'text',
|
||||
// '#allowed_formats' => ['basic_html'],
|
||||
];
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->config(SupplierSettingsForm::CONFIG_NAME)
|
||||
->set('server_list', $form_state->getValue('server_list'))
|
||||
->save();
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Plugin\Block;
|
||||
|
||||
use Drupal\amcpr_components\ComponentBuilder;
|
||||
use Drupal\amcpr_user_profile\UserProfileServiceInterface;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a 'User Profile' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "amcpr_account_preference_links_block",
|
||||
* admin_label = @Translation("AMCPR Account Preference Links Block"),
|
||||
* )
|
||||
*/
|
||||
class AccountPreferenceLinksBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\amcpr_user_profile\UserProfileServiceInterface
|
||||
*/
|
||||
protected $amcprUserProfileService;
|
||||
|
||||
/**
|
||||
* @var \Drupal\User\UserInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* UserProfileBlock constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* @param $plugin_id
|
||||
* @param $plugin_definition
|
||||
* @param \Drupal\amcpr_user_profile\UserProfileServiceInterface $user_profile_service
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, UserProfileServiceInterface $user_profile_service) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->amcprUserProfileService = $user_profile_service;
|
||||
$this->currentUser = $this->amcprUserProfileService->getCurrentUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('amcpr.user.utility')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
if (!$this->currentUser->isAuthenticated()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$links[] = [
|
||||
'url' => Url::fromRoute('amcpr_user_profile.account_preferences'),
|
||||
'title' => $this->t('Sign in & Security'),
|
||||
'style' => 'primary'
|
||||
];
|
||||
|
||||
$build = ComponentBuilder::buildCard(
|
||||
'Account Preferences',
|
||||
$links,
|
||||
);
|
||||
$build['#cache'] = ['contexts' => ['user']];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Plugin\Block;
|
||||
|
||||
use Drupal\amcpr_notification\Service\NotificationService;
|
||||
use Drupal\amcpr_user_profile\UserProfileServiceInterface;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a 'User Primary Nav' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "amcpr_user_block",
|
||||
* admin_label = @Translation("User Primary Nav Block"),
|
||||
* )
|
||||
*/
|
||||
class UserPrimaryNavBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
// @todo This value should be taken from block settings.
|
||||
const MAX_NOTIFICATIONS = 5;
|
||||
|
||||
/**
|
||||
* @var \Drupal\amcpr_user_profile\UserProfileServiceInterface
|
||||
*/
|
||||
protected $amcprUserProfileService;
|
||||
|
||||
/**
|
||||
* @var \Drupal\amcpr_notification\Service\NotificationService
|
||||
*/
|
||||
protected $notificationService;
|
||||
|
||||
/**
|
||||
* @var \Drupal\User\UserInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* UserBlock constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* @param $plugin_id
|
||||
* @param $plugin_definition
|
||||
* @param \Drupal\amcpr_user_profile\UserProfileServiceInterface $user_profile_service
|
||||
* @param \Drupal\amcpr_notification\Service\NotificationService $notification_service
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, UserProfileServiceInterface $user_profile_service, NotificationService $notification_service) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->amcprUserProfileService = $user_profile_service;
|
||||
$this->notificationService = $notification_service;
|
||||
$this->currentUser = $this->amcprUserProfileService->getCurrentUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('amcpr.user.utility'),
|
||||
$container->get('amcpr.notification')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$is_logged_in = $this->currentUser->isAuthenticated();
|
||||
$links = [];
|
||||
$notifications = [];
|
||||
$dropdown = [];
|
||||
|
||||
$links['search'] = [
|
||||
'url' => '/search', //@todo replace with actual search route.
|
||||
'title' => $this->t('Search'),
|
||||
'icon' => 'magnifying-glass',
|
||||
];
|
||||
|
||||
if ($is_logged_in) {
|
||||
|
||||
$notifications = $this->notificationService
|
||||
->getUnreadNotifications(static::MAX_NOTIFICATIONS);
|
||||
|
||||
$links['notifications'] = [
|
||||
'url' => Url::fromRoute('amcpr_dashboard.notifications'),
|
||||
'title' => $this->t('Notifications'),
|
||||
'icon' => 'bell',
|
||||
'unread' => $this->notificationService->getUnreadNotificationsCount()
|
||||
];
|
||||
|
||||
$links['dashboard'] = [
|
||||
'url' => Url::fromRoute('amcpr_dashboard.index'),
|
||||
'title' => $this->t('Dashboard'),
|
||||
'icon' => 'grid-two-up',
|
||||
];
|
||||
|
||||
$dropdown['title'] = t('Me');
|
||||
$dropdown['name'] = $this->amcprUserProfileService->getFullName();
|
||||
$dropdown['roles'] = $this->amcprUserProfileService->getAmcprRoles();
|
||||
$dropdown['profile_links'] = [];
|
||||
|
||||
if ($this->amcprUserProfileService->isProfileIncomplete()) {
|
||||
$dropdown['profile_links']['personal'] = [
|
||||
'url' => Url::fromRoute('amcpr_user_profile.profile_wizard'),
|
||||
'title' => $this->t('Complete Profile'),
|
||||
'icon' => 'person',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$dropdown['profile_links']['personal'] = [
|
||||
'url' => Url::fromRoute('amcpr_user_profile.profile'),
|
||||
'title' => $this->t('Personal Profile'),
|
||||
'icon' => 'person',
|
||||
];
|
||||
|
||||
if ($this->amcprUserProfileService->isSupplierProfileIncomplete()) {
|
||||
$dropdown['profile_links']['supplier'] = [
|
||||
'url' => Url::fromRoute('amcpr_user_profile.supplier_profile_wizard'),
|
||||
'title' => $this->t('Become a Supplier'),
|
||||
'icon' => 'globe',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$dropdown['profile_links']['supplier'] = [
|
||||
'url' => Url::fromRoute('amcpr_user_profile.profile_supplier'),
|
||||
'title' => $this->t('Supplier Profile'),
|
||||
'icon' => 'globe',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$dropdown['link_groups'] = [[
|
||||
'header' => 'Lists',
|
||||
'links' => [
|
||||
'favorites_list' => [
|
||||
'url' => Url::fromUri('internal:/lists/favorites'),
|
||||
'title' => $this->t('Favorites'),
|
||||
'icon' => 'star',
|
||||
],
|
||||
'offline_list' => [
|
||||
'url' => Url::fromUri('internal:/lists/offline'),
|
||||
'title' => $this->t('Offline Exports'),
|
||||
'icon' => 'data-transfer-download',
|
||||
],
|
||||
]
|
||||
], [
|
||||
'header' => 'Account',
|
||||
'links' => [
|
||||
'preferences' => [
|
||||
'url' => Url::fromRoute('amcpr_user_profile.account_preferences'),
|
||||
'title' => $this->t('Preferences'),
|
||||
'icon' => 'wrench',
|
||||
],
|
||||
]
|
||||
], [
|
||||
'header' => '',
|
||||
'links' =>[
|
||||
'logout' => [
|
||||
'url' => Url::fromRoute('user.logout'),
|
||||
'title' => $this->t('Logout'),
|
||||
'icon' => 'account-logout',
|
||||
],
|
||||
]
|
||||
]];
|
||||
}
|
||||
else {
|
||||
$links['register'] = [
|
||||
'url' => Url::fromRoute('user.register'),
|
||||
'title' => $this->t('Register'),
|
||||
'icon' => 'briefcase',
|
||||
];
|
||||
$links['login'] = [
|
||||
'url' => Url::fromRoute('user.login'),
|
||||
'title' => $this->t('Login'),
|
||||
'icon' => 'account-login',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'#cache' => ['contexts' => ['user']],
|
||||
'#theme' => 'amcpr_nav_user_block',
|
||||
'#is_logged_in' => $is_logged_in,
|
||||
'#links' => $links,
|
||||
'#notifications' => $notifications,
|
||||
'#account_dropdown' => $dropdown,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Routing;
|
||||
|
||||
use Drupal\Core\Routing\RouteSubscriberBase;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Listens to the dynamic route events.
|
||||
*/
|
||||
class ProfileRouteSubscriber extends RouteSubscriberBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterRoutes(RouteCollection $collection) {
|
||||
|
||||
// Change paths of following routes.
|
||||
if ($route = $collection->get('user.register')) {
|
||||
$route->setPath('/register');
|
||||
}
|
||||
|
||||
if ($route = $collection->get('user.pass')) {
|
||||
$route->setPath('/login/reset');
|
||||
}
|
||||
|
||||
if ($route = $collection->get('user.pass.http')) {
|
||||
$route->setPath('/login/reset');
|
||||
}
|
||||
|
||||
if ($route = $collection->get('user.logout.http')) {
|
||||
$route->setPath('/logout}');
|
||||
}
|
||||
|
||||
if ($route = $collection->get('user.login')) {
|
||||
$route->setPath('/login');
|
||||
}
|
||||
|
||||
if ($route = $collection->get('user.login.http')) {
|
||||
$route->setPath('/login');
|
||||
}
|
||||
|
||||
if ($route = $collection->get('user.reset')) {
|
||||
$route->setPath('/login/reset/{uid}/{timestamp}/{hash}');
|
||||
}
|
||||
|
||||
if ($route = $collection->get('user.reset.form')) {
|
||||
$route->setPath('/login/reset/{uid}');
|
||||
}
|
||||
|
||||
// Change the path and controller for the password reset login route.
|
||||
if ($route = $collection->get('user.reset.login')) {
|
||||
$route->setPath('/login/reset/{uid}/{timestamp}/{hash}/login');
|
||||
$route->setDefault('_controller', '\Drupal\amcpr_user_profile\Controller\ProfileUserController::resetPassLogin');
|
||||
}
|
||||
|
||||
// Change the path and controller for the logout route.
|
||||
if ($route = $collection->get('user.logout')) {
|
||||
$route->setPath('/logout/{logout_method}');
|
||||
$route->setDefault('_controller', '\Drupal\amcpr_user_profile\Controller\ProfileUserController::customLogout');
|
||||
$route->setDefault('logout_method', 'user');
|
||||
}
|
||||
|
||||
// Change the controller for the user page route.
|
||||
if ($route = $collection->get('user.page')) {
|
||||
$route->setDefault('_controller', '\Drupal\amcpr_user_profile\Controller\ProfileUserController::userPage');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile\Service;
|
||||
|
||||
use Drupal\amcpr_user_profile\UserProfileServiceInterface;
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
class UserProfileService implements UserProfileServiceInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
private $entityTypeManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
private $currentUser;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $profileStorage;
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* @var \Drupal\profile\Entity\ProfileInterface
|
||||
*/
|
||||
protected $personalProfile;
|
||||
|
||||
/**
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->currentUser = User::load($current_user->id());
|
||||
$this->profileStorage = $this->entityTypeManager->getStorage('profile');
|
||||
$this->userStorage = $this->entityTypeManager->getStorage('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getCurrentUser() {
|
||||
return $this->currentUser;
|
||||
}
|
||||
|
||||
public function getFullName() {
|
||||
if (!isset($this->personalProfile)) {
|
||||
$this->personalProfile = $this->getPersonalProfile();
|
||||
}
|
||||
|
||||
if (!$this->personalProfile) {
|
||||
return t('Name Not Set');
|
||||
}
|
||||
|
||||
return $this->personalProfile->get('field_first_name')->getString()
|
||||
. ' '
|
||||
. $this->personalProfile->get('field_last_name')->getString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAmcprRoles() {
|
||||
$roles = [t('Designer')];
|
||||
if ($this->isSupplier()) {
|
||||
$roles[] = t('Supplier');
|
||||
}
|
||||
|
||||
if ($this->isScreener()) {
|
||||
$roles[] = t('Screener');
|
||||
}
|
||||
|
||||
if ($this->isReviewer()) {
|
||||
$roles[] = t('Reviewer');
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isSupplier() {
|
||||
return !$this->isSupplierProfileIncomplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isScreener() {
|
||||
return $this->currentUser->hasRole('screener');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isReviewer() {
|
||||
return $this->currentUser->hasRole('reviewer');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAllScreeners() {
|
||||
return $this->getUsersWithRole('screener');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAllReviewers() {
|
||||
return $this->getUsersWithRole('reviewer');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAllAmcprAdministrators() {
|
||||
return $this->getUsersWithRole('amcpr_admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isProfileIncomplete() {
|
||||
// A flag to indicate incompleteness is set at the time of registration.
|
||||
// Fetch the flag value.
|
||||
$isIncompleteFlag = $this->currentUser->get('field_profile_incomplete')->getValue();
|
||||
|
||||
// See if the flag value is set to ON (numerical 1).
|
||||
// It means the user has not completed their profile.
|
||||
if (!empty($isIncompleteFlag) && $isIncompleteFlag[0]['value']) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isSupplierProfileIncomplete() {
|
||||
/** @var \Drupal\profile\Entity\Profile $supplierProfile */
|
||||
$supplierProfile = $this->getSupplierProfile();
|
||||
if ($supplierProfile) {
|
||||
// A flag to indicate incompleteness is set at the time of registration.
|
||||
// Fetch the flag value.
|
||||
$isIncompleteFlag = $supplierProfile->get('field_profile_incomplete')->getValue();
|
||||
// See if the flag value is set to ON (numerical 1).
|
||||
// It means the user has not completed their profile.
|
||||
if (!empty($isIncompleteFlag) && $isIncompleteFlag[0]['value']) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function removeProfileIncompleteFlag() {
|
||||
$this->currentUser->set('field_profile_incomplete', 0);
|
||||
try {
|
||||
$this->currentUser->save();
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
// @todo Log the exception to watchdog.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function removeSupplierProfileIncompleteFlag() {
|
||||
/** @var \Drupal\profile\Entity\Profile $supplierProfile */
|
||||
$supplierProfile = $this->getSupplierProfile();
|
||||
|
||||
$supplierProfile->set('field_profile_incomplete', 0);
|
||||
try {
|
||||
$supplierProfile->save();
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
// @todo Log the exception to watchdog.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPersonalProfile($create_new = FALSE) {
|
||||
return $this->getProfileEntity('personal', $create_new);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPersonalProfileByUid($uid)
|
||||
{
|
||||
$entityProperties = [
|
||||
'type' => 'personal',
|
||||
'uid' => $uid,
|
||||
];
|
||||
$profile = $this->profileStorage->loadByProperties($entityProperties);
|
||||
if (!empty($profile)) {
|
||||
return reset($profile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSupplierProfile($create_new = FALSE) {
|
||||
return $this->getProfileEntity('supplier', $create_new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get profile types used for identifying user role.
|
||||
*
|
||||
* @param array $excluded
|
||||
* Array of Profile bundle IDs to exclude.
|
||||
*
|
||||
* @return array
|
||||
* An array of profile type information.
|
||||
*/
|
||||
public function getProfileTypes($excluded = ['personal']) {
|
||||
//$profileBundleInfo = \Drupal::service('entity_type.bundle.info')->getBundleInfo('profile');
|
||||
|
||||
/** @var \Drupal\profile\Entity\ProfileType[] $profileBundles */
|
||||
try {
|
||||
$profileTypeIds = $this->entityTypeManager
|
||||
->getStorage('profile_type')
|
||||
->getQuery()
|
||||
->condition('id', $excluded, 'NOT IN')
|
||||
->execute();
|
||||
|
||||
$profileBundles = $this->entityTypeManager->getStorage('profile_type')
|
||||
->loadMultiple($profileTypeIds);
|
||||
}
|
||||
catch (PluginException $e) {
|
||||
$profileBundles = [];
|
||||
}
|
||||
|
||||
// Assemble a new associative array with a few properties.
|
||||
$profileTypes = [];
|
||||
foreach ($profileBundles as $profileBundle) {
|
||||
if (method_exists($profileBundle, 'getDescription')) {
|
||||
$profileTypes[] = [
|
||||
'id' => $profileBundle->id(),
|
||||
'label' => $profileBundle->label(),
|
||||
'description' => $profileBundle->getDescription(),
|
||||
];
|
||||
}
|
||||
else if (property_exists($profileBundle, 'description')) {
|
||||
$profileTypes[] = [
|
||||
'id' => $profileBundle->id(),
|
||||
'label' => $profileBundle->label(),
|
||||
'description' => $profileBundle->description,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $profileTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getProfileViewBuilder() {
|
||||
return $this->entityTypeManager->getViewBuilder('profile');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function killUserPrimaryNavBlockCache($uid = NULL) {
|
||||
if (!$uid) {
|
||||
$uid = $this->getCurrentUser()->id();
|
||||
}
|
||||
|
||||
// Kill the receivers User Block cache -- dirty method @todo use better method.
|
||||
$cid = 'entity_view:block:userprimarynavblock:[languages:language_interface]=en:[theme]=b4amcpr:[user]=' . $uid;
|
||||
\Drupal::cache('render')->delete($cid);
|
||||
}
|
||||
|
||||
public function getSupplierEmails($originalEmail){
|
||||
$toReturn = [];
|
||||
$currUserServer = strtolower(substr($originalEmail, strpos($originalEmail, "@") + 1));
|
||||
$configName = (\Drupal\amcpr_user_profile\Form\SupplierSettingsForm::CONFIG_NAME);
|
||||
$config = \Drupal::config($configName);
|
||||
$serverListString = $config->get('server_list');
|
||||
$serverListArray = preg_split('/(,|\r\n|\n|\r| )/', $serverListString);
|
||||
$isInServerList = FALSE;
|
||||
foreach($serverListArray as $indivServer){
|
||||
if (strtolower($indivServer) == $currUserServer){
|
||||
$isInServerList = TRUE;
|
||||
}
|
||||
}
|
||||
if (!$isInServerList){
|
||||
$query = \Drupal::entityQuery('user');
|
||||
$uids = $query->execute();
|
||||
$users = User::loadMultiple($uids);
|
||||
$foundProfile = FALSE;
|
||||
foreach($users as $indivUser){
|
||||
$currEmail = $indivUser->getEmail();
|
||||
$server = strtolower(substr($currEmail, strpos($currEmail, "@") + 1));
|
||||
if ($server == $currUserServer){
|
||||
$toReturn[] = $currEmail;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$toReturn[] = $originalEmail;
|
||||
}
|
||||
return $toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the profile entity of a given type that belongs to the current user.
|
||||
*
|
||||
* @param string $bundle
|
||||
* A profile entity type (bundle).
|
||||
*
|
||||
* @param bool $create_new
|
||||
* If set to TRUE, creates a new profile entity when one
|
||||
* isn't found.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* A profile entity if found; otherwise NULL.
|
||||
*/
|
||||
public function getProfileEntity($bundle, $create_new = FALSE, $uid_sent = NULL) {
|
||||
$uid_to_use = $this->currentUser->id();
|
||||
if (!empty($uid_sent)){
|
||||
$uid_to_use = $uid_sent;
|
||||
}
|
||||
$user_to_use = \Drupal\user\Entity\User::load($uid_to_use);
|
||||
if ($bundle == 'supplier'){
|
||||
$currUserEmail = $user_to_use->getEmail();
|
||||
$currUserServer = strtolower(substr($currUserEmail, strpos($currUserEmail, "@") + 1));
|
||||
|
||||
$configName = (\Drupal\amcpr_user_profile\Form\SupplierSettingsForm::CONFIG_NAME);
|
||||
$config = \Drupal::config($configName);
|
||||
$serverListString = $config->get('server_list');
|
||||
$serverListArray = preg_split('/(,|\r\n|\n|\r| )/', $serverListString);
|
||||
$isInServerList = FALSE;
|
||||
foreach($serverListArray as $indivServer){
|
||||
if (strtolower($indivServer) == $currUserServer){
|
||||
$isInServerList = TRUE;
|
||||
}
|
||||
}
|
||||
if (!$isInServerList){
|
||||
$query = \Drupal::entityQuery('user');
|
||||
$uids = $query->execute();
|
||||
$users = User::loadMultiple($uids);
|
||||
$foundProfile = FALSE;
|
||||
foreach($users as $indivUser){
|
||||
if (!$foundProfile){
|
||||
$currEmail = $indivUser->getEmail();
|
||||
$server = strtolower(substr($currEmail, strpos($currEmail, "@") + 1));
|
||||
if ($server == $currUserServer){
|
||||
$entityProperties = [
|
||||
'type' => $bundle,
|
||||
'uid' => $indivUser->id(),
|
||||
];
|
||||
$profile = $this->profileStorage->loadByProperties($entityProperties);
|
||||
if (!empty($profile)) {
|
||||
$profile = reset($profile);
|
||||
$isIncompleteFlag = $profile->get('field_profile_incomplete')->getValue();
|
||||
if (!empty($isIncompleteFlag) && !$isIncompleteFlag[0]['value']) {
|
||||
$uid_to_use = $indivUser->id();
|
||||
$foundProfile = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$entityProperties = [
|
||||
'type' => $bundle,
|
||||
'uid' => $uid_to_use,
|
||||
];
|
||||
|
||||
$profile = $this->profileStorage->loadByProperties($entityProperties);
|
||||
if (!empty($profile)) {
|
||||
return reset($profile);
|
||||
}
|
||||
|
||||
if ($create_new) {
|
||||
try {
|
||||
$profile = $this->profileStorage->create($entityProperties);
|
||||
$profile->save();
|
||||
return $profile;
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
// no code
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users with the given role id.
|
||||
*
|
||||
* @param string $rid
|
||||
* The role id.
|
||||
*
|
||||
* @return \Drupal\User\UserInterface[]
|
||||
*/
|
||||
private function getUsersWithRole($rid) {
|
||||
/** @var \Drupal\User\UserInterface[] $users */
|
||||
$users = $this->userStorage->loadByProperties([
|
||||
'status' => 1,
|
||||
'roles' => $rid
|
||||
]);
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\amcpr_user_profile;
|
||||
|
||||
|
||||
interface UserProfileServiceInterface {
|
||||
|
||||
/**
|
||||
* Gets the current active user.
|
||||
*
|
||||
* @return \Drupal\User\UserInterface
|
||||
*/
|
||||
public function getCurrentUser();
|
||||
|
||||
/**
|
||||
* Gets full name of the current user.
|
||||
*
|
||||
* @return string
|
||||
* The full name of the user.
|
||||
*/
|
||||
public function getFullName();
|
||||
|
||||
/**
|
||||
* Gets AMCPR roles for the current user.
|
||||
*
|
||||
* @return array
|
||||
* An array containing AMCPR roles.
|
||||
*/
|
||||
public function getAmcprRoles();
|
||||
|
||||
/**
|
||||
* Whether the current user is a supplier.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the user is a supplier, otherwise FALSE.
|
||||
*/
|
||||
public function isSupplier();
|
||||
|
||||
/**
|
||||
* Whether the current user is a screener.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the user is a screener, otherwise FALSE.
|
||||
*/
|
||||
public function isScreener();
|
||||
|
||||
/**
|
||||
* Whether the current user is a reviewer.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the user is a reviewer, otherwise FALSE.
|
||||
*/
|
||||
public function isReviewer();
|
||||
|
||||
/**
|
||||
* Get all users with the Screener role.
|
||||
*
|
||||
* @return \Drupal\User\UserInterface[]
|
||||
*/
|
||||
public function getAllScreeners();
|
||||
|
||||
/**
|
||||
* Get all users with Reviewer role.
|
||||
*
|
||||
* @return \Drupal\User\UserInterface[]
|
||||
*/
|
||||
public function getAllReviewers();
|
||||
|
||||
/**
|
||||
* Get all users with administrator role.
|
||||
*
|
||||
* @return \Drupal\User\UserInterface[]
|
||||
*/
|
||||
public function getAllAmcprAdministrators();
|
||||
|
||||
/**
|
||||
* Whether the current user has an incomplete Personal profile.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the use did not complete their profile; FALSE otherwise.
|
||||
*/
|
||||
public function isProfileIncomplete();
|
||||
|
||||
/**
|
||||
* Whether the current user has an incomplete Supplier profile.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the use did not complete their profile; FALSE otherwise.
|
||||
*/
|
||||
public function isSupplierProfileIncomplete();
|
||||
|
||||
/**
|
||||
* Remove the Incomplete Profile flag.
|
||||
*/
|
||||
public function removeProfileIncompleteFlag();
|
||||
|
||||
/**
|
||||
* Remove the Incomplete Supplier Profile flag.
|
||||
*/
|
||||
public function removeSupplierProfileIncompleteFlag();
|
||||
|
||||
/**
|
||||
* Get Personal profile entity associated with the current user.
|
||||
*
|
||||
* @param bool $create_new
|
||||
* If set to TRUE, creates a new Personal profile entity when one
|
||||
* isn't found.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* Supplier profile entity.
|
||||
*/
|
||||
public function getPersonalProfile($create_new = FALSE);
|
||||
|
||||
/**
|
||||
* Get Personal profile entity associated with the given user uid.
|
||||
*
|
||||
* @param int $uid
|
||||
* The UID of the user.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* Supplier profile entity.
|
||||
*/
|
||||
public function getPersonalProfileByUid($uid);
|
||||
|
||||
/**
|
||||
* Get Supplier profile entity associated with the current user.
|
||||
*
|
||||
* @param bool $create_new
|
||||
* If set to TRUE, creates a new Supplier profile entity when one
|
||||
* isn't found.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* Supplier profile entity.
|
||||
*/
|
||||
public function getSupplierProfile($create_new = FALSE);
|
||||
|
||||
/**
|
||||
* Get profile types used for identifying user role.
|
||||
*
|
||||
* @param array $excluded
|
||||
* Array of Profile bundle IDs to exclude.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing profile type information.
|
||||
*/
|
||||
public function getProfileTypes($excluded = ['personal']);
|
||||
|
||||
/**
|
||||
* Creates a new view builder instance for the Profile entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityViewBuilderInterface
|
||||
* A view builder instance.
|
||||
*/
|
||||
public function getProfileViewBuilder();
|
||||
|
||||
/**
|
||||
* Kill the Primary Nav Block cache of a user.
|
||||
* @param int|null $uid
|
||||
* If set, block cache for the given user id will be cleared.
|
||||
* Defaults to current user id.
|
||||
*/
|
||||
public function killUserPrimaryNavBlockCache($uid = NULL);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
name: Dafter Stock API
|
||||
description: Provides services to fetch stock related data from various third-party APIs.
|
||||
core: 8.x
|
||||
type: module
|
||||
package: Daftar API
|
||||
core_version_requirement: ^8 || ^9
|
||||
version: 1.0.0
|
||||
tags:
|
||||
- api
|
||||
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
daftar.stock_api.alphavantage:
|
||||
class: Drupal\daftar_stock_api\Service\AlphaVantageApiService
|
||||
arguments:
|
||||
- '@logger.factory'
|
||||
|
||||
daftar.stock_api.yahoo_parser:
|
||||
class: Drupal\daftar_stock_api\Service\YahooParserService
|
||||
arguments:
|
||||
- '@logger.factory'
|
||||
|
||||
daftar.stock_api.yahoo_api:
|
||||
class: Drupal\daftar_stock_api\Service\YahooApiService
|
||||
arguments:
|
||||
- '@logger.factory'
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_stock_api\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class StockApiException extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_stock_api\Service;
|
||||
|
||||
|
||||
use Drupal\Core\Logger\LoggerChannelFactory;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\daftar_stock_api\Exception\StockApiException;
|
||||
use Drupal\daftar_stock_api\StockApiServiceInterface;
|
||||
|
||||
class AlphaVantageApiService implements StockApiServiceInterface {
|
||||
|
||||
const API_URL = 'https://www.alphavantage.co';
|
||||
const API_PATH = 'query';
|
||||
const SETTINGS_KEY_STOCK_API_KEY = 'daftar_stock_api_key';
|
||||
|
||||
/**
|
||||
* Logger Factory.
|
||||
*
|
||||
* @var \Drupal\Core\Logger\LoggerChannelFactory
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param LoggerChannelFactory $loggerFactory
|
||||
*/
|
||||
public function __construct(LoggerChannelFactory $loggerFactory) {
|
||||
$this->logger = $loggerFactory->get('daftar_stock_api');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function doSymbolLookup($symbol) {
|
||||
$jsonData = $this->doGet($symbol, 'OVERVIEW');
|
||||
|
||||
if (!isset($jsonData['Symbol']) || $jsonData['Symbol'] !== $symbol) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $jsonData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $symbol
|
||||
* @param $function
|
||||
* @param array $params
|
||||
* @return null
|
||||
* @throws StockApiException
|
||||
*/
|
||||
private function doGet($symbol, $function, array $params = []) {
|
||||
if (!$symbol) {
|
||||
throw new StockApiException('Missing symbol');
|
||||
}
|
||||
|
||||
if (!$function) {
|
||||
$this->logger->error('Missing Stock API function.');
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$apiKey = Settings::get(static::SETTINGS_KEY_STOCK_API_KEY, '');
|
||||
if (!$apiKey) {
|
||||
$this->logger->error('Missing Stock API Key.');
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$client = new \GuzzleHttp\Client([
|
||||
'base_uri' => static::API_URL,
|
||||
'verify' => FALSE, // ignore invalid ssl
|
||||
]);
|
||||
|
||||
$options = [
|
||||
'query' => [
|
||||
'apikey' => $apiKey,
|
||||
'function' => strtoupper($function),
|
||||
'symbol' => strtoupper($symbol),
|
||||
],
|
||||
];
|
||||
|
||||
$response = $client->get(static::API_PATH, $options);
|
||||
|
||||
if ($response->getStatusCode() != '200') {
|
||||
$this->logger->error('Invalid response received from the Stock API.');
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return json_decode($response->getBody(), TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_stock_api\Service;
|
||||
|
||||
use DateTime;
|
||||
use DOMDocument;
|
||||
use Drupal\Core\Logger\LoggerChannelFactory;
|
||||
use Drupal\daftar_stock_api\Exception\StockApiException;
|
||||
use Drupal\daftar_stock_api\StockApiServiceInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class YahooApiService implements StockApiServiceInterface {
|
||||
|
||||
const API_URL = 'https://query2.finance.yahoo.com';
|
||||
const API_PATH = '/v7/finance';
|
||||
const API_ENDPOINT_OPTIONS = '/options';
|
||||
|
||||
/**
|
||||
* Logger Factory.
|
||||
*
|
||||
* @var \Drupal\Core\Logger\LoggerChannelFactory
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param LoggerChannelFactory $loggerFactory
|
||||
*/
|
||||
public function __construct(LoggerChannelFactory $loggerFactory) {
|
||||
$this->logger = $loggerFactory->get('dsa_yahoo_api');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $symbol
|
||||
* @param string $date
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function doSymbolLookup($symbol) {
|
||||
throw new StockApiException("doSymbolLookup has not been implemented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $symbol
|
||||
* @param string $date
|
||||
* @param string $type
|
||||
*
|
||||
* @return array
|
||||
* @throws \Drupal\daftar_stock_api\Exception\StockApiException
|
||||
*/
|
||||
public function getOptionContract(string $symbol, string $date, string $type, $strike): array {
|
||||
$data = $this->getOptionContracts($symbol, $date, $type);
|
||||
|
||||
if ($data['error']) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$error = FALSE;
|
||||
$message = '';
|
||||
|
||||
$needle = NULL;
|
||||
foreach ($data['contents'] as $contract) {
|
||||
if ($contract['strike'] == $strike) {
|
||||
$needle = $contract;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$needle) {
|
||||
$error = TRUE;
|
||||
$message = 'Contract with given strike price is not included.';
|
||||
}
|
||||
|
||||
return [
|
||||
'error' => $error,
|
||||
'message' => $message,
|
||||
'contents' => ($error) ? NULL : $needle,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $symbol
|
||||
* @param string $date
|
||||
* @param string $type
|
||||
*
|
||||
* @return array
|
||||
* @throws \Drupal\daftar_stock_api\Exception\StockApiException
|
||||
*/
|
||||
public function getOptionContracts(string $symbol, string $date, string $type): array {
|
||||
$data = $this->getOptionChain($symbol, $date);
|
||||
|
||||
if ($data['error']) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$error = FALSE;
|
||||
$message = '';
|
||||
|
||||
if (empty($data['contents'][$type])) {
|
||||
$error = TRUE;
|
||||
$message = "Option for given type ${$type} are not included.";
|
||||
}
|
||||
|
||||
return [
|
||||
'error' => $error,
|
||||
'message' => $message,
|
||||
'contents' => ($error) ? NULL : $data['contents'][$type],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $symbol
|
||||
* @param string $date
|
||||
*
|
||||
* @return array
|
||||
* @throws \Drupal\daftar_stock_api\Exception\StockApiException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getOptionChain(string $symbol, string $date): array {
|
||||
$function = sprintf('%s/%s', self::API_ENDPOINT_OPTIONS, $symbol);
|
||||
$dateTime = new DateTime($date);
|
||||
$timestamp = $dateTime->format('U');
|
||||
|
||||
$data = $this->doGet($function, [
|
||||
'date' => $timestamp,
|
||||
]);
|
||||
|
||||
$error = FALSE;
|
||||
$message = '';
|
||||
|
||||
if ($data['response_code'] !== Response::HTTP_OK) {
|
||||
$error = TRUE;
|
||||
$message = 'Response not received.';
|
||||
}
|
||||
elseif ($data['contents']['optionChain']['error']) {
|
||||
$error = TRUE;
|
||||
$message = 'Error in response received.';
|
||||
}
|
||||
elseif (empty($data['contents']['optionChain']['result'][0]['expirationDates'])
|
||||
|| empty($data['contents']['optionChain']['result'][0]['strikes'])) {
|
||||
$error = TRUE;
|
||||
$message = 'Option chain not included.';
|
||||
}
|
||||
elseif (!$this->findValue($timestamp, $data['contents']['optionChain']['result'][0]['expirationDates'])) {
|
||||
$error = TRUE;
|
||||
$message = 'Option chain for given date is not included.';
|
||||
}
|
||||
|
||||
return [
|
||||
'error' => $error,
|
||||
'message' => $message,
|
||||
'contents' => ($error) ? NULL : $data['contents']['optionChain']['result'][0]['options'][0],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $needle
|
||||
* @param $haystack
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function findValue($needle, $haystack): bool {
|
||||
$haystackIndex = array_flip($haystack);
|
||||
return isset($haystackIndex[$needle]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function
|
||||
* @param array $params
|
||||
*
|
||||
* @return array An associative array containing status code and contents
|
||||
* from the response.
|
||||
* @throws \Drupal\daftar_stock_api\Exception\StockApiException
|
||||
*/
|
||||
private function doGet(string $function, array $params = []): array {
|
||||
if (!$function) {
|
||||
throw new StockApiException('Missing required function.');
|
||||
}
|
||||
|
||||
if (substr($function, 0, 1) !== '/') {
|
||||
throw new StockApiException("Function must begin with '/'.");
|
||||
}
|
||||
|
||||
$client = new \GuzzleHttp\Client([
|
||||
'base_uri' => static::API_URL,
|
||||
'verify' => FALSE, // ignore invalid ssl
|
||||
]);
|
||||
|
||||
$options = [
|
||||
'allow_redirects' => FALSE,
|
||||
'query' => $params,
|
||||
];
|
||||
|
||||
$response = $client->get(self::API_PATH . $function, $options);
|
||||
if ($response->getStatusCode() != Response::HTTP_OK) {
|
||||
$this->logger->error('Invalid response received from Yahoo Finance API. Received status code: ' . $response->getStatusCode());
|
||||
}
|
||||
|
||||
return [
|
||||
'response_code' => $response->getStatusCode(),
|
||||
'contents' => json_decode($response->getBody()->getContents(), TRUE),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_stock_api\Service;
|
||||
|
||||
use DOMDocument;
|
||||
use Drupal\Core\Logger\LoggerChannelFactory;
|
||||
use Drupal\daftar_stock_api\Exception\StockApiException;
|
||||
use Drupal\daftar_stock_api\StockApiServiceInterface;
|
||||
|
||||
class YahooParserService implements StockApiServiceInterface {
|
||||
|
||||
const API_URL = 'https://finance.yahoo.com';
|
||||
const API_QUOTE_FUNCTION = 'quote';
|
||||
|
||||
/**
|
||||
* Logger Factory.
|
||||
*
|
||||
* @var \Drupal\Core\Logger\LoggerChannelFactory
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param LoggerChannelFactory $loggerFactory
|
||||
*/
|
||||
public function __construct(LoggerChannelFactory $loggerFactory) {
|
||||
$this->logger = $loggerFactory->get('daftar_stock_api_yahoo');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function doSymbolLookup($symbol) {
|
||||
throw new StockApiException("doSymbolLookup has not been implemented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $symbol
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPrice($symbol): ?string {
|
||||
$htmlData = $this->doGet($symbol, static::API_QUOTE_FUNCTION . '/' . $symbol);
|
||||
if (!$htmlData) {
|
||||
return NUll;
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadHTML($htmlData);
|
||||
|
||||
$quoteHeaderInfo = $doc->getElementById('quote-header-info');
|
||||
if (!$quoteHeaderInfo) {
|
||||
$this->logger->error('Unable to parse HTML response. Price not found. Symbol: ' . $symbol);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$finStreamers = $quoteHeaderInfo->getElementsByTagName('fin-streamer');
|
||||
if ($finStreamers->count() <= 0) {
|
||||
$this->logger->error('Missing element in HTML response.');
|
||||
}
|
||||
|
||||
/** @var \DOMElement $elem */
|
||||
foreach ($finStreamers as $elem) {
|
||||
$dataField = $elem->getAttribute('data-field');
|
||||
if ($dataField !== 'regularMarketPrice') {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $elem->getAttribute('value');
|
||||
}
|
||||
|
||||
$this->logger->error('Missing element with price in HTML response.');
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $symbol
|
||||
* @param $function
|
||||
* @param array $params
|
||||
* @return string|null
|
||||
*/
|
||||
private function doGet($symbol, $function, array $params = []) {
|
||||
if (!$function) {
|
||||
$this->logger->error('Missing required function.');
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$client = new \GuzzleHttp\Client([
|
||||
'base_uri' => static::API_URL,
|
||||
'verify' => FALSE, // ignore invalid ssl
|
||||
]);
|
||||
|
||||
$options = [
|
||||
'allow_redirects' => FALSE,
|
||||
'query' => [
|
||||
'p' => strtoupper($symbol),
|
||||
],
|
||||
];
|
||||
|
||||
$response = $client->get($function, $options);
|
||||
if ($response->getStatusCode() != '200') {
|
||||
$this->logger->error('Invalid response received from Yahoo. Received status code: ' . $response->getStatusCode());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_stock_api;
|
||||
|
||||
|
||||
use Drupal\daftar_stock_api\Exception\StockApiException;
|
||||
|
||||
interface StockApiServiceInterface {
|
||||
|
||||
/**
|
||||
* @param $symbol
|
||||
* @return null
|
||||
* @throws StockApiException
|
||||
*/
|
||||
public function doSymbolLookup($symbol);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
name: Daftar Core
|
||||
description: 'Defines core entities like account, portfolio and transaction (order, dividend and transfers).'
|
||||
type: module
|
||||
package: Daftar Content
|
||||
core_version_requirement: ^8.8 || ^9
|
||||
dependencies:
|
||||
- drupal:user
|
||||
- drupal:views
|
||||
- entity:entity
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core;
|
||||
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityKhatuInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityReferencedInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a chopda (portfolio) entity.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
interface ChopdaInterface extends
|
||||
ContentEntityInterface,
|
||||
EntityKhatuInterface,
|
||||
EntityCreatedInterface,
|
||||
EntityChangedInterface {
|
||||
|
||||
// no code
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityChangedTrait;
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedTrait;
|
||||
|
||||
/**
|
||||
* Implements base class for all daftar entities.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
class DaftarBase extends ContentEntityBase implements
|
||||
ContentEntityInterface,
|
||||
EntityCreatedInterface,
|
||||
EntityChangedInterface {
|
||||
|
||||
use EntityCreatedTrait;
|
||||
use EntityChangedTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
if ($entity_type->hasKey('label')) {
|
||||
$fields[$entity_type->getKey('label')] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Label'))
|
||||
->setSetting('max_length', 100)
|
||||
->setRequired(TRUE);
|
||||
}
|
||||
|
||||
if ($entity_type->hasKey('created')) {
|
||||
$fields[$entity_type->getKey('created')] = BaseFieldDefinition::create('created')
|
||||
->setLabel(t('Created'))
|
||||
->setDescription(t('The time that the entity was created.'))
|
||||
->setRequired(TRUE);
|
||||
}
|
||||
|
||||
if ($entity_type->hasKey('changed')) {
|
||||
$fields[$entity_type->getKey('changed')] = BaseFieldDefinition::create('changed')
|
||||
->setLabel(t('Changed'))
|
||||
->setDescription(t('The time that the entity was last edited.'))
|
||||
->setRequired(TRUE);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core;
|
||||
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityKhatuInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a dividend entity.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
interface DividendInterface extends
|
||||
ContentEntityInterface,
|
||||
EntityKhatuInterface,
|
||||
EntityCreatedInterface,
|
||||
EntityChangedInterface {
|
||||
|
||||
public const DEFAULT_TYPE = 'cash';
|
||||
public const DEFAULT_TAX_RATE = 'qualified';
|
||||
|
||||
public const TYPES = [
|
||||
'cash' => 'Cash',
|
||||
];
|
||||
|
||||
public const TAX_RATES = [
|
||||
'qualified' => 'Qualified',
|
||||
'ordinary' => 'Ordinary',
|
||||
'other' => 'Other',
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\daftar_core\ChopdaInterface;
|
||||
use Drupal\daftar_core\DaftarBase;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityKhatuTrait;
|
||||
|
||||
/**
|
||||
* Defines the Chopda (Portfolio) entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "daftar_chopda",
|
||||
* label = @Translation("Portfolio"),
|
||||
* label_singular = @Translation("portfolio"),
|
||||
* label_plural = @Translation("portfolios"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count portfolio",
|
||||
* plural = "@count portfolios",
|
||||
* ),
|
||||
* base_table = "daftar_chopdao",
|
||||
* entity_keys = {
|
||||
* "id" = "cid",
|
||||
* "uuid" = "uuid",
|
||||
* "khatu" = "kid",
|
||||
* "label" = "label",
|
||||
* "created" = "created",
|
||||
* "changed" = "changed",
|
||||
* },
|
||||
* handlers = {
|
||||
* "views_data" = "Drupal\views\EntityViewsData"
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
class Chopda extends DaftarBase implements ChopdaInterface {
|
||||
|
||||
use EntityKhatuTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
$fields += static::khatuBaseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['cid']->setLabel(t('Portfolio ID'));
|
||||
$fields['uuid']->setLabel(t('Portfolio UUID'));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toArray() {
|
||||
return[
|
||||
'cid' => $this->id(),
|
||||
'uuid' => $this->uuid(),
|
||||
'kid' => $this->getKhatuId(),
|
||||
'label' => $this->label(),
|
||||
'created' => $this->getCreatedTime(),
|
||||
'changed' => $this->getChangedTime(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Helper;
|
||||
|
||||
/**
|
||||
* Defines an interface for entity create timestamp tracking.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
interface EntityCreatedInterface {
|
||||
|
||||
/**
|
||||
* Returns the time that the entity was created.
|
||||
*
|
||||
* @return int
|
||||
* The timestamp of when the entity was created.
|
||||
*/
|
||||
public function getCreatedTime();
|
||||
|
||||
/**
|
||||
* Sets the creation date of the entity.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* The timestamp of when the entity was created.
|
||||
*
|
||||
* @return $this
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setCreatedTime($timestamp);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Helper;
|
||||
|
||||
/**
|
||||
* Provides a trait for accessing created time.
|
||||
*
|
||||
* @see \Drupal\daftar_core\Entity\Helper\EntityCreatedInterface
|
||||
* @ingroup daftar
|
||||
*/
|
||||
trait EntityCreatedTrait {
|
||||
|
||||
/**
|
||||
* Returns the time that the entity was created.
|
||||
*
|
||||
* @return int
|
||||
* The timestamp of when the entity was created.
|
||||
*/
|
||||
public function getCreatedTime() {
|
||||
return $this->get('created')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the creation date of the entity.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* The timestamp of when the entity was created.
|
||||
*
|
||||
* @return $this
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setCreatedTime($timestamp) {
|
||||
$this->set('created', $timestamp);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Helper;
|
||||
|
||||
use Drupal\daftar_core\KhatuInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for entities that belong to a khatu (account).
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
interface EntityKhatuInterface {
|
||||
|
||||
public const KEY = 'khatu';
|
||||
public const LABEL = 'Account ID';
|
||||
public const TARGET_ENTITY_TYPE = 'daftar_khatu';
|
||||
|
||||
/**
|
||||
* Returns the entity's khatu entity.
|
||||
*
|
||||
* @return \Drupal\daftar_core\KhatuInterface
|
||||
* The khatu entity.
|
||||
*/
|
||||
public function getKhatu();
|
||||
|
||||
/**
|
||||
* Sets the entity's khatu entity.
|
||||
*
|
||||
* @param \Drupal\daftar_core\KhatuInterface $khatu
|
||||
* The khatu entity.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setKhatu(KhatuInterface $khatu);
|
||||
|
||||
/**
|
||||
* Returns the entity's khatu ID.
|
||||
*
|
||||
* @return int|null
|
||||
* The khatu ID, or NULL in case the khatu ID field has not been set on
|
||||
* the entity.
|
||||
*/
|
||||
public function getKhatuId();
|
||||
|
||||
/**
|
||||
* Sets the entity's khatu entity ID.
|
||||
*
|
||||
* @param int $khatu_id
|
||||
* The khatu entity ID.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setKhatuId($khatu_id);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Helper;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\daftar_core\KhatuInterface;
|
||||
|
||||
/**
|
||||
* Provides a trait for entities that belong to a khatu (account).
|
||||
*
|
||||
* @see \Drupal\daftar_core\Entity\Helper\EntityKhatuInterface
|
||||
* @ingroup daftar
|
||||
*/
|
||||
trait EntityKhatuTrait {
|
||||
|
||||
/**
|
||||
* Returns an array of base field definitions for entity khatas (accounts).
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type to add the reference field to.
|
||||
*
|
||||
* @return \Drupal\Core\Field\BaseFieldDefinition[]
|
||||
* An array of base field definitions.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
* Thrown when the entity type does not implement EntityKhatuInterface
|
||||
* or if it does not have a "khatu" entity key.
|
||||
*/
|
||||
public static function khatuBaseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
if (!is_subclass_of($entity_type->getClass(), EntityKhatuInterface::class)) {
|
||||
throw new UnsupportedEntityTypeDefinitionException(
|
||||
sprintf('The entity type %s does not implement \Drupal\daftar\Entity\Helper\EntityReferencedInterface.', $entity_type->id())
|
||||
);
|
||||
}
|
||||
if (!$entity_type->hasKey(EntityKhatuInterface::KEY)) {
|
||||
throw new UnsupportedEntityTypeDefinitionException(
|
||||
sprintf('The entity type %s does not have a "%s" entity key.', $entity_type->id(), EntityKhatuInterface::KEY)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
$entity_type->getKey(EntityKhatuInterface::KEY) => BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t(EntityKhatuInterface::LABEL))
|
||||
->setDescription(t('The {daftar_khatao}.kid of the account.'))
|
||||
->setSetting('target_type', EntityKhatuInterface::TARGET_ENTITY_TYPE)
|
||||
->setRequired(TRUE),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKhatu() {
|
||||
$key = $this->getEntityType()->getKey(EntityKhatuInterface::KEY);
|
||||
return $this->get($key)->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setKhatu(KhatuInterface $entity) {
|
||||
$key = $this->getEntityType()->getKey(EntityKhatuInterface::KEY);
|
||||
$this->set($key, $entity);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKhatuId() {
|
||||
return $this->getEntityKey(EntityKhatuInterface::KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setKhatuId($khatu_id) {
|
||||
$key = $this->getEntityType()->getKey(EntityKhatuInterface::KEY);
|
||||
$this->set($key, $khatu_id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Helper;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for entity create timestamp tracking.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
interface EntityReferencedInterface {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the entity being referenced.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The referenced entity or NULL if nothing is referenced.
|
||||
*/
|
||||
public function getReference();
|
||||
|
||||
/**
|
||||
* Sets the entity being referenced.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The referenced entity.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setReference(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Returns the referenced entity's ID.
|
||||
*
|
||||
* @return int|null
|
||||
* The referenced entity ID, or NULL in case the reference field has not
|
||||
* been set on the entity.
|
||||
*/
|
||||
public function getReferenceId();
|
||||
|
||||
/**
|
||||
* Sets the referenced entity ID.
|
||||
*
|
||||
* @param int $entity_id
|
||||
* The referenced entity ID.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setReferenceId($entity_id);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Helper;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Provides a trait for accessing referenced entity.
|
||||
*
|
||||
* @see \Drupal\daftar_core\Entity\Helper\EntityReferencedInterface
|
||||
* @ingroup daftar
|
||||
*/
|
||||
trait EntityReferencedTrait {
|
||||
|
||||
/**
|
||||
* Returns an array of base field definitions for entity references.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type to add the reference field to.
|
||||
* @param string $target_type
|
||||
* The entity type ID of the referenced entity.
|
||||
*
|
||||
* @return \Drupal\Core\Field\BaseFieldDefinition[]
|
||||
* An array of base field definitions.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
* Thrown when the entity type does not implement EntityReferencedInterface
|
||||
* or if it does not have a "reference" entity key.
|
||||
*/
|
||||
public static function referenceBaseFieldDefinitions(EntityTypeInterface $entity_type, string $target_type) {
|
||||
if (!is_subclass_of($entity_type->getClass(), EntityReferencedInterface::class)) {
|
||||
throw new UnsupportedEntityTypeDefinitionException('The entity type ' . $entity_type->id() . ' does not implement \Drupal\daftar\Entity\Helper\EntityReferencedInterface.');
|
||||
}
|
||||
if (!$entity_type->hasKey('reference')) {
|
||||
throw new UnsupportedEntityTypeDefinitionException('The entity type ' . $entity_type->id() . ' does not have a "reference" entity key.');
|
||||
}
|
||||
|
||||
return [
|
||||
$entity_type->getKey('reference') => BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(new TranslatableMarkup('Reference ID'))
|
||||
->setSetting('target_type', $target_type)
|
||||
->setRequired(TRUE),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getReference() {
|
||||
$key = $this->getEntityType()->getKey('reference');
|
||||
return $this->get($key)->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setReference(EntityInterface $entity) {
|
||||
$key = $this->getEntityType()->getKey('reference');
|
||||
$this->set($key, $entity);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getReferenceId() {
|
||||
return $this->getEntityKey('reference');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setReferenceId($entity_id) {
|
||||
$key = $this->getEntityType()->getKey('reference');
|
||||
$this->set($key, $entity_id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\daftar_core\DaftarBase;
|
||||
use Drupal\daftar_core\KhatuInterface;
|
||||
use Drupal\user\EntityOwnerTrait;
|
||||
|
||||
/**
|
||||
* Defines the Khatu (Account) entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "daftar_khatu",
|
||||
* label = @Translation("Account"),
|
||||
* label_singular = @Translation("account"),
|
||||
* label_plural = @Translation("accounts"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count account",
|
||||
* plural = "@count accounts",
|
||||
* ),
|
||||
* bundle_label = @Translation("Account Type"),*
|
||||
* base_table = "daftar_khatao",
|
||||
* entity_keys = {
|
||||
* "id" = "kid",
|
||||
* "uuid" = "uuid",
|
||||
* "bundle" = "type",
|
||||
* "owner" = "uid",
|
||||
* "label" = "label",
|
||||
* "created" = "created",
|
||||
* "changed" = "changed",
|
||||
* },
|
||||
* handlers = {
|
||||
* "views_data" = "Drupal\views\EntityViewsData"
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
class Khatu extends DaftarBase implements KhatuInterface {
|
||||
|
||||
use EntityOwnerTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
$fields += static::ownerBaseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['kid']->setLabel(t('Account ID'));
|
||||
$fields['uuid']->setLabel(t('Account UUID'));
|
||||
$fields['type']->setLabel(t('Account Type'));
|
||||
|
||||
$fields['uid']
|
||||
->setDescription(t('The {user}.uid of the account owner.'));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isBank() {
|
||||
return $this->bundle() === 'bank';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isBrokerage() {
|
||||
return $this->bundle() === 'brokerage';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toArray() {
|
||||
return[
|
||||
'kid' => $this->id(),
|
||||
'uuid' => $this->uuid(),
|
||||
'type' => $this->bundle(),
|
||||
'uid' => $this->getOwnerId(),
|
||||
'label' => $this->label(),
|
||||
'created' => $this->getCreatedTime(),
|
||||
'changed' => $this->getChangedTime(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_Core\Entity\Sodo;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\daftar_core\DividendInterface;
|
||||
|
||||
/**
|
||||
* Defines the Dividend Transaction entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "daftar_dividend",
|
||||
* label = @Translation("Dividend"),
|
||||
* label_singular = @Translation("dividend"),
|
||||
* label_plural = @Translation("dividends"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count dividend",
|
||||
* plural = "@count dividends",
|
||||
* ),
|
||||
*
|
||||
* base_table = "daftar_dividends",
|
||||
*
|
||||
* entity_keys = {
|
||||
* "id" = "sid",
|
||||
* "uuid" = "uuid",
|
||||
* "khatu" = "kid",
|
||||
* "bundle" = "type",
|
||||
* "created" = "created",
|
||||
* "changed" = "changed",
|
||||
* },
|
||||
* )
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
class Dividend extends SodoEntityBase implements DividendInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
// Override parent label and description.
|
||||
$fields['date']->setLabel(t('Date Paid'));
|
||||
$fields['date']->setDescription(t('Date when this dividend was paid.'));
|
||||
|
||||
$fields['ex_date'] = BaseFieldDefinition::create('datetime')
|
||||
->setLabel(t('Execution Date'))
|
||||
->setDescription(t('Date when this dividend was executed.'))
|
||||
->setSetting('datetime_type', 'date');
|
||||
|
||||
$fields['symbol'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Symbol'))
|
||||
->setSetting('max_length', 15)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['quantity'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Number of shares'))
|
||||
->setDescription(t('Number of shares on which the dividend was paid out.'))
|
||||
->setSetting('precision', 12)
|
||||
->setSetting('scale', 4)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['tax_rate'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Tax Rate Classification'))
|
||||
->setSetting('max_length', 15)
|
||||
->setDescription(t('Tax rate classification of the dividend (e.g. qualified and ordinary).'));
|
||||
|
||||
$fields['auto_created'] = BaseFieldDefinition::create('boolean')
|
||||
->setLabel(t('Auto Created'))
|
||||
->setDescription(t('A boolean flag indicating whether the dividend was automatically added.'))
|
||||
->setDefaultValue(FALSE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Sodo;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\daftar_core\OrderInterface;
|
||||
|
||||
/**
|
||||
* Defines the Order Transaction entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "daftar_order",
|
||||
* label = @Translation("Order"),
|
||||
* label_singular = @Translation("order"),
|
||||
* label_plural = @Translation("orders"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count order",
|
||||
* plural = "@count orders",
|
||||
* ),
|
||||
*
|
||||
* base_table = "daftar_orders",
|
||||
*
|
||||
* entity_keys = {
|
||||
* "id" = "sid",
|
||||
* "uuid" = "uuid",
|
||||
* "khatu" = "kid",
|
||||
* "bundle" = "type",
|
||||
* "created" = "created",
|
||||
* "changed" = "changed",
|
||||
* },
|
||||
* )
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
class Order extends SodoEntityBase implements OrderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['action'] = BaseFieldDefinition::create('string')
|
||||
->setLabel($entity_type->getBundleLabel())
|
||||
->setRequired(TRUE)
|
||||
->setReadOnly(TRUE);
|
||||
|
||||
$fields['symbol'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Symbol'))
|
||||
->setSetting('max_length', 15)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['quantity'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Quantity'))
|
||||
->setDescription(t('Number of shares exchanged.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 4)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['on_hand'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Quantity On Hand'))
|
||||
->setDescription(t('Number of shares currently on hand.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 4);
|
||||
|
||||
$fields['cps'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Cost per Share'))
|
||||
->setDescription(t('Price paid per share (computed).'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 4)
|
||||
->setComputed(TRUE);
|
||||
|
||||
$fields['commission'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Commission'))
|
||||
->setDescription(t('Commission charged by the brokerage.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 4)
|
||||
->setDefaultValue(0);
|
||||
|
||||
$fields['fees'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Fees'))
|
||||
->setDescription(t('Fees charged for the transaction.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 4)
|
||||
->setDefaultValue(0);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Sodo;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityChangedTrait;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\daftar_core\DaftarBase;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedTrait;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityKhatuInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityKhatuTrait;
|
||||
|
||||
/**
|
||||
* Implements base class for sodo (transaction) entity types.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
abstract class SodoEntityBase extends DaftarBase implements
|
||||
ContentEntityInterface,
|
||||
EntityKhatuInterface,
|
||||
EntityCreatedInterface,
|
||||
EntityChangedInterface {
|
||||
|
||||
use EntityKhatuTrait;
|
||||
use EntityCreatedTrait;
|
||||
use EntityChangedTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
$fields += static::khatuBaseFieldDefinitions($entity_type);
|
||||
|
||||
// Fields attached to all transactions.
|
||||
|
||||
$fields['date'] = BaseFieldDefinition::create('datetime')
|
||||
->setLabel(t('Transaction Date'))
|
||||
->setDescription(t('Date when this transaction took place.'))
|
||||
->setSetting('datetime_type', 'date')
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['amount'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Amount'))
|
||||
->setDescription(t('Total transaction amount.'))
|
||||
->setSetting('precision', 12)
|
||||
->setSetting('scale', 4)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['notes'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Notes'))
|
||||
->setDescription(t('Short descriptive text for future references.'))
|
||||
->setSetting('max_length', 255);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core\Entity\Sodo;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\daftar_core\TransferInterface;
|
||||
|
||||
/**
|
||||
* Defines the Transfer Transaction entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "daftar_transfer",
|
||||
* label = @Translation("Transfer"),
|
||||
* label_singular = @Translation("transfer"),
|
||||
* label_plural = @Translation("transfers"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count transfer",
|
||||
* plural = "@count transfers",
|
||||
* ),
|
||||
*
|
||||
* base_table = "daftar_transfers",
|
||||
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "uuid" = "uuid",
|
||||
* "khatu" = "kid",
|
||||
* "bundle" = "type",
|
||||
* "created" = "created",
|
||||
* "changed" = "changed",
|
||||
* },
|
||||
* )
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
class Transfer extends SodoEntityBase implements TransferInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
// Override parent label and description.
|
||||
$fields['kid']->setDescription(t('The account from/to which the amount was transferred.'));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core;
|
||||
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedInterface;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Provides an interface defining a khatu (account) entity.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
interface KhatuInterface extends
|
||||
ContentEntityInterface,
|
||||
EntityOwnerInterface,
|
||||
EntityCreatedInterface,
|
||||
EntityChangedInterface {
|
||||
|
||||
/**
|
||||
* Khatu (Account) entity types.
|
||||
* Key/value pairs of machine names and labels.
|
||||
*/
|
||||
const TYPES = [
|
||||
'bank' => 'Bank',
|
||||
'brokerage' => 'Brokerage',
|
||||
'ira_tradition' => 'Traditional IRA',
|
||||
'ira_rollover' => 'Rollover IRA',
|
||||
'ira_roth' => 'Roth IRA',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* Returns TRUE if this entity is of bank type.
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
public function isBank();
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* Returns TRUE if this entity is of brokerage type.
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
public function isBrokerage();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core;
|
||||
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityKhatuInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining an order entity.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
interface OrderInterface extends
|
||||
ContentEntityInterface,
|
||||
EntityKhatuInterface,
|
||||
EntityCreatedInterface,
|
||||
EntityChangedInterface {
|
||||
|
||||
public const DEFAULT_TYPE = 'mkt';
|
||||
|
||||
public const TYPES = [
|
||||
'mkt' => 'Market',
|
||||
'lmt' => 'Limit',
|
||||
'stp' => 'Stop',
|
||||
'stplmt' => 'Stop Limit',
|
||||
];
|
||||
|
||||
public const ACTIONS = [
|
||||
'buy' => 'Buy',
|
||||
'sell' => 'Sell',
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_core;
|
||||
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityCreatedInterface;
|
||||
use Drupal\daftar_core\Entity\Helper\EntityKhatuInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a transfer entity.
|
||||
*
|
||||
* @ingroup daftar
|
||||
*/
|
||||
interface TransferInterface extends
|
||||
ContentEntityInterface,
|
||||
EntityKhatuInterface,
|
||||
EntityCreatedInterface,
|
||||
EntityChangedInterface {
|
||||
|
||||
// no code
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
name: Dafter Content
|
||||
description: Provides custom forms for adding content (e.g. Accounts, Orders, Dividend and Trasnsfers).
|
||||
core: 8.x
|
||||
type: module
|
||||
package: Daftar
|
||||
core_version_requirement: ^8 || ^9
|
||||
version: 1.0.0
|
||||
dependencies:
|
||||
- drupal:node
|
||||
- daftar_stock_api:daftar_stock_api
|
||||
@@ -0,0 +1,16 @@
|
||||
daftar_content.account_form:
|
||||
path: '/portfolio/add/account/{type}'
|
||||
defaults:
|
||||
_form: '\Drupal\daftar_content\Form\AccountForm'
|
||||
_title: 'Add an Account'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
|
||||
daftar_content.modal_form:
|
||||
path: '/portfolio/modal/add/{type}'
|
||||
defaults:
|
||||
_title: 'Content Form Modal'
|
||||
_controller: '\Drupal\daftar_content\Controller\ContentFormModalController::launchModal'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
type: '[a-z]+'
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_content\Controller;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\OpenModalDialogCommand;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Form\FormBuilder;
|
||||
use Drupal\daftar_content\Form\ContentBaseForm;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Provides callback methods used for launching modals with content form.
|
||||
* @package Drupal\daftar_content\Controller
|
||||
*/
|
||||
class ContentFormModalController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* @var FormBuilder
|
||||
*/
|
||||
protected $formBuilder;
|
||||
|
||||
/**
|
||||
* OptionsController constructor.
|
||||
* @param FormBuilder $formBuilder
|
||||
*/
|
||||
public function __construct(FormBuilder $formBuilder) {
|
||||
$this->formBuilder = $formBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('form_builder')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an AJAX response with the requested form.
|
||||
* @param string $type
|
||||
* @param int $width
|
||||
* @return AjaxResponse
|
||||
*/
|
||||
public function launchModal($type, $width = 600) {
|
||||
|
||||
$form = self::getFormInfo($type);
|
||||
if (NULL === $form) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$response = new AjaxResponse();
|
||||
$modalForm = $this->formBuilder->getForm($form['class'], $type, ContentBaseForm::DISPLAY_MODE_MODAL);
|
||||
$response->addCommand(new OpenModalDialogCommand($form['title'], $modalForm, ['width' => $width]));
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map id to name of a class that implements the form.
|
||||
* @param string $type
|
||||
* @return array|null
|
||||
*/
|
||||
private static function getFormInfo($type) {
|
||||
switch($type) {
|
||||
case 'brokerage':
|
||||
return [
|
||||
'class' => 'Drupal\daftar_content\Form\AccountForm',
|
||||
'title' => 'Add a Brokerage Account',
|
||||
];
|
||||
case 'bank':
|
||||
return [
|
||||
'class' => 'Drupal\daftar_content\Form\AccountForm',
|
||||
'title' => 'Add a Bank Account',
|
||||
];
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_content\Entity;
|
||||
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
|
||||
|
||||
/**
|
||||
* Defines the Dividend Transaction entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "transaction_dividend",
|
||||
* label = @Translation("Dividend"),
|
||||
* base_table = "dc_transaction_dividend",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "uuid" = "uuid",
|
||||
* "account" = "aid",
|
||||
* "changed" = "last_updated"
|
||||
* },
|
||||
* )
|
||||
*
|
||||
* @ingroup transaction_entity
|
||||
*/
|
||||
class Dividend extends TransactionEntityBase {
|
||||
|
||||
const TAX_TYPE_QUALIFIED = 10;
|
||||
const TAX_TYPE_ORDINARY = 20;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
// Override parent label and description.
|
||||
$fields['transaction_date']->setLabel(t('Date Paid'));
|
||||
$fields['transaction_date']->setDescription(t('Date when this dividend was paid.'));
|
||||
|
||||
$fields['execution_date'] = BaseFieldDefinition::create('datetime')
|
||||
->setLabel(t('Execution Date'))
|
||||
->setDescription(t('Date when this dividend was executed.'))
|
||||
->setSetting('datetime_type', 'date');
|
||||
|
||||
$fields['qualified'] = BaseFieldDefinition::create('boolean')
|
||||
->setLabel(t('Qualified dividend'))
|
||||
->setDescription(t('A boolean flag indicating qualified dividend classification.'))
|
||||
->setDefaultValue(TRUE);
|
||||
|
||||
$fields['auto_imported'] = BaseFieldDefinition::create('boolean')
|
||||
->setLabel(t('Auto Imported'))
|
||||
->setDescription(t('A boolean flag indicating whether the dividend was automatically imported.'))
|
||||
->setDefaultValue(FALSE);
|
||||
|
||||
// Entity reference field, holds the reference to a taxonomy term object.
|
||||
$fields['symbol'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Symbol'))
|
||||
->setDescription(t('Stock ticker.'))
|
||||
->setSetting('target_type', 'taxonomy_term')
|
||||
->setSetting('handler', 'default')
|
||||
->setSetting('handler_settings', [
|
||||
'target_bundles' => [
|
||||
'symbols' => 'symbols',
|
||||
],
|
||||
])
|
||||
->setReadOnly(TRUE)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['quantity'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Number of shares'))
|
||||
->setDescription(t('Number of shares on which dividend was paid.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 2)
|
||||
->setRequired(TRUE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_content\Entity\Interfaces;
|
||||
|
||||
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Defines a common interface for entities that belong to an account.
|
||||
*
|
||||
* An account is a node entity to which all transaction entities are
|
||||
* associated with.
|
||||
*
|
||||
* @ingroup transaction_entity
|
||||
*/
|
||||
interface TransactionEntityAccountInterface {
|
||||
|
||||
/**
|
||||
* Returns the entity's account node entity.
|
||||
*
|
||||
* @return NodeInterface
|
||||
* The account node entity.
|
||||
*/
|
||||
public function getAccount();
|
||||
|
||||
/**
|
||||
* Sets the entity's account node entity.
|
||||
*
|
||||
* @param NodeInterface $account
|
||||
* The account node entity.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAccount(NodeInterface $account);
|
||||
|
||||
/**
|
||||
* Returns the entity's account node ID.
|
||||
*
|
||||
* @return int|null
|
||||
* The account node ID, or NULL in case the account ID field has not been set on
|
||||
* the entity.
|
||||
*/
|
||||
public function getAccountId();
|
||||
|
||||
/**
|
||||
* Sets the entity's account node ID.
|
||||
*
|
||||
* @param int $aid
|
||||
* The account node id.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAccountId($aid);
|
||||
|
||||
}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_content\Entity\Interfaces;
|
||||
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a Transaction entity.
|
||||
*
|
||||
* We have this interface so we can join the other interfaces it extends.
|
||||
*
|
||||
* @ingroup transaction_entity
|
||||
*/
|
||||
interface TransactionEntityInterface extends ContentEntityInterface, TransactionEntityAccountInterface, EntityChangedInterface {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_content\Entity;
|
||||
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
|
||||
|
||||
/**
|
||||
* Defines the Order Transaction entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "transaction_order",
|
||||
* label = @Translation("Order"),
|
||||
* base_table = "dc_transaction_order",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "uuid" = "uuid",
|
||||
* "account" = "aid",
|
||||
* "changed" = "last_updated",
|
||||
* "notes" = "notes"
|
||||
* },
|
||||
* )
|
||||
*
|
||||
* @ingroup transaction_entity
|
||||
*/
|
||||
class Order extends TransactionEntityBase {
|
||||
|
||||
const ACTION_BUY = 10;
|
||||
const ACTION_SELL = 20;
|
||||
const ACTION_SELL_SHORT = 30;
|
||||
const ACTION_BUY_TO_COVER = 40;
|
||||
|
||||
const TYPE_MARKET = 10;
|
||||
const TYPE_LIMIT = 20;
|
||||
const TYPE_STOP_MARKET = 30;
|
||||
const TYPE_STOP_LIMIT = 40;
|
||||
const TYPE_MARKET_ON_CLOSE = 50;
|
||||
const TYPE_TRAILING_STOP_D = 60;
|
||||
const TYPE_TRAILING_STOP_P = 70;
|
||||
|
||||
/**
|
||||
* Maps order action to a constant.
|
||||
*
|
||||
* @param string $action Value to map.
|
||||
*
|
||||
* @return int Returns an integer value greater than zero if successfully mapped.
|
||||
*/
|
||||
public static function mapAction($action) {
|
||||
switch(strtolower($action)) {
|
||||
case 'buy':
|
||||
return static::ACTION_BUY;
|
||||
case 'sell':
|
||||
return static::ACTION_SELL;
|
||||
case 'sell short':
|
||||
return static::ACTION_SELL_SHORT;
|
||||
case 'buy to cover':
|
||||
return static::ACTION_BUY_TO_COVER;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps order type to a constant.
|
||||
*
|
||||
* @param string $type Value to map
|
||||
*
|
||||
* @return int Returns an integer value greater than zero if successfully mapped.
|
||||
*/
|
||||
public static function mapType($type) {
|
||||
switch(strtolower($type)) {
|
||||
case 'market':
|
||||
return static::TYPE_MARKET;
|
||||
case 'limit':
|
||||
return static::TYPE_LIMIT;
|
||||
case 'stop market':
|
||||
return static::TYPE_STOP_MARKET;
|
||||
case 'stop limit':
|
||||
return static::TYPE_STOP_LIMIT;
|
||||
case 'market on close':
|
||||
return static::TYPE_MARKET_ON_CLOSE;
|
||||
case 'trailing stop $':
|
||||
return static::TYPE_TRAILING_STOP_D;
|
||||
case 'trailing stop %':
|
||||
return static::TYPE_TRAILING_STOP_P;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['action'] = BaseFieldDefinition::create('integer')
|
||||
->setLabel(t('Order Action'))
|
||||
->setDescription(t('The action being performed by this order.'))
|
||||
->setSetting('size', 'tiny')
|
||||
->setSetting('unsigned', TRUE)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['type'] = BaseFieldDefinition::create('integer')
|
||||
->setLabel(t('Order Type'))
|
||||
->setDescription(t('The type of order being executed.'))
|
||||
->setSetting('size', 'tiny')
|
||||
->setSetting('unsigned', TRUE);
|
||||
|
||||
// Entity reference field, holds the reference to a taxonomy term object.
|
||||
$fields['symbol'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Symbol'))
|
||||
->setDescription(t('Stock ticker.'))
|
||||
->setSetting('target_type', 'taxonomy_term')
|
||||
->setSetting('handler', 'default')
|
||||
->setSetting('handler_settings', [
|
||||
'target_bundles' => [
|
||||
'symbols' => 'symbols',
|
||||
],
|
||||
])
|
||||
->setReadOnly(TRUE)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['quantity'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Quantity'))
|
||||
->setDescription(t('Number of shares exchanged.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 2)
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['on_hand'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Quantity On Hand'))
|
||||
->setDescription(t('Number of shares currently on hand.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 2);
|
||||
|
||||
$fields['pps'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Price per Share'))
|
||||
->setDescription(t('Price paid per share (computed).'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 2)
|
||||
->setComputed(TRUE);
|
||||
|
||||
$fields['commission'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Commission'))
|
||||
->setDescription(t('Commission charged by the brokerage.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 2)
|
||||
->setDefaultValue(0);
|
||||
|
||||
$fields['fees'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Fees'))
|
||||
->setDescription(t('Fees charged for the transaction.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 2)
|
||||
->setDefaultValue(0);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_content\Entity\Traits;
|
||||
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\daftar_content\Entity\Interfaces\TransactionEntityAccountInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
|
||||
trait TransactionAccountTrait {
|
||||
|
||||
/**
|
||||
* Returns an array of base field definitions for entity account.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type to add the account field to.
|
||||
*
|
||||
* @return \Drupal\Core\Field\BaseFieldDefinition[]
|
||||
* An array of base field definitions.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
* Thrown when the entity type does not implement TransactionEntityAccountInterface or
|
||||
* if it does not have an "account" entity key.
|
||||
*/
|
||||
public static function accountBaseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
if (!is_subclass_of($entity_type->getClass(), TransactionEntityAccountInterface::class)) {
|
||||
throw new UnsupportedEntityTypeDefinitionException('The entity type ' . $entity_type->id() . ' does not implement \Drupal\daftar_content\Entity\Interfaces\TransactionEntityAccountInterface.');
|
||||
}
|
||||
if (!$entity_type->hasKey('account')) {
|
||||
throw new UnsupportedEntityTypeDefinitionException('The entity type ' . $entity_type->id() . ' does not have an "account" entity key.');
|
||||
}
|
||||
|
||||
return [
|
||||
// Account field of the transaction.
|
||||
// Entity reference field, holds the reference to a account node object.
|
||||
$entity_type->getKey('account') => BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(new TranslatableMarkup('Account ID'))
|
||||
->setDescription(new TranslatableMarkup('The id of the account node this transaction is associated with.'))
|
||||
->setSetting('target_type', 'node')
|
||||
->setSetting('handler', 'default')
|
||||
->setSetting('handler_settings', [
|
||||
'target_bundles' => [
|
||||
'account' => 'account',
|
||||
],
|
||||
])
|
||||
->setReadOnly(TRUE),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity's account node entity.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* The account node entity.
|
||||
*/
|
||||
public function getAccount() {
|
||||
$key = $this->getEntityType()->getKey('account');
|
||||
return $this->get($key)->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entity's account node entity.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $account
|
||||
* The account node entity.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAccount(NodeInterface $account) {
|
||||
$key = $this->getEntityType()->getKey('account');
|
||||
$this->set($key, $account);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity's account node ID.
|
||||
*
|
||||
* @return int|null
|
||||
* The account node ID, or NULL in case the account ID field has not been set on
|
||||
* the entity.
|
||||
*/
|
||||
public function getAccountId() {
|
||||
return $this->getEntityKey('account');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entity's account node ID.
|
||||
*
|
||||
* @param int $aid
|
||||
* The account node id.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAccountId($aid) {
|
||||
$key = $this->getEntityType()->getKey('account');
|
||||
$this->set($key, $aid);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_content\Entity;
|
||||
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityChangedTrait;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\daftar_content\Entity\Interfaces\TransactionEntityInterface;
|
||||
use Drupal\daftar_content\Entity\Traits\TransactionAccountTrait;
|
||||
|
||||
|
||||
/**
|
||||
* Implements Transaction Entity specific enhancements to the Content Entity Base class.
|
||||
*
|
||||
* @TODO Add transaction_id (for mapping to brokerage) and notes fields in
|
||||
* a separate tables.
|
||||
*
|
||||
* @ingroup transaction_entity
|
||||
*/
|
||||
class TransactionEntityBase extends ContentEntityBase implements TransactionEntityInterface {
|
||||
|
||||
use EntityChangedTrait;
|
||||
use TransactionAccountTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
$fields += static::accountBaseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['transaction_date'] = BaseFieldDefinition::create('datetime')
|
||||
->setLabel(t('Transaction Date'))
|
||||
->setDescription(t('Date when this transaction took place.'))
|
||||
->setSetting('datetime_type', 'date')
|
||||
->setRequired(TRUE);
|
||||
|
||||
$fields['amount'] = BaseFieldDefinition::create('decimal')
|
||||
->setLabel(t('Amount'))
|
||||
->setDescription(t('Total transaction amount.'))
|
||||
->setSetting('precision', 10)
|
||||
->setSetting('scale', 2)
|
||||
->setRequired(TRUE);
|
||||
|
||||
if ($entity_type->hasKey('notes')) {
|
||||
$notesKey = $entity_type->getKey('notes');
|
||||
$fields[$notesKey] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Notes'))
|
||||
->setDescription(t('Short descriptive text for future references.'))
|
||||
->setSetting('max_length', 255);
|
||||
}
|
||||
|
||||
if ($entity_type->hasKey('changed')) {
|
||||
$changedKey = $entity_type->getKey('changed');
|
||||
$fields[$changedKey] = BaseFieldDefinition::create('changed')
|
||||
->setLabel(t('Last Updated'))
|
||||
->setDescription(t('The time that the transaction was last updated.'))
|
||||
->setReadOnly(TRUE);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_content\Entity;
|
||||
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
|
||||
/**
|
||||
* Defines the Transfer Transaction entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "transaction_transfer",
|
||||
* label = @Translation("Transfer"),
|
||||
* base_table = "dc_transaction_transfer",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "uuid" = "uuid",
|
||||
* "account" = "aid",
|
||||
* "changed" = "last_updated",
|
||||
* "notes" = "notes"
|
||||
* },
|
||||
* )
|
||||
*
|
||||
* @ingroup transaction_entity
|
||||
*/
|
||||
class Transfer extends TransactionEntityBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
// Override parent label and description.
|
||||
if ($entity_type->hasKey('account')) {
|
||||
$key = $entity_type->getKey('account');
|
||||
$fields[$key]->setLabel(t('To'));
|
||||
$fields[$key]->setDescription(t('The Account to which the amount was deposited.'));
|
||||
}
|
||||
|
||||
// Account field of the transaction.
|
||||
// Entity reference field, holds the reference to a account node object.
|
||||
$fields['account_withdrawn'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('From'))
|
||||
->setDescription(t('The Account from which the amount was withdrawn.'))
|
||||
->setSetting('target_type', 'node')
|
||||
->setSetting('handler', 'default')
|
||||
->setSetting('handler_settings', [
|
||||
'target_bundles' => [
|
||||
'account' => 'account',
|
||||
],
|
||||
])
|
||||
->setRequired(TRUE)
|
||||
->setReadOnly(TRUE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_content\Form;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\OpenModalDialogCommand;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class AccountForm extends ContentBaseForm {
|
||||
|
||||
const MIN_LENGTH_ALIAS = 5;
|
||||
const MAX_LENGTH_ALIAS = 30;
|
||||
const MAX_LENGTH_NOTE = 150;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'daftar_content_brokerage_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $type = 'brokerage', $display_mode = self::DISPLAY_MODE_DEFAULT) {
|
||||
$type = strtolower($type);
|
||||
if($type !== 'brokerage' && $type != 'bank') {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$form_state->set('type', $type);
|
||||
$fields = $this->getFieldDefinitions('account');
|
||||
|
||||
// Get anything we need from the base class.
|
||||
$form = parent::buildForm($form, $form_state, $display_mode);
|
||||
|
||||
$form['title'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $fields['title']->getLabel(),
|
||||
'#description' => $this->t('%title for the account. Do NOT enter your account number.', ['%title' => $fields['title']->getLabel()]),
|
||||
'#maxlength' => self::MAX_LENGTH_ALIAS,
|
||||
'#pattern' => '^[a-zA-Z0-9 ]{5,}$',
|
||||
'#attributes' => ['title' => $this->t('Must be at least five characters long. Only alphanumeric and space characters are supported.')],
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
switch($display_mode) {
|
||||
case self::DISPLAY_MODE_EMBEDDED:
|
||||
break;
|
||||
|
||||
case self::DISPLAY_MODE_DEFAULT:
|
||||
default:
|
||||
// set page title
|
||||
$form['#title'] = ($type === 'brokerage')
|
||||
? $this->t('Add a Brokerage Account')
|
||||
: $this->t('Add a Bank Account');
|
||||
|
||||
// no break - intentionally left off
|
||||
|
||||
case self::DISPLAY_MODE_MODAL:
|
||||
if (isset($fields['field_notes'])) {
|
||||
$form['field_notes'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $fields['field_notes']->getLabel(),
|
||||
'#description' => $this->t('Max characters allowed: %max', ['%max' => self::MAX_LENGTH_NOTE]),
|
||||
'#maxlength' => self::MAX_LENGTH_NOTE,
|
||||
'#rows' => 2,
|
||||
'#resizable' => 'none',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
$type = $form_state->get('type');
|
||||
$title = trim($form_state->getValue('title'));
|
||||
|
||||
// @todo change to custom exception
|
||||
if($type !== 'brokerage' && $type !== 'bank') {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$titleLabel = $form['title']['#title'];
|
||||
if (strlen($title) < self::MIN_LENGTH_ALIAS) {
|
||||
$form_state->setErrorByName('title', $this->t(
|
||||
'%label must be at least %min characters long.',
|
||||
['%label' => $titleLabel, '%min' => self::MIN_LENGTH_ALIAS]
|
||||
));
|
||||
}
|
||||
elseif (preg_match('/[^a-zA-Z0-9 $]/', $title)) {
|
||||
$form_state->setErrorByName('title', $this->t(
|
||||
'%label contains invalid characters.',
|
||||
['%label' => $titleLabel]
|
||||
));
|
||||
}
|
||||
elseif ($this->isDuplicate($title)) {
|
||||
$form_state->setErrorByName('title', $this->t(
|
||||
'An account already exists under your portfolio with given alias.',
|
||||
['%label' => $titleLabel]
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
$type = $form_state->get('type');
|
||||
$title = trim($form_state->getValue('title'));
|
||||
$notes = substr(trim($form_state->getValue('field_notes')), 0, self::MAX_LENGTH_NOTE);
|
||||
|
||||
$node = $this->nodeStorage->create([
|
||||
'type' => 'account',
|
||||
'status' => TRUE,
|
||||
'uid' => $this->currentUser()->id(),
|
||||
'title' => $title,
|
||||
'field_account_status' => TRUE,
|
||||
'field_account_type' => ($type == 'brokerage') ? 'br' : 'ba',
|
||||
'field_notes' => $notes
|
||||
]);
|
||||
|
||||
//@TODO improve for AJAX and embedded form.
|
||||
// Remove message.
|
||||
try {
|
||||
$saveOperation = $node->save();
|
||||
if ($saveOperation == SAVED_NEW) {
|
||||
$this->messenger()->addMessage(
|
||||
$this->t('Created %type account: %title', [
|
||||
'%type' => $form_state->get('type'),
|
||||
'%title' => $title,
|
||||
]),
|
||||
MessengerInterface::TYPE_STATUS
|
||||
);
|
||||
$form_state->set('account_node', $node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
$this->messenger()->addMessage(
|
||||
$this->t('Unable to create account. Please try again.'),
|
||||
MessengerInterface::TYPE_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$form_state->set('account_node', NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function submitFormAjaxAdd(array &$form, FormStateInterface $form_state) {
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new OpenModalDialogCommand("Success!", 'The modal form has been submitted.'));
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function isDuplicate($title) {
|
||||
$title = strtolower($title);
|
||||
$isDuplicate = FALSE;
|
||||
$results = $this->getAccountNids();
|
||||
|
||||
if (!empty($results)) {
|
||||
$nodes = $this->nodeStorage->loadMultiple($results);
|
||||
foreach ($nodes as $nid => $node) {
|
||||
if ( $title === strtolower(trim($node->label())) ) {
|
||||
$isDuplicate = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $isDuplicate;
|
||||
}
|
||||
|
||||
protected function getAccountNids() {
|
||||
return $this->nodeStorage->getQuery()
|
||||
->condition('status', 1)
|
||||
->condition('type', 'account')
|
||||
->condition('uid', $this->currentUser()->id())
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_content\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\node\NodeStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
abstract class ContentBaseForm extends FormBase {
|
||||
|
||||
const DISPLAY_MODE_DEFAULT = 'default';
|
||||
const DISPLAY_MODE_MODAL = 'modal';
|
||||
const DISPLAY_MODE_EMBEDDED = 'embedded';
|
||||
|
||||
/**
|
||||
* @var EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* @var NodeStorageInterface
|
||||
*/
|
||||
protected $nodeStorage;
|
||||
|
||||
public function __construct(EntityFieldManagerInterface $entity_field_manager, NodeStorageInterface $node_storage) {
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->nodeStorage = $node_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('entity_type.manager')->getStorage('node')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for AJAX submit. Deriving class must implement this method.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @return array
|
||||
* Renderable array (the box element)
|
||||
*/
|
||||
public abstract function submitFormAjaxAdd(array &$form, FormStateInterface $form_state);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $display_mode = self::DISPLAY_MODE_DEFAULT) {
|
||||
|
||||
$form['actions'] = [
|
||||
'#type' => 'actions',
|
||||
'#weight' => 100,
|
||||
];
|
||||
|
||||
switch($display_mode) {
|
||||
case self::DISPLAY_MODE_EMBEDDED:
|
||||
unset($form['actions']);
|
||||
break;
|
||||
|
||||
case self::DISPLAY_MODE_MODAL:
|
||||
$form['status_messages'] = [
|
||||
'#type' => 'status_messages',
|
||||
'#weight' => -100,
|
||||
];
|
||||
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Add'),
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'use-ajax',
|
||||
],
|
||||
],
|
||||
'#ajax' => [
|
||||
'callback' => [$this, 'submitFormAjaxAdd'],
|
||||
'event' => 'click',
|
||||
],
|
||||
];
|
||||
|
||||
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';
|
||||
|
||||
break;
|
||||
|
||||
case self::DISPLAY_MODE_DEFAULT:
|
||||
default:
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Add'),
|
||||
];
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $node_bundle
|
||||
* The bundle (e.g. account, order, dividend & etc).
|
||||
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
|
||||
*/
|
||||
protected function getFieldDefinitions($node_bundle) {
|
||||
return $this->entityFieldManager->getFieldDefinitions('node', $node_bundle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
name: Dafter Import CSV
|
||||
description: Allows user to import transactions from a CSV file.
|
||||
core: 8.x
|
||||
type: module
|
||||
package: Daftar
|
||||
core_version_requirement: ^8 || ^9
|
||||
version: 1.0.0
|
||||
dependencies:
|
||||
- daftar_content:daftar_content
|
||||
- daftar_stock_api:daftar_stock_api
|
||||
@@ -0,0 +1,15 @@
|
||||
daftar_import.csv_orders:
|
||||
path: '/import/csv/orders'
|
||||
defaults:
|
||||
_form: '\Drupal\daftar_import\Form\ImportCsvOrdersForm'
|
||||
_title: 'Import Orders from CSV'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
|
||||
daftar_import.csv_transfers:
|
||||
path: '/import/csv/transfers'
|
||||
defaults:
|
||||
_form: '\Drupal\daftar_import\Form\ImportCsvTransfersForm'
|
||||
_title: 'Import Transfers from CSV'
|
||||
requirements:
|
||||
_role: 'authenticated'
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Drupal\daftar_import\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CsvDataException extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,557 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_import\Form;
|
||||
|
||||
use Drupal\Component\Utility\Bytes;
|
||||
use Drupal\Component\Utility\Environment;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\daftar_content\Form\AccountForm;
|
||||
use Drupal\daftar_content\Form\ContentBaseForm;
|
||||
use Drupal\daftar_import\Exception\CsvDataException;
|
||||
use Drupal\daftar_stock_api\Exception\StockApiException;
|
||||
use Drupal\daftar_stock_api\StockApiServiceInterface;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\file\FileStorageInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\node\NodeStorageInterface;
|
||||
use Drupal\taxonomy\TermStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides shared methods.
|
||||
* @package Drupal\daftar\Form
|
||||
*/
|
||||
abstract class ImportCsvBaseForm extends FormBase {
|
||||
|
||||
const MAX_CSV_FILE_SIZE = '5MB';
|
||||
const TERM_TYPE_SYMBOLS = 'symbols';
|
||||
const TERM_TYPE_EXCHANGES = 'exchanges';
|
||||
const TERM_TYPE_SECTORS = 'sectors';
|
||||
const TERM_TYPE_INDUSTRIES = 'industries';
|
||||
|
||||
/**
|
||||
* @var NodeStorageInterface
|
||||
*/
|
||||
protected $nodeStorage;
|
||||
|
||||
/**
|
||||
* @var TermStorageInterface
|
||||
*/
|
||||
protected $termStorage;
|
||||
|
||||
/**
|
||||
* @var FileStorageInterface
|
||||
*/
|
||||
protected $fileStorage;
|
||||
|
||||
/**
|
||||
* @var StockApiServiceInterface
|
||||
*/
|
||||
protected $stockApi;
|
||||
|
||||
/**
|
||||
* @var AccountForm
|
||||
*/
|
||||
protected $accountForm;
|
||||
|
||||
public function __construct(NodeStorageInterface $node_storage, TermStorageInterface $term_storage, FileStorageInterface $file_storage, StockApiServiceInterface $stock_api) {
|
||||
$this->nodeStorage = $node_storage;
|
||||
$this->termStorage = $term_storage;
|
||||
$this->fileStorage = $file_storage;
|
||||
$this->stockApi = $stock_api;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
$form = new static(
|
||||
$container->get('entity_type.manager')->getStorage('node'),
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_term'),
|
||||
$container->get('entity_type.manager')->getStorage('file'),
|
||||
$container->get('daftar.stock_api.alphavantage')
|
||||
);
|
||||
|
||||
$form->accountForm = AccountForm::create($container);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$brokerages = $this->getBrokerageOptions();
|
||||
$maxCsvSize = intval($this->getMaxCsvSize());
|
||||
|
||||
$form['description'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => '<strong>Override or unset this element.</strong>',
|
||||
'#weight' => '-20',
|
||||
];
|
||||
|
||||
if (!empty($brokerages)) {
|
||||
$form['brokerage'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Brokerage Account'),
|
||||
'#options' => $brokerages,
|
||||
'#required' => TRUE,
|
||||
'#validated' => TRUE, // Setting it to TRUE stops default validation. Must validate manually.
|
||||
'#weight' => -15,
|
||||
];
|
||||
}
|
||||
else {
|
||||
$form['brokerage'] = [
|
||||
'#type' => 'hidden',
|
||||
'#value' => 'new'
|
||||
];
|
||||
}
|
||||
|
||||
$form += $this->buildAccountForm($form_state);
|
||||
if (isset($form['title'])) {
|
||||
unset($form['title']['#required']);
|
||||
|
||||
$form['title']['#title'] = $this->t('New Account Alias');
|
||||
$form['title']['#weight'] = -10;
|
||||
$form['title']['#states'] = [
|
||||
'visible' => [
|
||||
':input[name="brokerage"]' => ['value' => 'new'],
|
||||
],
|
||||
'required' => [
|
||||
':input[name="brokerage"]' => ['value' => 'new'],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$form['csv_file'] = [
|
||||
'#type' => 'managed_file',
|
||||
'#title' => $this->t('CSV File'),
|
||||
'#description' => $this->t(
|
||||
'Upload a CSV file containing your transactions. Maximum file size allowed: %size',
|
||||
['%size' => $this->getReadableFileSize($maxCsvSize)]
|
||||
),
|
||||
'#multiple' => FALSE,
|
||||
'#upload_location' => 'private://imports',
|
||||
'#upload_validators' => [
|
||||
'file_validate_extensions' => ['csv'],
|
||||
'file_validate_size' => [$maxCsvSize],
|
||||
],
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['file_description'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t(
|
||||
'Required columns in the CSV: %columns.',
|
||||
['%columns' => $this->getNaturalLanguageJoin($this->getRequiredKeys())]
|
||||
),
|
||||
];
|
||||
|
||||
// Add a submit button that handles the submission of the form.
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Import'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
protected function buildAccountForm(FormStateInterface $form_state) {
|
||||
$accountFormState = new FormState();
|
||||
$accountForm = $this->accountForm->buildForm([], $accountFormState, 'brokerage', ContentBaseForm::DISPLAY_MODE_EMBEDDED);
|
||||
$form_state->set('account_form', $accountForm);
|
||||
$form_state->set('account_form_state', $accountFormState);
|
||||
|
||||
return $accountForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$brokerages = $this->getBrokerageOptions();
|
||||
$brokerageSelected = $form_state->getValue('brokerage');
|
||||
|
||||
if ($brokerageSelected === 'new') {
|
||||
$this->validateAccountForm($form_state);
|
||||
}
|
||||
// Make sure the selected brokerage account belongs to the current user.
|
||||
elseif ($brokerageSelected !== 'new' && !isset($brokerages[$brokerageSelected])) {
|
||||
$form_state->setErrorByName('brokerage', $this->t('Please select a brokerage account.'));
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateAccountForm(FormStateInterface $form_state) {
|
||||
/** @var FormStateInterface $accountFormState */
|
||||
$newBrokerageTitle = trim($form_state->getValue('title'));
|
||||
$accountForm = $form_state->get('account_form');
|
||||
$accountFormState = $form_state->get('account_form_state');
|
||||
$accountFormState->setValue('title', $newBrokerageTitle);
|
||||
$accountFormState->setValue('type', 'brokerage');
|
||||
|
||||
$this->accountForm->validateForm($accountForm, $accountFormState);
|
||||
$accountFormErrors = (array) $accountFormState->getErrors();
|
||||
|
||||
foreach ($accountFormErrors as $key => $error) {
|
||||
$form_state->setErrorByName($key, $error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
/** @var NodeInterface $brokerage */
|
||||
|
||||
$brokerageSelected = $form_state->getValue('brokerage');
|
||||
|
||||
if ($brokerageSelected === 'new') {
|
||||
$brokerage = $this->submitAccountForm($form_state);
|
||||
}
|
||||
else {
|
||||
$brokerage = $this->getBrokerageNodeFromUuid($brokerageSelected);
|
||||
}
|
||||
|
||||
if (NULL === $brokerage) {
|
||||
$this->messenger()->addMessage(
|
||||
$this->t('Unable to retrieve brokerage account.'),
|
||||
MessengerInterface::TYPE_ERROR
|
||||
);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$csvFile = $form_state->getValue('csv_file');
|
||||
if (isset($csvFile[0])) {
|
||||
try {
|
||||
$file = $this->preserveFile($csvFile[0]);
|
||||
$data = $this->parseCsvToArray($file->getFileUri());
|
||||
|
||||
$this->messenger()->addMessage(
|
||||
$this->t('Hooray! Passed Validation!'),
|
||||
MessengerInterface::TYPE_STATUS
|
||||
);
|
||||
|
||||
return [
|
||||
'brokerage' => $brokerage,
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
$this->messenger()->addMessage(
|
||||
$this->t('Unable to process CSV file. Please try again.'),
|
||||
MessengerInterface::TYPE_ERROR
|
||||
);
|
||||
}
|
||||
catch (CsvDataException $e) {
|
||||
$this->messenger()->addMessage(
|
||||
['#type' => 'item', '#markup' => $e->getMessage()],
|
||||
MessengerInterface::TYPE_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
protected function submitAccountForm(FormStateInterface $form_state) {
|
||||
$accountForm = $form_state->get('account_form');
|
||||
$accountFormState = $form_state->get('account_form_state');
|
||||
|
||||
$this->accountForm->submitForm($accountForm, $accountFormState);
|
||||
return $accountFormState->get('account_node');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespaces form a string and converts it to lowercase.
|
||||
* @param string $key a string to sanitize
|
||||
* @return string a sanitized string
|
||||
*/
|
||||
public static function sanitizeKey($key) {
|
||||
$key = trim($key);
|
||||
return strtolower($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array an array of required keys
|
||||
*/
|
||||
protected abstract function getRequiredKeys();
|
||||
|
||||
protected function getMaxCsvSize() {
|
||||
static $max_size = -1;
|
||||
|
||||
if ($max_size < 0) {
|
||||
// Start with site-wide max size.
|
||||
$max_size = Environment::getUploadMaxSize();
|
||||
|
||||
// Reduce max_size if MAX_CSV_FILE_SIZE is less. Except if
|
||||
// MAX_CSV_FILE_SIZE is zero, which indicates no limit.
|
||||
$upload_max = Bytes::toInt(self::MAX_CSV_FILE_SIZE);
|
||||
if ($upload_max > 0 && $upload_max < $max_size) {
|
||||
$max_size = $upload_max;
|
||||
}
|
||||
}
|
||||
|
||||
return $max_size;
|
||||
}
|
||||
|
||||
protected function getReadableFileSize($size, $precision = 2) {
|
||||
$units = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
|
||||
$step = 1024;
|
||||
$i = 0;
|
||||
|
||||
while (($size / $step) > 0.9) {
|
||||
$size = $size / $step;
|
||||
$i++;
|
||||
}
|
||||
|
||||
return round($size, $precision).$units[$i];
|
||||
}
|
||||
|
||||
protected function getNaturalLanguageJoin(array $list, $conjunction = 'and') {
|
||||
$last = array_pop($list);
|
||||
if ($list) {
|
||||
return implode(', ', $list) . ' ' . $conjunction . ' ' . $last;
|
||||
}
|
||||
|
||||
return $last;
|
||||
}
|
||||
|
||||
protected function getBrokerageOptions() {
|
||||
$brokerages = [];
|
||||
$results = $this->getBrokerageNids();
|
||||
|
||||
if (!empty($results)) {
|
||||
$brokerages['new'] = '- ' . $this->t('Create a brokerage account') . ' -';
|
||||
$nodes = $this->nodeStorage->loadMultiple($results);
|
||||
foreach ($nodes as $nid => $node) {
|
||||
|
||||
$label = (boolean) $node->field_account_status->value
|
||||
? $node->label()
|
||||
: $node->label() . ' (inactive)';
|
||||
|
||||
$brokerages[$node->uuid()] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
return $brokerages;
|
||||
}
|
||||
|
||||
protected function getBrokerageNids() {
|
||||
return $this->nodeStorage->getQuery()
|
||||
->condition('status', 1)
|
||||
->condition('type', 'account')
|
||||
->condition('uid', $this->currentUser()->id())
|
||||
->condition('field_account_type', 'br')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uuid
|
||||
* @return NodeInterface|null
|
||||
*/
|
||||
protected function getBrokerageNodeFromUuid($uuid) {
|
||||
/** @var NodeInterface $brokerage */
|
||||
$brokerage = NULL;
|
||||
|
||||
$results = $this->nodeStorage->getQuery()
|
||||
->condition('status', 1)
|
||||
->condition('type', 'account')
|
||||
->condition('uuid', $uuid)
|
||||
->condition('field_account_type', 'br')
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
|
||||
if (!empty($results)) {
|
||||
reset($results);
|
||||
$brokerage = $this->nodeStorage->load(key($results));
|
||||
}
|
||||
|
||||
return $brokerage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $fid
|
||||
* @return FileInterface|null
|
||||
* @throws EntityStorageException
|
||||
*/
|
||||
protected function preserveFile($fid) {
|
||||
/** @var FileInterface $file */
|
||||
$file = $this->fileStorage->load($fid);
|
||||
$file->setPermanent();
|
||||
$file->save();
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $file_uri
|
||||
* @return array an associative array of CSV rows indexed by header keys
|
||||
* @throws EntityStorageException
|
||||
* @throws CsvDataException
|
||||
*/
|
||||
protected function parseCsvToArray($file_uri) {
|
||||
|
||||
if(!file_exists($file_uri) || !is_readable($file_uri)) {
|
||||
throw new EntityStorageException('File does not exists or is not readable.');
|
||||
}
|
||||
|
||||
$header = NULL;
|
||||
$rows = [];
|
||||
|
||||
if (($handle = fopen($file_uri, 'r')) !== FALSE ) {
|
||||
while (($row = fgetcsv($handle, 1000)) !== FALSE) {
|
||||
if (!$header){
|
||||
// Sanitize keys in the header.
|
||||
$header = array_map([__CLASS__, 'sanitizeKey'], $row);
|
||||
|
||||
// Check for missing keys.
|
||||
$missingRequiredKeys = array_diff($this->getRequiredKeys(), $header);
|
||||
if (!empty($missingRequiredKeys)) {
|
||||
throw new CsvDataException($this->t(
|
||||
'Missing required columns: %columns',
|
||||
['%columns' => implode($missingRequiredKeys, ', ')]
|
||||
));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$associativeRow = [];
|
||||
foreach ($row as $i => $value) {
|
||||
$associativeRow[$header[$i]] = $value;
|
||||
}
|
||||
$rows[] = $associativeRow;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a date string and converts it to ISO8601 format.
|
||||
* @param string $date_string A date string.
|
||||
* @return string A date string in ISO8601 format.
|
||||
* @throws CsvDataException
|
||||
* @todo Maybe this function can be swapped out with a Drupal/PHP function.
|
||||
*/
|
||||
protected function getIso8601DateString($date_string) {
|
||||
$parsedDate = date_parse($date_string);
|
||||
|
||||
if (FALSE === $parsedDate) {
|
||||
throw new CsvDataException('Unable to parse date.');
|
||||
}
|
||||
|
||||
if (!isset($parsedDate['year'])) {
|
||||
throw new CsvDataException('Date is missing the year.');
|
||||
}
|
||||
|
||||
$date = $parsedDate['year'];
|
||||
|
||||
if (isset($parsedDate['month'])) {
|
||||
$date .= '-' . str_pad($parsedDate['month'], 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
if (isset($parsedDate['day'])) {
|
||||
$date .= '-' . str_pad($parsedDate['day'], 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the taxonomy term ID of a given term.
|
||||
*
|
||||
* @param string $name The term name.
|
||||
* @param string $vid The vocabulary ID.
|
||||
* @param bool $create_term A boolean flag indicating whether to create
|
||||
* a new term when not found.
|
||||
*
|
||||
* @return int|null Returns the term ID for a given term; null otherwise.
|
||||
*
|
||||
* @throws EntityStorageException
|
||||
* @throws CsvDataException
|
||||
* @throws StockApiException
|
||||
*/
|
||||
protected function getTermId($name, $vid, $create_term = FALSE) {
|
||||
|
||||
// Extract symbol from
|
||||
if ($vid === static::TERM_TYPE_SYMBOLS) {
|
||||
$symbol = strtoupper($name);
|
||||
|
||||
if (strpos($symbol, ':') !== FALSE) {
|
||||
$symbolSplit = explode(':', $symbol);
|
||||
|
||||
if (!$symbolSplit[1]) {
|
||||
throw new CsvDataException('Invalid symbol provided.');
|
||||
}
|
||||
|
||||
$name = $symbolSplit[1];
|
||||
}
|
||||
}
|
||||
|
||||
$termIds = $this->termStorage->getQuery()
|
||||
->condition('vid', $vid)
|
||||
->condition('name', $name)
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
|
||||
if (!empty($termIds)) {
|
||||
return reset($termIds);
|
||||
}
|
||||
|
||||
if ($create_term) {
|
||||
return $this->createTerm($name, $vid);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the taxonomy term entity for a given term.
|
||||
*
|
||||
* @param string $name The term name.
|
||||
* @param string $vid The vocabulary ID.
|
||||
*
|
||||
* @return int|null Returns the term ID for a given term; null otherwise.
|
||||
*
|
||||
* @throws EntityStorageException
|
||||
* @throws CsvDataException
|
||||
* @throws StockApiException
|
||||
*/
|
||||
protected function createTerm($name, $vid) {
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'vid' => $vid,
|
||||
];
|
||||
|
||||
if ($vid === static::TERM_TYPE_SYMBOLS) {
|
||||
$profile = $this->stockApi->doSymbolLookup($name);
|
||||
|
||||
if (empty($profile)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$exchangeTid = $this->getTermId($profile['Exchange'], static::TERM_TYPE_EXCHANGES, TRUE);
|
||||
$sectorTid = $this->getTermId($profile['Sector'], static::TERM_TYPE_SECTORS, TRUE);
|
||||
$industryTid = $this->getTermId($profile['Industry'], static::TERM_TYPE_INDUSTRIES, TRUE);
|
||||
|
||||
$data += [
|
||||
'field_company_name' => $profile['Name'],
|
||||
'field_exchange' => ['target_id' => $exchangeTid],
|
||||
'field_sectors' => ['target_id' => $sectorTid],
|
||||
'field_industries' => ['target_id' => $industryTid],
|
||||
];
|
||||
}
|
||||
|
||||
$term = $this->termStorage->create($data);
|
||||
$term->save();
|
||||
return $term->id();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_import\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\daftar_content\Entity\Order;
|
||||
use Drupal\daftar_import\Exception\CsvDataException;
|
||||
use Drupal\daftar_stock_api\Exception\StockApiException;
|
||||
|
||||
/**
|
||||
* Implements form for processing order transactions from a CSV file.
|
||||
* @package Drupal\daftar\Form
|
||||
*/
|
||||
class ImportCsvOrdersForm extends ImportCsvBaseForm {
|
||||
|
||||
/**
|
||||
* @var EntityStorageInterface
|
||||
*/
|
||||
protected $orderStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'daftar_csv_import_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function getRequiredKeys() {
|
||||
return [
|
||||
'id',
|
||||
'date',
|
||||
'action',
|
||||
'symbol',
|
||||
'quantity',
|
||||
'amount',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
$form['#title'] = 'Import Orders from CSV';
|
||||
$form['description']['#markup'] = $this->t('Import transactions such as Buy, Sell, Sell Short and Buy to Cover from your brokerage.');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$data = parent::submitForm($form, $form_state);
|
||||
|
||||
// End processing if no CSV data was retrieved.
|
||||
if (FALSE === $data) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentRow = 1; // Starts at 1 because first row is a header row.
|
||||
try {
|
||||
$this->orderStorage = \Drupal::entityTypeManager()->getStorage('transaction_order');
|
||||
$brokerageNodeId = $data['brokerage']->id();
|
||||
|
||||
foreach ($data['data'] as $row) {
|
||||
$currentRow++;
|
||||
$this->createOrder($brokerageNodeId, $row);
|
||||
}
|
||||
}
|
||||
catch(CsvDataException $e) {
|
||||
$this->messenger->addError(
|
||||
'Data error on row ' . $currentRow . ': ' . $e->getMessage()
|
||||
);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// @TODO log to watchdog
|
||||
$this->messenger()->addError(
|
||||
"Import process stopped at row {$currentRow} due to internal errors."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the order entity.
|
||||
*
|
||||
* @param int $aidBrokerage Node ID of the brokerage account.
|
||||
* @param array $data An associative array containing CSV row.
|
||||
*
|
||||
* @throws CsvDataException
|
||||
* @throws EntityStorageException
|
||||
* @throws StockApiException
|
||||
*/
|
||||
protected function createOrder($aidBrokerage, array $data) {
|
||||
|
||||
$dateIso8601 = $this->getIso8601DateString($data['date']);
|
||||
$symbolTid = $this->getTermId($data['symbol'], static::TERM_TYPE_SYMBOLS, TRUE);
|
||||
|
||||
$action = Order::mapAction($data['action']);
|
||||
if (!$action) {
|
||||
throw new CsvDataException('Unable to parse order action.');
|
||||
}
|
||||
|
||||
$type = Order::mapType($data['type']);
|
||||
if (!$type) {
|
||||
throw new CsvDataException('Unable to parse order type.');
|
||||
}
|
||||
|
||||
$this->orderStorage->create([
|
||||
'aid' => $aidBrokerage,
|
||||
'transaction_date' => $dateIso8601,
|
||||
'symbol' => $symbolTid,
|
||||
'action' => $action,
|
||||
'type' => $type,
|
||||
'amount' => $data['amount'],
|
||||
'quantity' => $data['quantity'],
|
||||
'on_hand' => $data['quantity'],
|
||||
'commission' => (isset($data['commission']) && $data['commission'] !== NULL) ? $data['commission'] : 0,
|
||||
'fees' => (isset($data['fees']) && $data['fees'] !== NULL) ? $data['fees'] : 0,
|
||||
'notes' => (isset($data['notes'])) ? $data['notes'] : NULL,
|
||||
])->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_import\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\daftar_import\Exception\CsvDataException;
|
||||
|
||||
/**
|
||||
* Implements form for processing transfer transactions from a CSV file.
|
||||
* @package Drupal\daftar\Form
|
||||
*/
|
||||
class ImportCsvTransfersForm extends ImportCsvBaseForm {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $transferStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'daftar_import_csv_transfer_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function getRequiredKeys() {
|
||||
return [
|
||||
'id',
|
||||
'date',
|
||||
'amount',
|
||||
'action',
|
||||
'account',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
$form['#title'] = 'Import Transfers from CSV';
|
||||
$form['description']['#markup'] = $this->t('Import transactions such as deposits and withdrawals.');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$data = parent::submitForm($form, $form_state);
|
||||
|
||||
// End processing if no CSV data was retrieved.
|
||||
if (FALSE === $data) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentRow = 1; // Starts at 1 because first row is a header row.
|
||||
try {
|
||||
$this->transferStorage = \Drupal::entityTypeManager()->getStorage('transaction_transfer');
|
||||
$brokerageNodeId = $data['brokerage']->id();
|
||||
|
||||
foreach ($data['data'] as $row) {
|
||||
$currentRow++;
|
||||
$this->createTransfer($brokerageNodeId, $row);
|
||||
}
|
||||
}
|
||||
catch (CsvDataException $e) {
|
||||
$this->messenger->addError(
|
||||
'Data error on row ' . $currentRow . ': ' . $e->getMessage()
|
||||
);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// @TODO log to watchdog
|
||||
$this->messenger()->addError(
|
||||
"Import process stopped at row {$currentRow} due to internal errors."
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the bank account node ID of a given account alias.
|
||||
*
|
||||
* @param string $alias Alias for the bank account.
|
||||
* @param bool $create_account A boolean flag indicating whether to create
|
||||
* a new bank account when not found.
|
||||
*
|
||||
* @return int|null Returns the bank account node ID for a given alias; null otherwise.
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getBankNodeId($alias, $create_account = FALSE) {
|
||||
$nodeIds = $this->nodeStorage->getQuery()
|
||||
->condition('status', 1)
|
||||
->condition('type', 'account')
|
||||
->condition('uid', $this->currentUser()->id())
|
||||
->condition('title', $alias)
|
||||
->condition('field_account_type', 'ba')
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
|
||||
if (!empty($nodeIds)) {
|
||||
return reset($nodeIds);
|
||||
}
|
||||
|
||||
if ($create_account) {
|
||||
return $this->createBankAccount($alias);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a bank account for the user.
|
||||
*
|
||||
* @param string $alias Alias for the bank account.
|
||||
*
|
||||
* @return int|null Returns the bank account node ID when successfully created; null otherwise.
|
||||
*
|
||||
* @throws CsvDataException
|
||||
*/
|
||||
protected function createBankAccount($alias) {
|
||||
$accountFormState = new FormState();
|
||||
$accountForm = $this->accountForm->buildForm([], $accountFormState, 'bank');
|
||||
|
||||
$accountFormState->setValue('title', $alias);
|
||||
$accountFormState->setValue('type', 'bank');
|
||||
|
||||
$this->accountForm->validateForm($accountForm, $accountFormState);
|
||||
$accountFormErrors = (array) $accountFormState->getErrors();
|
||||
|
||||
if (!empty($accountFormErrors)) {
|
||||
throw new CsvDataException("Unable to create bank account: " . reset($accountFormErrors));
|
||||
}
|
||||
|
||||
$this->accountForm->submitForm($accountForm, $accountFormState);
|
||||
|
||||
$node = $accountFormState->get('account_node');
|
||||
if ($node) {
|
||||
return $node->id();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the transfer entity.
|
||||
*
|
||||
* @param int $aidBrokerage Node ID of the brokerage account.
|
||||
* @param array $data An associative array containing CSV row.
|
||||
*
|
||||
* @throws CsvDataException
|
||||
* @throws EntityStorageException
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function createTransfer($aidBrokerage, $data) {
|
||||
$dateIso8601 = $this->getIso8601DateString($data['date']);
|
||||
|
||||
$bankNodeId = $this->getBankNodeId($data['account'], TRUE);
|
||||
if (!$bankNodeId) {
|
||||
throw new \Exception("Unable to create bank account for alias {$data['account']}.");
|
||||
}
|
||||
|
||||
if (strtolower($data['action']) === 'deposit') {
|
||||
$aid = $aidBrokerage;
|
||||
$accountWithdrawn = $bankNodeId;
|
||||
}
|
||||
else {
|
||||
$aid = $bankNodeId;
|
||||
$accountWithdrawn = $aidBrokerage;
|
||||
}
|
||||
|
||||
$this->transferStorage->create([
|
||||
'aid' => $aid,
|
||||
'account_withdrawn' => $accountWithdrawn,
|
||||
'amount' => $data['amount'],
|
||||
'transaction_date' => $dateIso8601,
|
||||
])->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
name: Dafter Quote
|
||||
description: Provides quotes for a security, ETF or an option.
|
||||
core: 8.x
|
||||
type: module
|
||||
package: Daftar
|
||||
core_version_requirement: ^8 || ^9
|
||||
version: 1.0.0
|
||||
dependencies:
|
||||
- daftar_stock_api:daftar_stock_api
|
||||
@@ -0,0 +1,35 @@
|
||||
daftar_quote.options:
|
||||
path: '/quote/options'
|
||||
defaults:
|
||||
_title: 'Options Quote'
|
||||
_controller: '\Drupal\daftar_quote\Controller\OptionsController::quote'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
daftar_quote.v2.options.contracts:
|
||||
path: '/v2/option/contracts/{symbol}/{type}/{expiration_date}'
|
||||
defaults:
|
||||
_title: 'Options Quote'
|
||||
_controller: '\Drupal\daftar_quote\Controller\OptionsController::v2_contracts'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
symbol: '[A-Z]{1,5}'
|
||||
type: 'calls|puts'
|
||||
expiration_date: '\d{4}-\d{2}-\d{2}'
|
||||
|
||||
daftar_quote.v2.options.contract:
|
||||
path: '/v2/option/contracts/{contract_symbol}'
|
||||
defaults:
|
||||
_title: 'Option Contract'
|
||||
_controller: '\Drupal\daftar_quote\Controller\OptionsController::v2_contract'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
#contract_symbol: '[A-Z]{1,5}\d{6}(P|C)\d{8}'
|
||||
|
||||
daftar_quote.v2.options.parse_contract_symbol:
|
||||
path: '/v2/option/contracts/parse'
|
||||
defaults:
|
||||
_title: 'Option Contract Parse'
|
||||
_controller: '\Drupal\daftar_quote\Controller\OptionsController::v2_parse_contract_symbol'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\daftar_quote\Controller;
|
||||
|
||||
use DateTime;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\daftar_stock_api\Exception\StockApiException;
|
||||
use Drupal\daftar_stock_api\Service\YahooApiService;
|
||||
use Drupal\daftar_stock_api\Service\YahooParserService;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Provides callback methods used for launching modals with content form.
|
||||
* @package Drupal\daftar_content\Controller
|
||||
*/
|
||||
class OptionsController extends ControllerBase {
|
||||
const SETTINGS_KEY_OPTIONS_TOKEN = 'daftar_quote_options_token';
|
||||
const CONTRACT_SYMBOL_PATTERN = '/^([A-Z]{1,5})(\d{6})([PC])(\d{8})$/';
|
||||
const CONTRACT_SYMBOL_FD_PATTERN = '/^-?([A-Z]{1,5})(\d{6})([PC])(\d{1,4}([.]\d{1,2})?)$/';
|
||||
// Tran Date Contact Symbol QT PPS Total
|
||||
const CONTRACT_SYMBOL_FD_PATTERN2 = '/^(\d{2}\/\d{2}\/\d{4})[ ]+YOU (SOLD|BOUGHT) (OPENING|CLOSING) TRANSACTION[ ]+-?([A-Z]{1,5})(\d{6})([PC])(\d{1,4}[\.]?\d{0,2})[ ]+(-?\d{1,4})[ ]+\$(\d{1,4}[\.]?\d{0,4})[ ]+\$(-?\d+[,]?\d{0,3}[\.]?\d{0,2})[ ]+\$(\d{1,3}[\.]?\d{0,2})[ ]+\$(\d{1,3}[\.]?\d{0,2}).*$/';
|
||||
// Tran Date Contact Symbol QT PPS Total
|
||||
const CONTRACT_SYMBOL_FD_PATTERN2B = '/^(\d{2}\/\d{2}\/\d{4})YOU (SOLD|BOUGHT) (OPENING|CLOSING) TRANSACTION-?([A-Z]{1,5})(\d{6})([PC])(\d{1,4}[\.]?\d{0,2})(-?\d{1,4})\$(\d{1,4}[\.]?\d{0,4})\$(-?\d+[,]?\d{0,3}[\.]?\d{0,2})\$(\d{1,3}[\.]?\d{0,2})\$(\d{1,3}[\.]?\d{0,2}).*$/';
|
||||
const CONTRACT_SYMBOL_TDA_PATTERN = '/^(Sold|Bought) (\d+) ([A-Z]{1,5}) ([A-Z][a-z]{2} \d{1,2} \d{4}) (\d{1,4}([.]\d{1,2})?) (Call|Put) @ (\d{1,4}([.]\d{1,2})?)$/';
|
||||
/**
|
||||
* @var \Drupal\daftar_stock_api\Service\YahooParserService
|
||||
*/
|
||||
protected YahooParserService $yahooParser;
|
||||
|
||||
/**
|
||||
* @var \Drupal\daftar_stock_api\Service\YahooApiService
|
||||
*/
|
||||
protected YahooApiService $yahooApi;
|
||||
|
||||
/**
|
||||
* OptionsController constructor.
|
||||
*
|
||||
* @param YahooParserService $yahoo_parser_service
|
||||
* @param \Drupal\daftar_stock_api\Service\YahooApiService $yahoo_api_service
|
||||
*/
|
||||
public function __construct(YahooParserService $yahoo_parser_service, YahooApiService $yahoo_api_service) {
|
||||
$this->yahooParser = $yahoo_parser_service;
|
||||
$this->yahooApi = $yahoo_api_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container): OptionsController {
|
||||
return new static(
|
||||
$container->get('daftar.stock_api.yahoo_parser'),
|
||||
$container->get('daftar.stock_api.yahoo_api')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $symbol
|
||||
* @param string $type
|
||||
* @param string $expiration_date
|
||||
* @param Request $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function v2_contracts(string $symbol, string $type, string $expiration_date, Request $request): JsonResponse {
|
||||
$tokenCheck = $this->checkToken($request);
|
||||
if ($tokenCheck) {
|
||||
return $tokenCheck;
|
||||
}
|
||||
|
||||
$strike = $request->get('strike');
|
||||
return $this->v2_fetchContracts($symbol, $type, $expiration_date, $strike);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contract_symbol
|
||||
* @param Request $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function v2_contract(string $contract_symbol, Request $request): JsonResponse {
|
||||
$tokenCheck = $this->checkToken($request);
|
||||
if ($tokenCheck) {
|
||||
return $tokenCheck;
|
||||
}
|
||||
|
||||
if (preg_match(self::CONTRACT_SYMBOL_PATTERN, $contract_symbol, $matches)) {
|
||||
$date = DateTime::createFromFormat('ymd', $matches[2]);
|
||||
$type = ($matches[3] === 'P') ? 'puts' : 'calls';
|
||||
$strike = intval($matches[4])/1000;
|
||||
return $this->v2_fetchContracts($matches[1], $type, $date->format('Y-m-d'), $strike);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'error' => TRUE,
|
||||
'message' => $this->t('Invalid contract symbol.'),
|
||||
], Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contract_symbol
|
||||
* @param Request $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function v2_parse_contract_symbol(Request $request): JsonResponse {
|
||||
$tokenCheck = $this->checkToken($request);
|
||||
if ($tokenCheck) {
|
||||
return $tokenCheck;
|
||||
}
|
||||
|
||||
$parsed = [];
|
||||
$contractSymbol = $request->request->get('contract_symbol');
|
||||
//$contractSymbol = $request->get('contract_symbol');
|
||||
if (preg_match(self::CONTRACT_SYMBOL_PATTERN, $contractSymbol, $matches)) {
|
||||
$date = DateTime::createFromFormat('ymd', $matches[2]);
|
||||
$parsed = [
|
||||
'symbol' => $matches[1],
|
||||
'expiration_date' => $date->format('m/d/Y'),
|
||||
'type' => ($matches[3] === 'P') ? 'PUT' : 'CALL',
|
||||
'strike' => intval($matches[4])/1000,
|
||||
'account' => '',
|
||||
];
|
||||
}
|
||||
elseif (preg_match(self::CONTRACT_SYMBOL_FD_PATTERN, $contractSymbol, $matches)) {
|
||||
$date = DateTime::createFromFormat('ymd', $matches[2]);
|
||||
$parsed = [
|
||||
'symbol' => $matches[1],
|
||||
'expiration_date' => $date->format('m/d/Y'),
|
||||
'type' => ($matches[3] === 'P') ? 'PUT' : 'CALL',
|
||||
'strike' => $matches[4],
|
||||
'account' => 'FD',
|
||||
];
|
||||
}
|
||||
elseif (preg_match(self::CONTRACT_SYMBOL_FD_PATTERN2, $contractSymbol, $matches)
|
||||
|| preg_match(self::CONTRACT_SYMBOL_FD_PATTERN2B, $contractSymbol, $matches)) {
|
||||
$date = DateTime::createFromFormat('ymd', $matches[5]);
|
||||
$action = substr($matches[2], 0, 1) . '2';
|
||||
$action .= ($matches[3] === 'CLOSING') ? 'X' : 'O';
|
||||
|
||||
$parsed = [
|
||||
'tran_date' => $matches[1],
|
||||
'action' => $action,
|
||||
'symbol' => $matches[4],
|
||||
'expiration_date' => $date->format('m/d/Y'),
|
||||
'type' => ($matches[6] === 'P') ? 'PUT' : 'CALL',
|
||||
'strike' => $matches[7],
|
||||
'quantity' => $matches[8],
|
||||
'account' => 'FD2',
|
||||
'price' => $matches[9],
|
||||
'comm' => $matches[11],
|
||||
'fees' => $matches[12]
|
||||
];
|
||||
}
|
||||
elseif (preg_match(self::CONTRACT_SYMBOL_TDA_PATTERN, $contractSymbol, $matches)) {
|
||||
$date = DateTime::createFromFormat('M d Y', $matches[4]);
|
||||
$parsed = [
|
||||
'symbol' => $matches[3],
|
||||
'expiration_date' => $date->format('m/d/Y'),
|
||||
'type' => ($matches[7] === 'Put') ? 'PUT' : 'CALL',
|
||||
'strike' => $matches[5],
|
||||
'account' => 'TDA',
|
||||
'action' => ($matches[1] === 'Sold') ? 'S2O' : 'B2O',
|
||||
'quantity' => $matches[2],
|
||||
'price' => $matches[8],
|
||||
];
|
||||
}
|
||||
|
||||
if ($parsed) {
|
||||
$parsed['contract'] = NULL;
|
||||
try {
|
||||
$type = ($parsed['type'] == 'CALL') ? 'calls' : 'puts';
|
||||
$contractData = $this->yahooApi->getOptionContract(
|
||||
$parsed['symbol'], $parsed['expiration_date'], $type, $parsed['strike']
|
||||
);
|
||||
if (!$contractData['error']) {
|
||||
$parsed['contract'] = $contractData['contents'];
|
||||
}
|
||||
|
||||
} catch (StockApiException $e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'error' => FALSE,
|
||||
'message' => '',
|
||||
'contents' => $parsed,
|
||||
], Response::HTTP_OK);
|
||||
}
|
||||
else {
|
||||
return new JsonResponse([
|
||||
'error' => TRUE,
|
||||
'message' => $this->t('Invalid or missing contract symbol.'),
|
||||
'contract_symbol' => $contractSymbol,
|
||||
], Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function quote(Request $request): JsonResponse {
|
||||
$tokenCheck = $this->checkToken($request);
|
||||
if ($tokenCheck) {
|
||||
return $tokenCheck;
|
||||
}
|
||||
|
||||
$symbol = $request->get('symbol');
|
||||
if (!$symbol) {
|
||||
return new JsonResponse([
|
||||
'error' => TRUE,
|
||||
'message' => $this->t('Missing symbol.'),
|
||||
], Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$price = $this->yahooParser->getPrice($symbol);
|
||||
if ($price) {
|
||||
return new JsonResponse([
|
||||
'error' => FALSE,
|
||||
'message' => 'success',
|
||||
'price' => $price,
|
||||
], Response::HTTP_OK);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'error' => TRUE,
|
||||
'message' => $this->t('Unable to parse price.'),
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $symbol
|
||||
* @param string $type
|
||||
* @param string $expiration_date
|
||||
* @param $strike
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\JsonResponse
|
||||
*/
|
||||
private function v2_fetchContracts(string $symbol, string $type, string $expiration_date, $strike = ""): JsonResponse {
|
||||
if ($type == 'CALL') {
|
||||
$type == 'calls';
|
||||
}
|
||||
if ($type == 'PUT') {
|
||||
$type == 'puts';
|
||||
}
|
||||
|
||||
try {
|
||||
if ($strike) {
|
||||
$data = $this->yahooApi->getOptionContract($symbol, $expiration_date, $type, $strike);
|
||||
}
|
||||
else {
|
||||
$data = $this->yahooApi->getOptionContracts($symbol, $expiration_date, $type);
|
||||
}
|
||||
return new JsonResponse($data, Response::HTTP_OK);
|
||||
}
|
||||
catch (StockApiException $e) {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'error' => TRUE,
|
||||
'message' => $this->t('Error occurred while finding contract.'),
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
private function checkToken(Request $request): ?JsonResponse {
|
||||
$optionsToken = Settings::get(static::SETTINGS_KEY_OPTIONS_TOKEN, '');
|
||||
if (!$optionsToken) {
|
||||
return new JsonResponse([
|
||||
'error' => TRUE,
|
||||
'message' => $this->t('Token setup has not been completed.'),
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$receivedToken = $request->get('token');
|
||||
if ($receivedToken !== $optionsToken) {
|
||||
return new JsonResponse([
|
||||
'error' => TRUE,
|
||||
'message' => $this->t('Invalid token.'),
|
||||
], Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user