skilling.module 117 KB
Newer Older
mathieso's avatar
mathieso committed
1
<?php
2

3 4 5 6
/**
 * @file
 * The Skilling module file.
 */
7

mathieso's avatar
mathieso committed
8 9
/* @noinspection PhpInconsistentReturnPointsInspection */
/* @noinspection PhpUndefinedFieldInspection */
10

mathieso's avatar
mathieso committed
11
/* @noinspection PhpUnusedParameterInspection */
mathieso's avatar
mathieso committed
12

mathieso's avatar
mathieso committed
13
use Drupal\Component\Utility\Html;
mathieso's avatar
mathieso committed
14
use Drupal\Core\Entity\EntityInterface;
mathieso's avatar
mathieso committed
15
use Drupal\Core\Entity\EntityTypeInterface;
mathieso's avatar
mathieso committed
16
use Drupal\Core\Extension\ModuleHandlerInterface;
17
use Drupal\Core\Form\FormStateInterface;
18
use Drupal\Core\Link;
mathieso's avatar
mathieso committed
19
use Drupal\Core\Routing\RouteMatchInterface;
mathieso's avatar
mathieso committed
20
use Drupal\Core\Session\AccountProxy;
21
use Drupal\Core\Url;
mathieso's avatar
mathieso committed
22
use Drupal\editor\Entity\Editor;
23
use Drupal\node\Entity\Node;
mathieso's avatar
mathieso committed
24
use Drupal\node\NodeInterface;
25
use Drupal\paragraphs\Entity\Paragraph;
26
use Drupal\skilling\Access\SkillingAccessChecker;
27
use Drupal\skilling\Exception\SkillingInvalidValueException;
mathieso's avatar
mathieso committed
28
use Drupal\skilling\Help\HelpConstants;
29
use Drupal\skilling\Plugin\SkillingCustomTag\ExerciseTag;
30
use Drupal\skilling\SkillingConstants;
mathieso's avatar
mathieso committed
31
use Drupal\skilling\SkillingCurrentUser;
32
use Drupal\skilling\SkillingParser\SkillingParser;
mathieso's avatar
mathieso committed
33
use Drupal\skilling\Utilities;
mathieso's avatar
mathieso committed
34
use Drupal\skilling_history\SkillingHistoryConstants;
mathieso's avatar
mathieso committed
35
use Drupal\user\Entity\User;
mathieso's avatar
mathieso committed
36
use Drupal\views\Plugin\views\cache\CachePluginBase;
37
//use Drupal\views\Render\ViewsRenderPipelineMarkup;
mathieso's avatar
mathieso committed
38
use Drupal\views\ViewExecutable;
mathieso's avatar
mathieso committed
39
use Symfony\Component\HttpFoundation\RedirectResponse;
40
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
mathieso's avatar
mathieso committed
41
use Drupal\skilling\Exception\SkillingUnknownValueException;
42
use Drupal\Core\Render\Markup;
43
use Drupal\Core\Config\ConfigFactoryInterface;
mathieso's avatar
mathieso committed
44

45 46
// Access control code moved here, to make it easier to think about.
require_once 'src/Access/access-control.inc';
47

mathieso's avatar
mathieso committed
48
// Checklist API code.
49
require_once 'skilling.checklist.inc';
mathieso's avatar
mathieso committed
50

mathieso's avatar
mathieso committed
51 52 53 54
/**
 * Implements hook_help().
 */
function skilling_help($route_name, RouteMatchInterface $route_match) {
mathieso's avatar
mathieso committed
55
  /** @var \Drupal\skilling\Help\Help $helpService */
mathieso's avatar
mathieso committed
56
  $helpService = Drupal::service('skilling.help');
mathieso's avatar
mathieso committed
57
  $docStartUrl = $helpService->getDocUrlAsString(HelpConstants::USER_DOC_START);
mathieso's avatar
mathieso committed
58 59 60 61 62 63
  switch ($route_name) {
    // Main module help for the skilling module.
    case 'help.page.skilling':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Create and run skill courses.') . '</p>';
mathieso's avatar
mathieso committed
64
      $output .= '<p>' . t('See the <a target="_blank" href=":docs">documentation</a> for help.', [':docs' => $docStartUrl]) . '</p>';
mathieso's avatar
mathieso committed
65 66 67 68
      return $output;
  }
}

69 70 71 72
/**
 * Implements hook_page_attachments().
 */
function skilling_page_attachments(array &$attachments) {
73
  // Fixes for admin theme Seven.
74
  $attachments['#attached']['library'][] = 'skilling/seven-extensions';
75
  $attachments['#attached']['library'][] = 'skilling/swal';
mathieso's avatar
mathieso committed
76
  // Utility functions, like finding the base URL for the site.
77
  $attachments['#attached']['library'][] = 'skilling/skilling-utils';
mathieso's avatar
mathieso committed
78
  // Styles for display.
mathieso's avatar
mathieso committed
79
  $attachments['#attached']['library'][] = 'skilling/display';
80
  // Modifications to CK.
81
  //  $attachments['#attached']['library'][] = 'skilling/ck-mods';
mathieso's avatar
mathieso committed
82 83
  // Book nav repeater.
  $attachments['#attached']['library'][] = 'skilling/book-nav-repeat';
84
  // Send user id and roles to JS.
mathieso's avatar
mathieso committed
85
  /** @var Drupal\skilling\SkillingCurrentUser $currentUser */
mathieso's avatar
mathieso committed
86
  $currentUser = Drupal::service('skilling.skilling_current_user');
87 88 89
  $attachments['#attached']['drupalSettings']['currentUserId'] = $currentUser->id();
  $roles = $currentUser->id() ? $currentUser->getRoles() : [];
  $attachments['#attached']['drupalSettings']['currentUserRoles'] = $roles;
mathieso's avatar
mathieso committed
90
  $attachments['#attached']['drupalSettings']['currentUserName'] = $currentUser->getAccountName();
91 92 93 94
  // Counts of various node types, to customize Lists menu.
  $attachments['#attached']['drupalSettings']['contentTypeCounts']
    = _skilling_content_types_counts_for_lists_menu();
  $attachments['#attached']['library'][] = 'skilling/adjust-list-links';
mathieso's avatar
mathieso committed
95 96 97
  if ($currentUser->isAuthenticated()) {
    // Add notice count display.
    /** @var \Drupal\skilling\Notice $noticeService */
mathieso's avatar
mathieso committed
98
    $noticeService = Drupal::service('skilling.notice');
mathieso's avatar
mathieso committed
99 100 101 102 103 104 105
    $countUnreadNotices = $noticeService->getUnreadNoticeCountCurrentUser();
    if ($countUnreadNotices > 0) {
      $attachments['#attached']['drupalSettings']['countUnreadNotices']
        = $countUnreadNotices;
      $attachments['#attached']['library'][] = 'skilling/unread-notice-count';
    }
  }
106
  // Completion score code for students.
mathieso's avatar
mathieso committed
107 108 109
  if ($currentUser->isStudent()) {
    // Load the completion score for the current enrollment.
    /** @var \Drupal\skilling\CompletionScore $completionScoreService */
mathieso's avatar
mathieso committed
110
    $completionScoreService = Drupal::service('skilling.completion_score');
111
    $completionScore = $completionScoreService->getCompletionScoreForCurrentEnrollment();
112 113
    $explanation = '';
    $fileName = '';
114
    if (!is_null($completionScore)) {
115
      list($fileName, $explanation) = $completionScoreService->getCompletionScoreInterpretation($completionScore);
116
    }
mathieso's avatar
mathieso committed
117
    /** @var Drupal\skilling\Utilities $utilities */
mathieso's avatar
mathieso committed
118
    $utilities = Drupal::service('skilling.utilities');
mathieso's avatar
mathieso committed
119 120
    $imagePath = $utilities->getModuleUrlPath()
      . SkillingConstants::COMPLETION_SCORE_SMILIES_PATH
121
      . $fileName;
mathieso's avatar
mathieso committed
122 123 124
    $attachments['#attached']['library'][] = 'skilling/completion-score';
    $attachments['#attached']['drupalSettings']['completionScoreFileName']
      = $imagePath;
125
    $attachments['#attached']['drupalSettings']['completionScoreText'] = $explanation;
mathieso's avatar
mathieso committed
126
  }
mathieso's avatar
mathieso committed
127 128 129
  if (!$currentUser->isAnonymous()) {
    // Add user suggestion code.
    $attachments['#attached']['library'][] = 'skilling/user-suggestion';
mathieso's avatar
mathieso committed
130 131
    $ajaxSecurityService = Drupal::service('skilling.ajax_security');
    $session = Drupal::service('session');
mathieso's avatar
mathieso committed
132 133
    $attachments['#attached']['drupalSettings']['csrfToken'] = $ajaxSecurityService->getCsrfToken();
    $attachments['#attached']['drupalSettings']['sessionId'] = $session->getId();
mathieso's avatar
mathieso committed
134
  }
mathieso's avatar
mathieso committed
135 136
  // Changes to the UI.
  $attachments['#attached']['library'][] = 'skilling/ui-munge';
137 138
}

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

