diff --git a/composer.json b/composer.json index 4f6a76abd..29ea7dd89 100644 --- a/composer.json +++ b/composer.json @@ -17,10 +17,6 @@ } } }, - "drupal": { - "type": "composer", - "url": "https://packages.drupal.org/8" - }, "umlib_admin-theme": { "type": "package", "package": { diff --git a/docker-compose.yml b/docker-compose.yml index f3f2648d4..432877f85 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,10 +7,10 @@ services: - database:/var/lib/mysql - ./drupal8-blogs.sql.gz:/docker-entrypoint-initdb.d/drupal8-blogs.sql.gz environment: - - MARIADB_ROOT_PASSWORD=password - - MARIADB_USER=user - - MARIADB_PASSWORD=password - - MARIADB_DATABASE=drupal + - MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD:-change_me} + - MARIADB_USER=${MARIADB_USER:-user} + - MARIADB_PASSWORD=${MARIADB_PASSWORD:-change_me} + - MARIADB_DATABASE=${MARIADB_DATABASE:-drupal} restart: always drupal: @@ -21,11 +21,12 @@ services: ports: - 25647:80 environment: - - MARIADB_USER=user - - MARIADB_PASSWORD=password - - MARIADB_DATABASE=drupal + - MARIADB_USER=${MARIADB_USER:-user} + - MARIADB_PASSWORD=${MARIADB_PASSWORD:-change_me} + - MARIADB_DATABASE=${MARIADB_DATABASE:-drupal} - DATABASE_HOST=database - - DRUPAL_HASH_SALT='drupal_hash_salt' + - DRUPAL_HASH_SALT=${DRUPAL_HASH_SALT:-set_a_strong_hash_salt} + - DRUPAL_DEV_MODE=${DRUPAL_DEV_MODE:-1} volumes: - ./:/var/www/html restart: always diff --git a/modules/custom/custom_blogs/custom_blogs.module b/modules/custom/custom_blogs/custom_blogs.module index a915d21b6..639ae5bc8 100644 --- a/modules/custom/custom_blogs/custom_blogs.module +++ b/modules/custom/custom_blogs/custom_blogs.module @@ -2,7 +2,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; -use Drupal\Core\Render\Markup; use Drupal\Core\Block\BlockPluginInterface; use Drupal\views\ViewExecutable; use Drupal\node\Entity\Node; @@ -81,9 +80,9 @@ function _custom_blogs_form_user_register_validate(&$form, FormStateInterface $f $email = $form_state->getValue('mail'); $uname = NULL; if ($email) { - $uname = explode('@', $email); - if ($uname[1] == 'umich.edu') { - $uname = $uname[0]; + $email = trim($email); + if (str_ends_with(strtolower($email), '@umich.edu')) { + $uname = substr($email, 0, -strlen('@umich.edu')); } else { $uname = $email; @@ -94,7 +93,7 @@ function _custom_blogs_form_user_register_validate(&$form, FormStateInterface $f $form_state->setValue('edit_name', $uname); } else { - $form_state->setErrorByName('blogs_username_error', t('Cannot create user ' . $uname)); + $form_state->setErrorByName('blogs_username_error', t('Cannot create user from the provided email address.')); } } @@ -104,32 +103,56 @@ function _custom_blogs_form_user_register_validate(&$form, FormStateInterface $f function _get_mcommunity_user($name) { $user_data = []; if ($mcomm = Settings::get('mcommunity')) { + $name = trim((string) $name); + if ($name === '') { + return $user_data; + } + if (function_exists('ldap_escape')) { + $name = ldap_escape($name, '', LDAP_ESCAPE_FILTER); + } + else { + $name = preg_replace('/[^a-zA-Z0-9._-]/', '', $name); + } $ldap_config = parse_ini_file($mcomm, TRUE); $ldap_resource = NULL; $attempts = 0; $ldap_result = FALSE; while ($attempts < 5 && !$ldap_result) { $ldap_resource = ldap_connect($ldap_config['connect']['uri']); - ldap_bind($ldap_resource, $ldap_config['bind']['dn'], $ldap_config['bind']['pw']); + if (!$ldap_resource) { + sleep(5); + $attempts++; + continue; + } + if (!ldap_bind($ldap_resource, $ldap_config['bind']['dn'], $ldap_config['bind']['pw'])) { + ldap_unbind($ldap_resource); + sleep(5); + $attempts++; + continue; + } $ldap_result = ldap_search( $ldap_resource, "ou=People,dc=umich,dc=edu", - "(uid=".$name.")", + '(uid=' . $name . ')', ['entityid','displayName','title','mail','givenname','umichdisplaysn','umichtitle','umichInstRoles','umichHR'] ); if (!$ldap_result) { + ldap_unbind($ldap_resource); sleep(5); } $attempts++; } - $ldap_entries = ldap_get_entries($ldap_resource, $ldap_result); - if ($ldap_entries['count'] != 0) { - $raw_data = array_filter($ldap_entries[0], function($k) { return !is_numeric($k); }, ARRAY_FILTER_USE_KEY); - foreach ($raw_data as $key => $data) { - if (isset($data[0])) { - $user_data[$key] = $data[0]; + if ($ldap_resource && $ldap_result) { + $ldap_entries = ldap_get_entries($ldap_resource, $ldap_result); + if ($ldap_entries['count'] != 0) { + $raw_data = array_filter($ldap_entries[0], function($k) { return !is_numeric($k); }, ARRAY_FILTER_USE_KEY); + foreach ($raw_data as $key => $data) { + if (isset($data[0])) { + $user_data[$key] = $data[0]; + } } } + ldap_unbind($ldap_resource); } } @@ -193,7 +216,8 @@ function custom_blogs_user_presave($user) { */ function custom_blogs_paragraph_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) { if ($entity->bundle() == 'get_in_touch') { - $is_empty = _custom_blogs_make_sure_we_delete_empty_paragraphs($entity); + // Never mutate storage during render. + $is_empty = _custom_blogs_is_empty_get_in_touch_paragraph($entity); if (!$is_empty) { $name = trim($entity->get('field_name')->value); $email = trim($entity->get('field_email')->value); @@ -212,15 +236,22 @@ function custom_blogs_paragraph_view_alter(array &$build, EntityInterface $entit */ // see if https://www.drupal.org/project/paragraphs/issues/2877695 is ever solved. function _custom_blogs_make_sure_we_delete_empty_paragraphs(EntityInterface $entity) { - $name = trim($entity->get('field_name')->value); - $email = trim($entity->get('field_email')->value); - if (empty($name) && empty($email)) { + if (_custom_blogs_is_empty_get_in_touch_paragraph($entity)) { $entity->delete(); return TRUE; } return FALSE; } +/** + * Determine if the paragraph has no contact information. + */ +function _custom_blogs_is_empty_get_in_touch_paragraph(EntityInterface $entity) { + $name = trim((string) $entity->get('field_name')->value); + $email = trim((string) $entity->get('field_email')->value); + return empty($name) && empty($email); +} + /** * Implements hook_ENTITY_TYPE_view_alter(). */ @@ -237,7 +268,11 @@ function custom_blogs_node_view_alter(array &$build, EntityInterface $entity, En } if ($entity->bundle() == 'blog' && $display->id() == 'node.blog.subscribe') { $build['#post_render'][] = function ($html, array $elements) { - return str_replace(' group', ' '.$elements['#node']->label(), $html); + $label = ''; + if (!empty($elements['#node']) && method_exists($elements['#node'], 'label')) { + $label = Html::escape($elements['#node']->label()); + } + return str_replace(' group', ' ' . $label, $html); }; } } @@ -375,8 +410,8 @@ function custom_blogs_form_views_exposed_form_alter(array &$form, FormStateInter $form['keys']['#title'] = 'Browse by Date'; } $form['#info']['filter-keys']['label'] = $form['keys']['#title']; - $form['keys']['#placeholder'] = Markup::create(str_replace('search ', 'Search within ', strtolower($form['keys']['#title']))); - $form['#prefix'] = '

'.$form['keys']['#title'].'

'; + $form['keys']['#placeholder'] = str_replace('search ', 'Search within ', strtolower($form['keys']['#title'])); + $form['#prefix'] = '

' . Html::escape($form['keys']['#title']) . '

'; if (strpos($path, 'all-posts') !== FALSE) { $form['#suffix'] = '
Browse by Date
'; } @@ -399,13 +434,13 @@ function custom_blogs_form_views_exposed_form_alter(array &$form, FormStateInter $form['keys']['#title'] = 'Browse in ' . $blog->getTitle(); } $form['#info']['filter-keys']['label'] = $form['keys']['#title']; - $form['keys']['#placeholder'] = Markup::create(str_replace('Search ', 'Search within ', $form['keys']['#title'])); - $form['#prefix'] = '

'.$form['keys']['#title'].'

'; + $form['keys']['#placeholder'] = str_replace('Search ', 'Search within ', $form['keys']['#title']); + $form['#prefix'] = '

' . Html::escape($form['keys']['#title']) . '

'; } } } //Unset these if we arent on the all-posts page with optional date - if (($view['view']->id() == 'all_posts' && !strpos($path, 'all-posts')) || ($view['view']->id() == 'all_posts_date' && !strpos($path, 'all-dates'))) { + if (($view['view']->id() == 'all_posts' && strpos($path, 'all-posts') === FALSE) || ($view['view']->id() == 'all_posts_date' && strpos($path, 'all-dates') === FALSE)) { unset($form['blog']); } if ($view['view']->id() == 'recent_posts') { @@ -438,12 +473,12 @@ function custom_blogs_form_views_exposed_form_alter(array &$form, FormStateInter else { $form['#attributes']['class'][] = 'hidden'; } - $form['keys']['#placeholder'] = Markup::create(str_replace('Search in', 'Search within', $form['keys']['#title'])); - if ($form['actions']['reset']['#access']) { - $form['#suffix'] = '
Reset results list
'; + $form['keys']['#placeholder'] = str_replace('Search in', 'Search within', $form['keys']['#title']); + if (!empty($form['actions']['reset']['#access'])) { + $form['#suffix'] = '
Reset results list
'; unset($form['actions']['reset']); } - $form['#prefix'] = '

'.$form['keys']['#title'].'

'; + $form['#prefix'] = '

' . Html::escape($form['keys']['#title']) . '

'; } } @@ -627,7 +662,7 @@ function custom_blogs_views_post_render(ViewExecutable $view, array &$output, Ca $blog_id = $view->args[1]; if ($blog = Node::load($blog_id)) { $title = $blog->getTitle(); - $view->setTitle($view->getTitle() . ' in Blog ' . $title . ''); + $view->setTitle($view->getTitle() . ' in Blog ' . $title); } unset($output['#view']->field['og_audience']); } @@ -637,7 +672,7 @@ function custom_blogs_views_post_render(ViewExecutable $view, array &$output, Ca $blog_id = $view->args[0]; $blog = Node::load($blog_id); $title = $blog->getTitle(); - $view->setTitle($view->getTitle() . ' in ' . $title . ''); + $view->setTitle($view->getTitle() . ' in ' . $title); } } if ($view->id() == 'recent_posts' && $view->current_display == 'block_1') { diff --git a/modules/custom/custom_blogs_og/custom_blogs_og.module b/modules/custom/custom_blogs_og/custom_blogs_og.module index 34302225f..aeb97a1bb 100644 --- a/modules/custom/custom_blogs_og/custom_blogs_og.module +++ b/modules/custom/custom_blogs_og/custom_blogs_og.module @@ -31,10 +31,12 @@ function _custom_blogs_og_verify_users($nid) { $memberships = $query->execute(); foreach ($memberships as $member) { $user = User::load($member->uid); + if (!$user) { + continue; + } $name = $user->getDisplayName(); $user_data = _get_mcommunity_user($name); if (strpos($name, '@') === FALSE && empty($user_data)) { - $roles['remove'] = _custom_blogs_get_all_roles(); $memberships = \Drupal::service('og.membership_manager')->getMemberships($user->id()); foreach ($memberships as $membership) { $membership->delete(); @@ -61,7 +63,7 @@ function custom_blogs_og_form_og_unsubscribe_confirm_form_alter(&$form, FormStat $account = \Drupal::currentUser(); $accessible = _custom_blogs_test_og_access($node, 'edit', $account); $form['description'] = ['#markup' => t('You can resubscribe at any time.')]; - $access = $accessible[$node->id()]; + $access = $accessible[$node->id()] ?? 'blog_member'; if ($access != 'blog_member') { $form['description'] = ['#markup' => t('Unsubscribing also removes your access to contribute to this blog.
If you resubscribe in the future, authoring access has to be granted separately by a blog manager.')]; } @@ -473,10 +475,13 @@ function _custom_blogs_form_og_membership_add_validate(&$form, FormStateInterfac } $roles = _custom_blogs_get_roles($roles); if (empty($form_state->getValue('uid')[0]['target_id'])) { - $new_user_name = trim($user_input['uid'][0]['target_id']); - $uname = explode('@', $new_user_name); - if ($uname[1] == 'umich.edu') { - $new_user_name = $uname[0]; + $new_user_name = trim((string) ($user_input['uid'][0]['target_id'] ?? '')); + if ($new_user_name === '') { + $form_state->setErrorByName('uid', t('A username or email address is required.')); + return; + } + if (str_ends_with(strtolower($new_user_name), '@umich.edu')) { + $new_user_name = substr($new_user_name, 0, -strlen('@umich.edu')); } $new_user = NULL; if (strpos($new_user_name, '@') === FALSE) { @@ -529,6 +534,10 @@ function _custom_blogs_form_og_membership_add_validate(&$form, FormStateInterfac } else { $user = User::load($form_state->getValue('uid')[0]['target_id']); + if (!$user) { + $form_state->setErrorByName('uid', t('Unable to load the selected user.')); + return; + } foreach ($roles['add'] as $role) { $user->addRole($role); } diff --git a/modules/custom/custom_openid/custom_openid.module b/modules/custom/custom_openid/custom_openid.module index 0bad9fa3e..0880ec14c 100644 --- a/modules/custom/custom_openid/custom_openid.module +++ b/modules/custom/custom_openid/custom_openid.module @@ -10,17 +10,14 @@ use Drupal\paragraphs\Entity\Paragraph; * Implements hook_openid_connect_pre_authorize(). */ function custom_openid_openid_connect_pre_authorize($account, array $context) { - $uname = NULL; - if (isset($context['userinfo']['preferred_username']) || isset($context['userinfo']['email'])) { - $uname = $context['userinfo']['preferred_username']; - if (empty($uname)) { - $uname = explode('@', $context['userinfo']['email']); - if ($uname[1] == 'umich.edu') { - $uname = $uname[0]; - } - else { - $uname = $context['userinfo']['email']; - } + if (($context['plugin_id'] ?? '') !== 'generic') { + return FALSE; + } + $uname = ''; + if (!empty($context['userinfo'])) { + $uname = trim((string) ($context['userinfo']['preferred_username'] ?? '')); + if ($uname === '') { + $uname = _custom_openid_normalize_username_from_email($context['userinfo']['email'] ?? ''); } // UNCOMMENT 3 LINES BELOW and replace YOUR_USERNAME_HERE and USER_YOU_WANT_TO_BE_HERE with appropriate values // if (in_array($uname,['YOUR_USERNAME_HERE']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome')) { @@ -40,17 +37,15 @@ function custom_openid_openid_connect_pre_authorize($account, array $context) { // above // $userinfo['name'] = $uname; } - if ((is_bool($account) || !$account) && $uname) { + if ((is_bool($account) || !$account) && $uname !== '') { $account = user_load_by_name($uname); } // Allow access only for current users - if (($account && !$account->isBlocked() && $account->hasRole('authenticated')) - && $context['plugin_id'] == 'generic' - ) { + if ($account && !$account->isBlocked() && $account->hasRole('authenticated')) { return $account; } // Deny all other users. -// return FALSE; + return FALSE; } /** @@ -58,14 +53,24 @@ function custom_openid_openid_connect_pre_authorize($account, array $context) { */ function custom_openid_openid_connect_userinfo_alter(array &$userinfo, array $context) { // Add some custom information. - if ($context['plugin_id'] == 'generic') { - $uname = explode('@', $userinfo['email']); - if ($uname[1] == 'umich.edu') { - $uname = $uname[0]; - } - else { - $uname = $userinfo['email']; + if (($context['plugin_id'] ?? '') == 'generic') { + $uname = _custom_openid_normalize_username_from_email($userinfo['email'] ?? ''); + if ($uname !== '') { + $userinfo['name'] = $uname; } - $userinfo['name'] = $uname; } } + +/** + * Normalize identity provider email to a local username format. + */ +function _custom_openid_normalize_username_from_email($email) { + $email = trim((string) $email); + if ($email === '') { + return ''; + } + if (str_ends_with(strtolower($email), '@umich.edu')) { + return substr($email, 0, -strlen('@umich.edu')); + } + return $email; +} diff --git a/modules/custom/custom_uml_blogs_mail/custom_uml_blogs_mail.module b/modules/custom/custom_uml_blogs_mail/custom_uml_blogs_mail.module index eb8fa95c6..5098c86a6 100644 --- a/modules/custom/custom_uml_blogs_mail/custom_uml_blogs_mail.module +++ b/modules/custom/custom_uml_blogs_mail/custom_uml_blogs_mail.module @@ -26,22 +26,42 @@ function custom_uml_blogs_mail_module_implements_alter(&$implementations, $hook) * Alter destination of outgoing emails to group members. */ function custom_uml_blogs_mail_mail_alter(&$message) { - if ($message['id'] == 'content_moderation_notifications_content_moderation_notification') { - $node = $message['params']['context']['entity']; + if (($message['id'] ?? '') == 'content_moderation_notifications_content_moderation_notification') { + $node = $message['params']['context']['entity'] ?? NULL; + if (!$node instanceof \Drupal\node\NodeInterface || !$node->hasField('og_audience') || $node->get('og_audience')->isEmpty()) { + return; + } $group_node = \Drupal\node\Entity\Node::load($node->get('og_audience')->target_id); + if (!$group_node) { + return; + } $memberships = \Drupal::service('og.membership_manager')->getGroupMembershipsByRoleNames($group_node, ['member']); - $bcc = ''; + $bcc_emails = []; + $validator = \Drupal::service('email.validator'); foreach ($memberships as $membership) { - $bcc .= $membership->getOwner()->getEmail() . ','; + $email = $membership->getOwner()->getEmail(); + if (!empty($email) && $validator->isValid($email)) { + $bcc_emails[$email] = $email; + } } - $subject = str_replace('[node:og_audience:entity:title]: [node:title]', $group_node->getTitle().': '.$node->getTitle(), $message['params']['subject']); - \Drupal::logger('custom_uml_blogs_mail')->notice('Meessage %subject sent to %bcc', + if (empty($bcc_emails)) { + return; + } + $bcc = implode(',', $bcc_emails); + $subject = str_replace( + '[node:og_audience:entity:title]: [node:title]', + $group_node->getTitle() . ': ' . $node->getTitle(), + (string) ($message['params']['subject'] ?? '') + ); + \Drupal::logger('custom_uml_blogs_mail')->notice('Message %subject sent to %count subscribers', [ '%subject' => $subject, - '%bcc' => $bcc, + '%count' => count($bcc_emails), ]); - $message['bcc'] = rtrim($bcc, ','); - $message['headers']['Bcc'] = $message['bcc']; - \Drupal::messenger()->addMessage('All subscribers of ' . $group_node->getTitle() . ' have been sent a notification email.'); + $message['bcc'] = $bcc; + if (!isset($message['headers']) || !is_array($message['headers'])) { + $message['headers'] = []; + } + $message['headers']['Bcc'] = $bcc; } } diff --git a/sites/default/docker.settings.php b/sites/default/docker.settings.php index d0bd62ad0..6f3d1ed2c 100644 --- a/sites/default/docker.settings.php +++ b/sites/default/docker.settings.php @@ -770,7 +770,7 @@ # include $app_root . '/' . $site_path . '/settings.local.php'; # } -$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT'); +$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT') ?: ''; $databases['default']['default'] = [ 'database' => $_ENV["MARIADB_DATABASE"], 'username' => $_ENV["MARIADB_USER"], @@ -782,6 +782,14 @@ 'driver' => 'mysql', ]; $settings['config_sync_directory'] = 'sites/default/files/config_aFUnY9HdNNWWel_SNtonfe7b_UatBYGN_c8dC9jZqeG6xmeY8CwslFHgWq9YcF8ArK8PZE857Q/sync'; -error_reporting(E_ALL); -ini_set('display_errors', TRUE); -ini_set('display_startup_errors', TRUE); + +if (filter_var(getenv('DRUPAL_DEV_MODE') ?: '0', FILTER_VALIDATE_BOOL)) { + error_reporting(E_ALL); + ini_set('display_errors', '1'); + ini_set('display_startup_errors', '1'); +} +else { + error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED); + ini_set('display_errors', '0'); + ini_set('display_startup_errors', '0'); +} diff --git a/themes/umlib_blogs/templates/colorbox-formatter.html.twig b/themes/umlib_blogs/templates/colorbox-formatter.html.twig index c71b32d2b..815cf7bb1 100644 --- a/themes/umlib_blogs/templates/colorbox-formatter.html.twig +++ b/themes/umlib_blogs/templates/colorbox-formatter.html.twig @@ -14,4 +14,4 @@ */ #} -{{ image }} +{{ image }}