function _skilling_content_types_counts_for_lists_menu() {
  /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
  $entityTypeManager = Drupal::service('entity_type.manager');
  $typesToCount = [
    SkillingConstants::LESSON_CONTENT_TYPE,
    SkillingConstants::EXERCISE_CONTENT_TYPE,
    SkillingConstants::BADGE_CONTENT_TYPE,
    SkillingConstants::PATTERN_CONTENT_TYPE,
    SkillingConstants::PRINCIPLE_CONTENT_TYPE,
    SkillingConstants::MODEL_CONTENT_TYPE,
  ];
  $result = [];
  $storage = $entityTypeManager->getStorage('node');
  foreach ($typesToCount as $type) {
    $count = $storage->getQuery()
      ->condition('type', $type)
      ->condition('status', TRUE)
      ->count()
      ->execute();
    $result[$type] = (integer)$count;
  }
  return $result;
}

mathieso's avatar
mathieso committed
164
/**
mathieso's avatar
mathieso committed
165
 * Implements hook_editor_js_settings_alter().
mathieso's avatar
mathieso committed
166 167
 */
function skilling_editor_js_settings_alter(array &$settings) {
mathieso's avatar
mathieso committed
168
  if (empty($settings['editor']['formats']['skilling'])) {
mathieso's avatar
mathieso committed
169 170 171
    return;
  }
  $skillingSettings = &$settings['editor']['formats']['skilling']['editorSettings'];
mathieso's avatar
mathieso committed
172
  // Use brs for enter key.
mathieso's avatar
mathieso committed
173
  $skillingSettings['enterMode'] = 2;
mathieso's avatar
mathieso committed
174
  // Use brs for shift+enter key.
mathieso's avatar
mathieso committed
175
  $skillingSettings['shiftEnterMode'] = 2;
mathieso's avatar
mathieso committed
176
  // Tab key is four spaces.
mathieso's avatar
mathieso committed
177
  $skillingSettings['tabSpaces'] = 4;
mathieso's avatar
mathieso committed
178
  // Don't encode entities?
mathieso's avatar
mathieso committed
179
  $skillingSettings['entities'] = FALSE;
mathieso's avatar
mathieso committed
180
  // Force pasting to use plain text.
mathieso's avatar
mathieso committed
181
  $skillingSettings['forcePasteAsPlainText'] = TRUE;
mathieso's avatar
mathieso committed
182 183
  // Remove plugins that implement context menu.
  // See https://www.drupal.org/project/ckeditor_browser_context_menu
mathieso's avatar
mathieso committed
184
  $skillingSettings['removePlugins'] = 'contextmenu,tabletools,tableresize,elementspath';
mathieso's avatar
mathieso committed
185
  $skillingSettings['disableNativeSpellChecker'] = FALSE;
mathieso's avatar
mathieso committed
186 187 188 189 190 191 192
  // Change height of the ckeditor wysiwyg editor.
  if (isset($settings['editor']['formats']['skilling'])) {
    $settings['editor']['formats']['skilling']['editorSettings']['height'] = '400';
  }
  if (isset($settings['editor']['formats']['full_html'])) {
    $settings['editor']['formats']['full_html']['editorSettings']['height'] = '400';
  }
mathieso's avatar
mathieso committed
193 194
}

mathieso's avatar
mathieso committed
195 196 197
/**
 * @param array $css
 * @param \Drupal\editor\Entity\Editor $editor
198
 *
mathieso's avatar
mathieso committed
199 200
 * @noinspection PhpUnusedParameterInspection
 */
mathieso's avatar
mathieso committed
201
function skilling_ckeditor_css_alter(array &$css, Editor $editor) {
202 203
  if ($editor->getFilterFormat()
      ->id() === SkillingConstants::SKILLING_TEXT_FORMAT) {
204
    // Add CSS to change CKEditor to monospace font.
205
    $css[] = drupal_get_path('module', 'skilling') . '/css/ckeditor.css';
206
  }
mathieso's avatar
mathieso committed
207 208
}

209

210 211 212 213 214 215 216 217 218
/**
 * Implements hook_form_alter().
 */
function skilling_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  if ($form_id == 'node_multiple_choice_question_form') {
    $form['#attached']['library'][] = 'skilling_theme/seven-admin-styling';
  }
}

219 220 221
/**
 * Implements hook_form_FORM_alter().
 */
mathieso's avatar
mathieso committed
222 223 224 225 226 227 228 229 230 231 232
function skilling_form_node_exercise_edit_form_alter(&$form, FormStateInterface $form_state) {
  skilling_alter_exercise_edit_form($form, $form_state);
}

function skilling_form_node_exercise_form_alter(&$form, FormStateInterface $form_state) {
  skilling_alter_exercise_edit_form($form, $form_state);
}

function skilling_alter_exercise_edit_form(&$form, FormStateInterface $form_state) {
  // Add a link on exercise edit form that opens rubric item categories view in
  // a new tab.
233 234 235 236 237 238 239 240
  $linkLabel = t('rubric items by category');
  $linkOptions = ['attributes' => ['target' => '_blank']];
  $linkRICategoriesView = Link::createFromRoute(
    $linkLabel,
    'skilling.admin.exercises.list_rubric_item_categories',
    [],
    $linkOptions
  )->toString();
241
  $rendered = t('Show @categories.', ['@categories' => $linkRICategoriesView])->render();
242
  $form['field_rubric_items']['widget']['#description'] .= ' ' . $rendered;
mathieso's avatar
mathieso committed
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
  // Put notes field in collapsed container.
  $temp = $form['field_notes'];
  $form['field_notes'] = [
    '#type' => 'details',
    '#title' => t('Notes'),
    '#open' => FALSE,
    'inner' => $temp,
  ];
  // Put where referenced in collapsed container.
  if (isset($form['field_where_referenced'])) {
    $temp = $form['field_where_referenced'];
    $form['field_where_referenced'] = [
      '#type' => 'details',
      '#title' => t('Where referenced'),
      '#open' => FALSE,
      'inner' => $temp,
    ];
  }
mathieso's avatar
mathieso committed
261 262 263 264 265
  // Hide the standard rubric item interface.
  $form['field_rubric_items']['#access'] = false;
  // Add rubric item GUI.
//  $form['#attached']['library'][] = 'skilling/rubric-item-chooser';
  $form['rubric_item_chooser'] = [
mathieso's avatar
mathieso committed
266
    '#markup' => '<div id="app"></div>',
267
    '#weight' => 5,
mathieso's avatar
mathieso committed
268 269 270
    '#attached' => [
      'library' => [
        'skilling/rubric-item-chooser',
271
      ]
mathieso's avatar
mathieso committed
272 273
    ],
  ];
274 275
  $form['rubric_item_chooser_return'] = array(
    '#type' => 'hidden',
276
    '#value' => "nothing",
277 278 279 280
  );
  if (!isset($form['actions']['submit']['#submit'])) {
    $form['actions']['submit']['#submit'] = [];
  }
281
  $form['actions']['submit']['#submit'][] = 'skilling_exercise_form_submit_after_save';
282 283
}

284 285 286 287 288 289 290 291 292 293 294
/**
 * Run after exercise edit form is saved.
 *
 * @param $form
 * @param \Drupal\Core\Form\FormStateInterface $formState
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 * @throws \Drupal\skilling\Exception\SkillingInvalidValueException
 */
295
function skilling_exercise_form_submit_after_save(&$form, FormStateInterface $formState) {
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
  $rubricItemGuiJson = $formState->getUserInput()['rubric_item_chooser_return'];
  // If Save button pressed before GUI initialized, "nothing" will be returned.
  if ($rubricItemGuiJson !== 'nothing') {
    _skilling_save_rubric_item_gui_data($formState, $rubricItemGuiJson);
  }
}

/**
 * Save data from the rubric item GUI on the exercise form.
 *
 * @param \Drupal\Core\Form\FormStateInterface $formState
 * @param $rubricItemGuiJson
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 * @throws \Drupal\skilling\Exception\SkillingInvalidValueException
 */
function _skilling_save_rubric_item_gui_data(FormStateInterface $formState, $rubricItemGuiJson) {
315 316 317 318 319 320 321 322 323
  /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
  $entityTypeManager = Drupal::service('entity_type.manager');
  $rubricItemGuiReturn = json_decode($rubricItemGuiJson);
  $guiRubricItems = $rubricItemGuiReturn->rubricItems;
  $guiResponseOptions = $rubricItemGuiReturn->responseOptions;
  $guiRubricItemOrder = $rubricItemGuiReturn->rubricItemOrder;
  // Array mapping new response options to their new nids.
  // Index is the GUI's temp id, value is the real nid.
  $newResponseOptionsMapping = [];
324 325
  // Remember all response options, so can get their revision ids.
  $allResponseOptions = [];
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
  foreach ($guiResponseOptions as $guiResponseOption) {
    // Get the GUI's data for the new response option.
    $guiResponseOptionId = $guiResponseOption->responseOptionId;
    $guiResponseOptionResponseText = $guiResponseOption->name;
    $guiResponseOptionNotes = $guiResponseOption->notes;
    $guiResponseOptionCompletes = $guiResponseOption->completes;
    $guiResponseOptionAppliesToExerciseIds = $guiResponseOption->appliesToExerciseIds;
    // Make array for saving exercise refs.
    $exerciseRefs = [];
    foreach ($guiResponseOptionAppliesToExerciseIds as $guiResponseOptionAppliesToExerciseId) {
      $newRef = [];
      $newRef['target_id'] = $guiResponseOptionAppliesToExerciseId;
      $exerciseRefs[] = $newRef;
    }
    if (!is_numeric($guiResponseOptionId)){
      // Is it a new response option?
      if (substr($guiResponseOptionId, 0, 3) !== 'new') {
        throw new SkillingInvalidValueException(
          'Bad new returnedResponseOptionId: ' . $guiResponseOptionId);
      }
      // Create new paragraph.
      $responseParagraph = Paragraph::create(['type' => 'rubric_item_response']);
      $responseParagraph->set(
        SkillingConstants::FIELD_RESPONSE_TO_STUDENT,
        [
          'value' => $guiResponseOptionResponseText,
          'format' => SkillingConstants::STRIPPED_TEXT_FORMAT,
        ]
      );
      $responseParagraph->set(
        SkillingConstants::FIELD_COMPLETES_RUBRIC_ITEM,
        $guiResponseOptionCompletes
      );
      $responseParagraph->set(
        SkillingConstants::FIELD_NOTES, [
          'value' => $guiResponseOptionNotes,
          'format' => SkillingConstants::SKILLING_TEXT_FORMAT,
        ]
      );
      $responseParagraph->set(
        SkillingConstants::FIELD_EXERCISES,
        $exerciseRefs
      );
      $responseParagraph->isNew();
      $responseParagraph->save();
      // Remember what the real id of GUI's temp id is.
      $newResponseOptionsMapping[$guiResponseOptionId] = $responseParagraph->id();
373
      $allResponseOptions[$responseParagraph->id()] = $responseParagraph;
374 375 376 377 378 379 380
    }
    else {
      // Not a new response option.
      // Load the current one.
      /** @var \Drupal\paragraphs\ParagraphInterface $responseParagraph */
      $responseParagraph = $entityTypeManager->getStorage('paragraph')
        ->load($guiResponseOptionId);
mathieso's avatar
mathieso committed
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
      if ($responseParagraph) {
        if ($responseParagraph->bundle() != SkillingConstants::RUBRIC_ITEM_RESPONSE_PARAGRAPH_TYPE) {
          throw new SkillingInvalidValueException(
            'Not right para type $returnedResponseOptionId: ' . $guiResponseOptionId);
        }
        $responseParagraph->set(
          SkillingConstants::FIELD_RESPONSE_TO_STUDENT,
          [
            'value' => $guiResponseOptionResponseText,
            'format' => SkillingConstants::STRIPPED_TEXT_FORMAT,
          ]
        );
        $responseParagraph->set(
          SkillingConstants::FIELD_COMPLETES_RUBRIC_ITEM,
          $guiResponseOptionCompletes
        );
        $responseParagraph->set(
          SkillingConstants::FIELD_NOTES, [
            'value' => $guiResponseOptionNotes,
            'format' => SkillingConstants::SKILLING_TEXT_FORMAT,
          ]
        );
        $responseParagraph->set(
          SkillingConstants::FIELD_EXERCISES,
          $exerciseRefs
        );
        $responseParagraph->save();
        $allResponseOptions[$responseParagraph->id()] = $responseParagraph;
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
      }
    }
  }

  // Make the new rubric items.
  // Array mapping new rubric items to their new nids.
  // Index is the GUI's temp id, value is the real nid.
  $newRubricItemsMapping = [];
  $newRubricItems = [];
  foreach ($guiRubricItems as $guiRubricItem) {
    // Grab the fields.
    $guiRubricItemId = $guiRubricItem->rubricItemId;
    $guiRubricItemName = $guiRubricItem->name;
    $guiRubricItemNotes = $guiRubricItem->notes;
    $guiRubricItemCategories = $guiRubricItem->categories;
    $guiRubricItemResponseOptionIds = $guiRubricItem->responseOptionIds;
    // Make array for saving taxonomy terms.
    $taxTerms = [];
    foreach ($guiRubricItemCategories as $guiRubricItemCategoryTermId) {
      $newRef = [];
      $newRef['target_id'] = $guiRubricItemCategoryTermId;
      $taxTerms[] = $newRef;
    }
    // Is this a new rubric item?
    if (!is_numeric($guiRubricItemId)) {
      // Might be.
      if (substr($guiRubricItemId, 0, 3) !== 'new') {
        throw new SkillingInvalidValueException(
          'Bad new $guiRubricItemId: ' . $guiRubricItemId);
      }
      // Yes, it is.
      // Create a new rubric item.
      // Rubric item responses list not set yet.
442
      /** @var Drupal\node\NodeInterface $node */
443 444 445 446 447 448 449 450 451 452
      $node = Node::create([
        'type'        => SkillingConstants::RUBRIC_ITEM_CONTENT_TYPE,
        'title'       => $guiRubricItemName,
        SkillingConstants::FIELD_NOTES => [
          'value' => $guiRubricItemNotes,
          'format' => SkillingConstants::STRIPPED_TEXT_FORMAT,
        ],
        SkillingConstants::FIELD_CATEGORIES => $taxTerms
      ]);
      // Remember the mapping between GUI id and actual.
453
      $node->save();
454
      $newRubricItemsMapping[$guiRubricItemId] = $node->id();
455 456 457 458 459 460
      $newRubricItems[] = $node;
    }
    else {
      // An existing rubric item.
      $node = $entityTypeManager->getStorage('node')
        ->load($guiRubricItemId);
mathieso's avatar
mathieso committed
461 462 463 464 465 466 467 468 469 470 471 472
      if ($node) {
        if ($node->bundle() != SkillingConstants::RUBRIC_ITEM_CONTENT_TYPE) {
          throw new SkillingInvalidValueException(
            'Wrong content type: $guiRubricItemId: ' . $guiRubricItemId);
        }
        // Set the fields with the new values.
        $node->set('title', $guiRubricItemName);
        $node->set(SkillingConstants::FIELD_NOTES, [
          'value' => $guiRubricItemNotes,
          'format' => SkillingConstants::STRIPPED_TEXT_FORMAT
        ]);
        $node->set(SkillingConstants::FIELD_CATEGORIES, $taxTerms);
473 474
      }
    }
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497

    // Set the refs to the right response options.
    // Erase existing refs.
    $numOptions = $node->get(SkillingConstants::FIELD_RUBRIC_ITEM_RESPONSES)->count();
    for ($i = 0; $i < $numOptions; $i++) {
      $node->get(SkillingConstants::FIELD_RUBRIC_ITEM_RESPONSES)->removeItem(0);
    }
    $newFieldValue = [];
    for ($index = 0; $index < count($guiRubricItemResponseOptionIds); $index++) {
      $id = $guiRubricItemResponseOptionIds[$index];
      // A new option?
      if (substr($id, 0, 3) == 'new') {
        // Look up the real id.
        $id = $newResponseOptionsMapping[$id];
      }
      $revisionId = $allResponseOptions[$id]->getRevisionId();
      $newFieldValue[] = [
        'target_id' => $id,
        'target_revision_id' => $revisionId,
      ];
    }
    $node->field_rubric_item_responses = $newFieldValue;
    $node->save();
498 499 500 501 502 503 504 505
  } // End for each rubric item.

  // Load the exercise.
  $nodeForm = $formState->getBuildInfo()['callback_object'];
  $exerciseId = (integer)$nodeForm->getEntity()->id();
  /** @var Drupal\node\NodeInterface $exercise */
  $exercise = $entityTypeManager->getStorage('node')
    ->load($exerciseId);
mathieso's avatar
mathieso committed
506 507 508 509 510
  if ($exercise) {
    // Erase the existing values.
    $numItems = $exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->count();
    for ($i = 0; $i < $numItems; $i++) {
      $exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->removeItem(0);
511
    }
mathieso's avatar
mathieso committed
512 513 514 515 516 517 518 519 520 521 522
    // Make array with new ids in order.
    $rubricItemIdOrder = [];
    foreach ($guiRubricItemOrder as $rubricItemOrderElement) {
      $order = $rubricItemOrderElement->index;
      $id = $rubricItemOrderElement->rubricItemId;
      // A new RI?
      if (substr($id, 0, 3) == 'new') {
        // Look up the real id.
        $id = $newRubricItemsMapping[$id];
      }
      $rubricItemIdOrder[$order] = $id;
523
    }
mathieso's avatar
mathieso committed
524 525 526 527 528 529 530 531 532 533 534
    // Apply the new order.
    for ($index = 0; $index < count($rubricItemIdOrder); $index++) {
      $id = $rubricItemIdOrder[$index];
      if ($index === 0) {
        $exercise->set(SkillingConstants::FIELD_RUBRIC_ITEMS, $id);
      }
      else {
        $exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->appendItem([
          'target_id' => $id,
        ]);
      }
535
    }
mathieso's avatar
mathieso committed
536
    $exercise->save();
537
  }
538
}
539

mathieso's avatar
mathieso committed
540 541 542 543 544 545 546 547 548 549 550 551 552 553
/**
 * Implements hook_inline_entity_form_entity_form_alter().
 */
function skilling_inline_entity_form_entity_form_alter(&$entity_form, &$form_state) {
  if ($entity_form['#entity_type'] === 'node') {
    if ($entity_form['#bundle'] === 'rubric_item') {
      // Put notes field in collapsed container.
      $temp = $entity_form['field_notes'];
      $entity_form['field_notes'] = [
        '#type' => 'details',
        '#title' => t('Notes'),
        '#open' => FALSE,
        'inner' => $temp,
      ];
mathieso's avatar
mathieso committed
554 555 556 557 558 559 560
      if (isset($entity_form['field_rubric_item_responses'])) {
        // Wrap fields in collapsed detail containers.
        $widget = $entity_form['field_rubric_item_responses']['widget'];
        for ($responseCounter = 0; $responseCounter <= $widget['#max_delta']; $responseCounter++) {
          $subform = $widget[$responseCounter]['subform'];
          $temp = $subform['field_notes'];
          $entity_form['field_rubric_item_responses']['widget'][$responseCounter]['subform']['field_notes'] = [
mathieso's avatar
mathieso committed
561
            '#type' => 'details',
mathieso's avatar
mathieso committed
562
            '#title' => t('Notes'),
mathieso's avatar
mathieso committed
563 564 565
            '#open' => FALSE,
            'inner' => $temp,
          ];
566
          //        $entity_form['field_rubric_item_responses']['widget'][$responseCounter]['subform']['field_response_to_student']['widget'][0]['#rows'] = 3;
mathieso's avatar
mathieso committed
567 568 569 570 571 572 573 574 575
          if (isset($subform['field_exercises'])) {
            $temp = $subform['field_exercises'];
            $entity_form['field_rubric_item_responses']['widget'][$responseCounter]['subform']['field_exercises'] = [
              '#type' => 'details',
              '#title' => t('Exercises'),
              '#open' => FALSE,
              'inner' => $temp,
            ];
          }
mathieso's avatar
mathieso committed
576 577 578 579 580 581 582
        }
      }
    }

  }
}

mathieso's avatar
mathieso committed
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656

/**
 * Implements hook_form_FORM_alter().
 */
function skilling_form_node_fill_in_the_blank_edit_form_alter(&$form, FormStateInterface $form_state) {
  skilling_alter_fib_edit_form($form, $form_state);
}

function skilling_form_node_fill_in_the_blank_form_alter(&$form, FormStateInterface $form_state) {
  skilling_alter_fib_edit_form($form, $form_state);
}

function skilling_alter_fib_edit_form(&$form, FormStateInterface $form_state) {
  // Put notes field in collapsed container.
  $temp = $form['field_notes'];
  $form['field_notes'] = [
    '#type' => 'details',
    '#title' => t('Notes'),
    '#open' => FALSE,
    'inner' => $temp,
  ];
  // Put where referenced in collapsed container.
  if (isset($form['field_where_referenced'])) {
    $temp = $form['field_where_referenced'];
    $form['field_where_referenced'] = [
      '#type' => 'details',
      '#title' => t('Where referenced'),
      '#open' => FALSE,
      'inner' => $temp,
    ];
  }
  if (isset($form['field_fib_responses'])) {
    // Wrap fields in collapsed detail containers.
    $widget = $form['field_fib_responses']['widget'];
    $maxIndexResponses = $widget['#max_delta'];
    for ($responseCounter = 0; $responseCounter <= $maxIndexResponses; $responseCounter++) {
      $subform = $widget[$responseCounter]['subform'];
      $temp = $subform['field_notes'];
      $form['field_fib_responses']['widget'][$responseCounter]['subform']['field_notes'] = [
        '#type' => 'details',
        '#title' => t('Notes'),
        '#open' => FALSE,
        'inner' => $temp,
      ];
      // Combine fields for numeric into one collapsed field.
      $tempLow = $subform['field_lowest_number'];
      unset($form['field_fib_responses']['widget'][$responseCounter]['subform']['field_lowest_number']);
      $tempHigh = $subform['field_highest_number'];
      unset($form['field_fib_responses']['widget'][$responseCounter]['subform']['field_highest_number']);
      $form['field_fib_responses']['widget'][$responseCounter]['subform']['numeric_fields'] = [
        '#type' => 'details',
        '#title' => t('Numeric options'),
        '#open' => FALSE,
        'low' => $tempLow,
        'high' => $tempHigh,
      ];
      // Combine fields for text into one collapsed field.
      $tempMatchTexts = $subform['field_matching_text_responses'];
      unset($form['field_fib_responses']['widget'][$responseCounter]['subform']['field_matching_text_responses']);
      $tempCaseSensitive = $subform['field_case_sensitive'];
      unset($form['field_fib_responses']['widget'][$responseCounter]['subform']['field_case_sensitive']);
      $form['field_fib_responses']['widget'][$responseCounter]['subform']['text_fields'] = [
        '#type' => 'details',
        '#title' => t('Text options'),
        '#open' => FALSE,
        'matches' => $tempMatchTexts,
        'case' => $tempCaseSensitive,
      ];
    }
  }

}


657
function skilling_form_user_login_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
mathieso's avatar
mathieso committed
658 659
  // Focus to username field when form first displays.
//  $form['#attached']['library'][] = 'skilling/username-focus';
660
  // Prevent login form caching.
mathieso's avatar
mathieso committed
661
  Drupal::service('page_cache_kill_switch')->trigger();
662 663 664 665 666 667 668
  $referer = $_SERVER["HTTP_REFERER"];
  if (stripos($referer, 'login') === FALSE) {
    // Login is not in the referer.
    user_cookie_save(['skillingLoginReferer' => $_SERVER["HTTP_REFERER"]]);
  }
}

669 670 671 672 673 674 675 676 677 678 679 680
/**
 * Implements hook_form_FORM_alter().
 */
function skilling_form_user_pass_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  // Add message to password reset form, that email may go to spam folder.
  $form['check_spam'] = [
    '#prefix' => '<p>',
    '#suffix' => '</p>',
    '#markup' => t('The message may go to your spam folder.'),
  ];
}

mathieso's avatar
mathieso committed
681
/**
mathieso's avatar
mathieso committed
682
 * Implements hook_form_FORM_alter().
683
 *
mathieso's avatar
mathieso committed
684 685 686 687
 * @param $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 * @param $form_id
 *
688
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
689
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
mathieso's avatar
mathieso committed
690 691
 * @throws \Drupal\skilling\Exception\SkillingException
 * @throws \Drupal\skilling\Exception\SkillingNotFoundException
mathieso's avatar
mathieso committed
692
 * @throws \Drupal\skilling\Exception\SkillingUnknownValueException
mathieso's avatar
mathieso committed
693
 */
694
function skilling_form_node_submission_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
mathieso's avatar
mathieso committed
695
  skilling_change_submission_form($form);
696 697 698 699 700 701 702 703
}

/**
 * @param $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 * @param $form_id
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
704
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
mathieso's avatar
mathieso committed
705 706
 * @throws \Drupal\skilling\Exception\SkillingException
 * @throws \Drupal\skilling\Exception\SkillingNotFoundException
mathieso's avatar
mathieso committed
707
 * @throws \Drupal\skilling\Exception\SkillingUnknownValueException
708 709
 */
function skilling_form_node_submission_edit_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
mathieso's avatar
mathieso committed
710
  skilling_change_submission_form($form);
711 712 713 714 715 716
}

/**
 * @param $form
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
mathieso's avatar
mathieso committed
717 718
 * @throws \Drupal\skilling\Exception\SkillingException
 * @throws \Drupal\skilling\Exception\SkillingNotFoundException
mathieso's avatar
mathieso committed
719
 * @throws \Drupal\skilling\Exception\SkillingUnknownValueException
mathieso's avatar
mathieso committed
720
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
721
 */
mathieso's avatar
mathieso committed
722
function skilling_change_submission_form(&$form) {
mathieso's avatar
mathieso committed
723
  /** @var \Drupal\skilling\Submissions $submissionsService */
mathieso's avatar
mathieso committed
724
  $submissionsService = Drupal::service('skilling.submissions');
725
  $submissionsService->changeFloatingSubmissionForm($form);
mathieso's avatar
mathieso committed
726 727 728 729 730 731
  if (isset($form['field_solution'])) {
    $form['field_solution']['widget']['#after_build'][] = '_skilling_remove_textarea_help';
  }
  if (isset($form['field_difficulty_reasons'])) {
    $form['field_difficulty_reasons']['widget']['#after_build'][] = '_skilling_remove_textarea_help';
  }
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
  $form['#validate'][] = 'skilling_floating_submission_form_validate';
}

/**
 * Validate the floating submission form.
 *
 * @param array $form
 *   The form.
 * @param \Drupal\Core\Form\FormStateInterface $formState
 *   The form's state.
 */
function skilling_floating_submission_form_validate(array $form, FormStateInterface $formState) {
  $difficultyGiven = $formState->getValue('difficulty_rating_given')[0]['value'];
  if ($difficultyGiven === 'no') {
    $formState->setErrorByName('field_difficulty', t('Please say how difficult the exercise was.'));
  }
748 749
  $submissionSolution = $formState->getValue('field_solution')[0]['value'];
  if ($submissionSolution) {
mathieso's avatar
mathieso committed
750 751
    // Filter.
    /** @var \Drupal\skilling\Access\FilterUserInputInterface $filterService */
mathieso's avatar
mathieso committed
752
    $filterService = Drupal::service('skilling.filter_user_input');
mathieso's avatar
mathieso committed
753
    $submissionSolution = $filterService->filterUserContent($submissionSolution);
754 755
    $formState->setValueForElement($form['field_solution'], $submissionSolution);
  }
mathieso's avatar
mathieso committed
756 757
}

mathieso's avatar
mathieso committed
758
function _skilling_remove_textarea_help($form_element, FormStateInterface $form_state) {
759
  //return;
mathieso's avatar
mathieso committed
760
  if (isset($form_element[0]['format'])) {
761
    //    unset($form_element[0]['format']);
mathieso's avatar
mathieso committed
762

mathieso's avatar
mathieso committed
763 764 765 766 767 768 769 770 771 772 773 774
    // All this stuff is needed to hide the help text.
    unset($form_element[0]['format']['guidelines']);
    unset($form_element[0]['format']['help']);
    unset($form_element[0]['format']['#type']);
    unset($form_element[0]['format']['#theme_wrappers']);
    $form_element[0]['format']['format']['#access'] = FALSE;
  }

  return $form_element;
}


mathieso's avatar
mathieso committed
775 776 777 778 779 780 781 782 783 784 785
/**
 * Add settings JS script needs for floating submission windows.
 *
 * @param array $form Form to modify.
 * @param string $submissionOperation add, edit, or delete.
 * @param integer $exerciseNid Exercise nid.
 * @param integer $submissionNid Submission nid. 0 for new submission.
 *
 * @throws \Drupal\skilling\Exception\SkillingUnknownValueException
 */
function skilling_add_floating_submission_js_settings(
786 787 788 789
  array &$form,
  $submissionOperation,
  $exerciseNid,
  $submissionNid) {
mathieso's avatar
mathieso committed
790
  $form['#attached']['library'][] = 'skilling/floating-submission';
mathieso's avatar
mathieso committed
791
  // Send the URL query param that shows that the form is a floater.
mathieso's avatar
mathieso committed
792
  $form['#attached']['drupalSettings']['skillingFloatQueryParam']
793
    = SkillingConstants::FLOATER_QUERY_STRING;
mathieso's avatar
mathieso committed
794
  // Send the operation: add, edit, delete.
mathieso's avatar
mathieso committed
795
  $submissionOperation = strtolower(trim($submissionOperation));
796
  if ($submissionOperation !== 'add'
797 798
    && $submissionOperation !== 'edit'
    && $submissionOperation !== 'delete') {
mathieso's avatar
mathieso committed
799
    throw new SkillingUnknownValueException(
mathieso's avatar
mathieso committed
800
      'Bad submission operation', $submissionOperation,
mathieso's avatar
mathieso committed
801 802 803 804
      __FILE__, __LINE__);
  }
  $form['#attached']['drupalSettings']['skillingFloatOperation']
    = $submissionOperation;
805 806
  // Pass the initials of the user.
  /** @var Drupal\skilling\SkillingCurrentUser $currentUser */
mathieso's avatar
mathieso committed
807
  $currentUser = Drupal::service('skilling.skilling_current_user');
808
  $form['#attached']['drupalSettings']['userInitials'] = $currentUser->getInitials();
809
  // The exercise and submission nids are passed on in the
mathieso's avatar
mathieso committed
810 811 812 813
  // destination params in the URL
  // of the submission window. They're passed on further to the window
  // close JS code. They're used to call a JS routine on the opener
  // window to update submission links.
mathieso's avatar
mathieso committed
814 815 816
  $form['#attached']['drupalSettings']['skillingExerciseNid'] = $exerciseNid;
  $form['#attached']['drupalSettings']['skillingSubmissionNid']
    = $submissionNid;
mathieso's avatar
mathieso committed
817 818 819
  if ($submissionOperation === 'add' || $submissionOperation === 'edit') {
    // Add difficulty range template and code.
    /** @var \Drupal\Core\Entity\EntityFieldManager $entityFieldManager */
mathieso's avatar
mathieso committed
820
    $entityFieldManager = Drupal::service('entity_field.manager');
mathieso's avatar
mathieso committed
821 822
    /** @var \Drupal\field\Entity\FieldConfig $difficultFieldDefinition */
    $difficultFieldDefinition = $entityFieldManager->getFieldDefinitions('node', 'submission')['field_difficulty'];
823 824 825 826
    $min = $difficultFieldDefinition->getConfig('submission')
      ->getSetting('min');
    $max = $difficultFieldDefinition->getConfig('submission')
      ->getSetting('max');
mathieso's avatar
mathieso committed
827 828 829 830
    if (isset($form['field_difficulty']['widget'][0]['value']['#default_value'])) {
      $difficultyValue = $form['field_difficulty']['widget'][0]['value']['#default_value'];
    }
    else {
831 832 833 834 835 836 837 838
      // Strange bug, where there is no [0]. Not sure what it's about.
      if (isset($difficultFieldDefinition->getConfig('submission')
          ->getDefaultValueLiteral()[0]['value'])) {
        $difficultyValue = $difficultFieldDefinition->getConfig('submission')
          ->getDefaultValueLiteral()[0]['value'];
      }
      else {
        // Slimy patch.
839
        $difficultyValue = $min + ($max - $min) / 2;
840
      }
mathieso's avatar
mathieso committed
841 842 843 844 845
    }
    // Get the weight of the form field element, so can put replacement widget
    // in the same spot.
    $weight = $form['field_difficulty']['#weight'];
    /** @var \Drupal\Core\Render\Renderer $renderer */
mathieso's avatar
mathieso committed
846
    $renderer = Drupal::service('renderer');
mathieso's avatar
mathieso committed
847 848 849 850 851 852
    $renderable = [
      '#theme' => 'difficulty_range_widget',
      '#value' => $difficultyValue,
      '#min' => $min,
      '#max' => $max,
      '#step_size' => SkillingConstants::DIFFICULTY_RANGE_WIDGET_STEP_SIZE,
853 854 855
      // Send operation, add or edit. Used when testing whether user has
      // used the range widget.
      '#operation' => $submissionOperation,
mathieso's avatar
mathieso committed
856 857 858 859 860 861 862
    ];
    $rendered = $renderer->render($renderable);
    $form['difficulty_range_widget'] = [
      '#markup' => $rendered,
      '#weight' => $weight,
    ];
    $form['#attached']['library'][] = 'skilling/difficulty-range';
863
  }
mathieso's avatar
mathieso committed
864 865
}

866
/**
mathieso's avatar
mathieso committed
867
 * Implements hook_form_FORM_alter().
868
 *
mathieso's avatar
mathieso committed
869 870 871 872
 * @param $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 * @param $form_id
 *
873
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
874 875
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\skilling\Exception\SkillingNotFoundException
mathieso's avatar
mathieso committed
876
 * @throws \Drupal\skilling\Exception\SkillingUnknownValueException
877 878
 * @throws \Drupal\skilling\Exception\SkillingValueMissingException
 * @throws \Drupal\skilling\Exception\SkillingWrongTypeException
879 880
 */
function skilling_form_node_submission_delete_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
mathieso's avatar
mathieso committed
881
  /** @var Drupal\skilling\Utilities $utilities */
mathieso's avatar
mathieso committed
882
  $utilities = Drupal::service('skilling.utilities');
mathieso's avatar
mathieso committed
883
  // Clear status messages from previous operations.
884
  $utilities->clearMessages();
mathieso's avatar
mathieso committed
885
  // Was the delete click from a floating edit form?
886
  $floater = FALSE;
mathieso's avatar
mathieso committed
887
  $referer = Drupal::request()->server->get('HTTP_REFERER');
888
  if (strstr($referer, SkillingConstants::FLOATER_QUERY_STRING) !== FALSE) {
889 890
    $floater = TRUE;
  }
891
  if ($floater) {
mathieso's avatar
mathieso committed
892 893 894 895
    // Yes, this is a floater.
    // Adjust the form.
    // Get the exercise title.
    // Explode the URL to get the submission id.
mathieso's avatar
mathieso committed
896
    $pathBits = explode('/', Drupal::request()->getPathInfo());
897
    if (!is_array($pathBits)) {
898 899
      throw new AccessDeniedHttpException('Bad path in floating delete');
    }
900
    if (!isset($pathBits[2])) {
901 902
      throw new AccessDeniedHttpException('Missing id in floating delete');
    }
903
    if (!is_numeric($pathBits[2])) {
904 905 906
      throw new AccessDeniedHttpException('Bad id in floating delete:'
        . $pathBits[2]);
    }
mathieso's avatar
mathieso committed
907 908
    $submissionOperation = 'delete';
    $submissionNid = $pathBits[2];
mathieso's avatar
mathieso committed
909
    // Check that it's a valid submission.
mathieso's avatar
mathieso committed
910
    $submission = $utilities->getPublishedSubmissionWithNid($submissionNid);
911
    if (!$submission) {
912 913
      return;
    }
mathieso's avatar
mathieso committed
914
    // Get the exercise id the submission is for.
mathieso's avatar
mathieso committed
915
    $exerciseNid = $submission->field_exercise->target_id;
mathieso's avatar
mathieso committed
916
    // Check that it's an exercise.
917
    $exercise = $utilities->getExerciseWithId($exerciseNid);
918 919
    $form['#title'] = [
      '#markup' => t('Confirm deleting submission for exercise: ')
920
        . $exercise->getTitle(),
921
    ];
mathieso's avatar
mathieso committed
922 923
    skilling_add_floating_submission_js_settings(
      $form, $submissionOperation, $exerciseNid, $submissionNid);
924
  }
925 926
}

mathieso's avatar
mathieso committed
927 928
/**
 * Implements hook_entity_insert().
mathieso's avatar
mathieso committed
929
 *
mathieso's avatar
mathieso committed
930 931
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *
mathieso's avatar
mathieso committed
932
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
933 934 935
 * @throws \Drupal\skilling\Exception\SkillingParserException
 * @throws \Drupal\Core\TempStore\TempStoreException
 * @throws \Drupal\Core\Entity\EntityStorageException
936
 * @throws \Drupal\skilling\Exception\SkillingException
mathieso's avatar
mathieso committed
937
 */
mathieso's avatar
mathieso committed
938
function skilling_entity_insert(EntityInterface $entity) {
mathieso's avatar
mathieso committed
939
  // Is the entity a node?
940
  if ($entity->getEntityType()->id() === 'node') {
mathieso's avatar
mathieso committed
941 942
    /** @var \Drupal\node\Entity\Node $node */
    $node = $entity;
mathieso's avatar
mathieso committed
943
    //Check the content type.
mathieso's avatar
mathieso committed
944
    $contentType = $node->bundle();
945
    if ($contentType === SkillingConstants::ENROLLMENT_CONTENT_TYPE) {
mathieso's avatar
mathieso committed
946
      //Schedule recompute of the enrolled user's class roles.
947
      /** @var \Drupal\skilling\RecomputeClassRoles $recomputeClassRolesService */
mathieso's avatar
mathieso committed
948
      $recomputeClassRolesService = Drupal::service('skilling.recompute_class_roles');
mathieso's avatar
mathieso committed
949 950
      $uidToRecompute = $node->field_user->target_id;
      $recomputeClassRolesService->scheduleClassRoleRecompute($uidToRecompute);
951
    }
952 953
    $isLesson = $contentType === SkillingConstants::LESSON_CONTENT_TYPE;
    $isDesign = $contentType === SkillingConstants::DESIGN_PAGE_CONTENT_TYPE;
954
    if ($isLesson || $isDesign) {
mathieso's avatar
mathieso committed
955
      // Could have book data.
956
      if (isset($node->book)) {
mathieso's avatar
mathieso committed
957
        $bookData = $node->book;
958
        if (isset($bookData['bid'])) {
mathieso's avatar
mathieso committed
959
          $bookId = $bookData['bid'];
960
          if (is_numeric($bookId)) {
961
            // Clear the cache of the book tree.
mathieso's avatar
mathieso committed
962
            Drupal::cache()->delete('book' . $bookId);
mathieso's avatar
mathieso committed
963
            //Update the pages' order field for the book.
964
            /** @var Drupal\skilling\BookUpdateTracker $bookUpdateTracker */
mathieso's avatar
mathieso committed
965
            $bookUpdateTracker = Drupal::service('skilling.book_update_tracker');
966
            $bookUpdateTracker->scheduleBookOrderUpdate([$bookId]);
mathieso's avatar
mathieso committed
967 968 969 970
          } //Bid is numeric.
        } //There is a bid.
      } //There is book data.
    } //Content type uses books.
971
    //For lessons, trigger parsing of the body field.
972
    //Then update the lesson refs of these exercises.
973
    if ($isLesson) {
974
      /** @var \Drupal\skilling\LessonReparser $lessonReparser */
mathieso's avatar
mathieso committed
975
      $lessonReparser = Drupal::service('skilling.lesson_reparser');
976
      $lessonReparser->lessonInserted($node);
977
    }
mathieso's avatar
mathieso committed
978
    if ($contentType === SkillingConstants::EXERCISE_SUBMISSION_CONTENT_TYPE) {
979 980
      $submissionSolution = $entity->field_solution->value;
      if ($submissionSolution) {
mathieso's avatar
mathieso committed
981 982
        // Filter.
        /** @var \Drupal\skilling\Access\FilterUserInputInterface $filterService */
mathieso's avatar
mathieso committed
983
        $filterService = Drupal::service('skilling.filter_user_input');
mathieso's avatar
mathieso committed
984
        $submissionSolution = $filterService->filterUserContent($submissionSolution);
985 986
        $entity->field_solution->value = $submissionSolution;
      }
mathieso's avatar
mathieso committed
987
      /** @var Drupal\skilling\SkillingCurrentUser $currentUser */
mathieso's avatar
mathieso committed
988
      $currentUser = Drupal::service('skilling.skilling_current_user');
mathieso's avatar
mathieso committed
989
      if ($currentUser->isStudent()) {
mathieso's avatar
mathieso committed
990
        // Store in history?
mathieso's avatar
mathieso committed
991 992 993 994 995 996 997 998 999 1000 1001 1002
        // Check whether the history module is active.
        /** @var Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler */
        $moduleHandler = Drupal::moduleHandler();
        $isHistoryModuleActive =
          $moduleHandler->moduleExists(SkillingConstants::HISTORY_MODULE_NAME);
        if ($isHistoryModuleActive) {
          // Store the event.
          /** @var \Drupal\skilling_history\History $historyService */
          $historyService = Drupal::service(SkillingConstants::HISTORY_SERVICE_NAME);
          // Record the response in history.
          try {
            $historyService->recordSubmissionAccess(
1003
              $node->id(), SkillingHistoryConstants::OPERATION_CREATE
mathieso's avatar
mathieso committed
1004
            );
1005
          } catch (\Exception $e) {
mathieso's avatar
mathieso committed
1006 1007 1008
            $message = 'Exception submission access history call, uid: '
              . Html::escape($currentUser->id())
              . $e->getMessage();
1009 1010
            Drupal::logger(SkillingConstants::HISTORY_MODULE_NAME)
              ->error($message);
mathieso's avatar
mathieso committed
1011 1012
          }
        }
mathieso's avatar
mathieso committed
1013 1014
      }
    }
mathieso's avatar
mathieso committed
1015
  } //Entity is a node.
mathieso's avatar
mathieso committed
1016 1017
}

1018 1019 1020 1021
/**
 * Implements hook_entity_view().
 */
function skilling_user_view(
1022 1023 1024 1025
  array &$build,
  \Drupal\Core\Entity\EntityInterface $entity,
  \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display,
  $viewMode
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058
) {
  if ($viewMode === 'full') {
    /** @var Drupal\skilling\SkillingCurrentUser $currentUser */
    $currentUser = Drupal::service('skilling.skilling_current_user');
    if ($currentUser->isStudent()) {
      // Store event in history?
      // Check whether the history module is active.
      /** @var Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler */
      $moduleHandler = Drupal::moduleHandler();
      $isHistoryModuleActive =
        $moduleHandler->moduleExists(SkillingConstants::HISTORY_MODULE_NAME);
      if ($isHistoryModuleActive) {
        // Store the event.
        /** @var \Drupal\skilling_history\History $historyService */
        $historyService = Drupal::service(SkillingConstants::HISTORY_SERVICE_NAME);
        // Record the response in history.
        try {
          $historyService->recordAccountAccess(
            $entity->id(),
            SkillingHistoryConstants::OPERATION_VIEW
          );
        } catch (\Exception $e) {
          $message = 'Exception account access history call, uid: '
            . Html::escape($currentUser->id())
            . $e->getMessage();
          Drupal::logger(SkillingConstants::HISTORY_MODULE_NAME)
            ->error($message);
        }
      }
    }
  }
}

1059 1060 1061 1062 1063 1064
/**
 * Implements hook_form_FORM_ID_alter().
 */
function skilling_form_user_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  // Can students decide whether they have personal contact forms?
  /** @var Drupal\skilling\SkillingCurrentUser $currentUser */
mathieso's avatar
mathieso committed
1065
  $currentUser = Drupal::service('skilling.skilling_current_user');
1066 1067
  if ($currentUser->isStudent()) {
    /** @var ConfigFactory $configFactory */
mathieso's avatar
mathieso committed
1068
    $configFactory = Drupal::service('config.factory');
1069 1070 1071 1072 1073 1074 1075
    $settings = $configFactory->get('skilling.settings');
    $allowContactForm = $settings->get('student_profile.allow_contact_form');
    if (!$allowContactForm) {
      $form['contact']['#access'] = FALSE;
    }
  }
}
1076 1077 1078

/**
 * Implements hook_entity_update().
1079 1080 1081 1082 1083 1084 1085 1086 1087
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 * @throws \Drupal\skilling\Exception\SkillingException
 * @throws \Drupal\skilling\Exception\SkillingValueMissingException
 * @throws \Drupal\skilling\Exception\SkillingWrongTypeException
1088 1089 1090
 */
function skilling_user_update(Drupal\Core\Entity\EntityInterface $entity) {
  /** @var \Drupal\skilling\RecomputeClassRoles $recomputeClassRolesService */
mathieso's avatar
mathieso committed
1091
  $recomputeClassRolesService = Drupal::service('skilling.recompute_class_roles');
1092 1093
  if (!$recomputeClassRolesService->isRecomputingClassRoles()) {
    /** @var Drupal\skilling\SkillingCurrentUser $currentUser */
mathieso's avatar
mathieso committed
1094
    $currentUser = Drupal::service('skilling.skilling_current_user');
1095
    if ($currentUser->isStudent()) {
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
      // Store event in history?
      // Check whether the history module is active.
      /** @var Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler */
      $moduleHandler = Drupal::moduleHandler();
      $isHistoryModuleActive =
        $moduleHandler->moduleExists(SkillingConstants::HISTORY_MODULE_NAME);
      if ($isHistoryModuleActive) {
        // Store the event.
        /** @var \Drupal\skilling_history\History $historyService */
        $historyService = Drupal::service(SkillingConstants::HISTORY_SERVICE_NAME);
        // Record the response in history.
        try {
          $historyService->recordAccountAccess(
            $entity->id(),
            SkillingHistoryConstants::OPERATION_EDIT
          );
        } catch (\Exception $e) {
          $message = 'Exception account edit history call, uid: '
            . Html::escape($currentUser->id())
            . $e->getMessage();
          Drupal::logger(SkillingConstants::HISTORY_MODULE_NAME)
            ->error($message);
        }
      }