Add Drupal application

This commit is contained in:
Oliver Davies 2019-06-05 19:35:24 +01:00
parent c00b769d67
commit 4bb39011f3
261 changed files with 22997 additions and 0 deletions

17
drupal/web/autoload.php Normal file
View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* Includes the autoloader created by Composer.
*
* This file was generated by drupal-composer/drupal-scaffold.
* https://github.com/drupal-composer/drupal-scaffold
*
* @see composer.json
* @see index.php
* @see core/install.php
* @see core/rebuild.php
* @see core/modules/statistics/statistics.php
*/
return require __DIR__ . '/../vendor/autoload.php';

View file

View file

@ -0,0 +1,3 @@
name: DTC Import
core: 8.x
type: module

View file

@ -0,0 +1,9 @@
services:
Drupal\Core\Entity\EntityTypeManagerInterface:
alias: entity_type.manager
Drupal\dtc_import\Service\Importer\CsvSessionImporter:
autowire: true
Drupal\dtc_import\Service\Importer\CsvSpeakerImporter:
autowire: true

View file

@ -0,0 +1,6 @@
Test Driven Drupal,Oliver Davies
Introduction to Views,Tom Metcalfe
Taking Flight with Tailwind CSS,Oliver Davies
The Real State of Drupal,Dan McNamara
Automate to manage repetitive tasks with Ansible,Daniel Pickering
Doing good with Drupal,Matt Howarth
1 Test Driven Drupal Oliver Davies
2 Introduction to Views Tom Metcalfe
3 Taking Flight with Tailwind CSS Oliver Davies
4 The Real State of Drupal Dan McNamara
5 Automate to manage repetitive tasks with Ansible Daniel Pickering
6 Doing good with Drupal Matt Howarth

View file

@ -0,0 +1,5 @@
Oliver Davies
Tom Metcalfe
Dan McNamara
Matt Howarth
Daniel Pickering
1 Oliver Davies
2 Tom Metcalfe
3 Dan McNamara
4 Matt Howarth
5 Daniel Pickering

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\dtc_import\Service\Importer;
use Tightenco\Collect\Support\Collection;
abstract class CsvImporter implements ImporterInterface {
abstract protected function headings(): array;
protected function getCsv(string $path): string {
return file_get_contents($path);
}
protected function splitRows(string $rows): Collection {
return collect(explode(PHP_EOL, $rows));
}
protected function splitRow(string $row): Collection {
return collect(explode(',', $row));
}
protected function mapFieldsToHeadings(Collection $session): Collection {
return $session->zip($this->headings())
->mapWithKeys(function ($session): array {
list($value, $key) = $session;
return [$key => $value];
});
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Drupal\dtc_import\Service\Importer;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\node\NodeInterface;
use Tightenco\Collect\Support\Collection;
class CsvSessionImporter extends CsvImporter {
const NODE_TYPE_SESSION = 'session';
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
private $sessionStorage;
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
private $userStorage;
/**
* CsvImporter constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function __construct(
EntityTypeManagerInterface $entityTypeManager
) {
$this->sessionStorage = $entityTypeManager->getStorage('node');
$this->userStorage = $entityTypeManager->getStorage('user');
}
public function import(): void {
$csv = $this->getCsv(__DIR__ . '/../../../sessions.csv');
$this->splitRows($csv)->filter()->map(function (string $row): Collection {
return $this->splitRow($row);
})->map(function (Collection $session): Collection {
return $this->mapFieldsToHeadings($session);
})->map(function (Collection $values): array {
return $this->findSessionNode($values);
})->filter(function (array $sessionArray): bool {
return !$sessionArray['node'];
})->map(function (array $sessionItem) {
list($values, $node) = $sessionItem;
return $this->createSessionNode($values);
});
}
protected function headings(): array {
return ['title', 'field_speakers'];
}
private function findSessionNode(Collection $values): array {
$node = collect($this->sessionStorage->loadByProperties([
'title' => $values->get('title'),
'type' => self::NODE_TYPE_SESSION,
]))->first();
return [$values, $node];
}
private function createSessionNode(Collection $values): NodeInterface {
$values = $values->merge([
'field_speakers' => $this->findSpeakers($values->get('field_speakers')),
'type' => self::NODE_TYPE_SESSION,
]);
return tap($this->sessionStorage->create($values->toArray()), function (NodeInterface $session) {
$session->setOwnerId(1);
$session->setPublished();
$session->save();
});
}
private function findSpeakers(string $speakers): array {
return $this->userStorage->loadByProperties([
'name' => explode(',', $speakers),
]);
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\dtc_import\Service\Importer;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\user\UserInterface;
use Tightenco\Collect\Support\Collection;
class CsvSpeakerImporter extends CsvImporter {
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
private $userStorage;
public function __construct(
EntityTypeManagerInterface $entityTypeManager
) {
$this->userStorage = $entityTypeManager->getStorage('user');
}
public function import(): void {
$csv = $this->getCsv(__DIR__ . '/../../../speakers.csv');
$this->splitRows($csv)->filter()->map(function (string $row): Collection {
return $this->splitRow($row);
})->map(function (Collection $session): Collection {
return $this->mapFieldsToHeadings($session);
})->map(function (Collection $values): array {
return $this->findUser($values);
})->filter(function (array $speakerArray): bool {
return !$speakerArray['user'];
})->map(function (array $speakerArray): UserInterface {
list($values, $user) = $speakerArray;
return $this->createUser($values);
});
}
protected function headings(): array {
return ['name'];
}
private function findUser(Collection $values) {
$user = collect($this->userStorage->loadByProperties([
'name' => $values->get('name'),
]))->first();
return [$values, $user];
}
private function createUser(Collection $values) {
$values = $values->merge([
'status' => TRUE,
]);
return tap($this->userStorage->create($values->toArray()), function (UserInterface $user) {
$user->save();
});
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Drupal\dtc_import\Service\Importer;
interface ImporterInterface {
public function import(): void;
}

View file

@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- node.type.session
id: node.session.promote
field_name: promote
entity_type: node
bundle: session
label: 'Promoted to front page'
description: ''
required: false
translatable: true
default_value:
-
value: 0
default_value_callback: ''
settings:
on_label: 'On'
off_label: 'Off'
field_type: boolean

View file

@ -0,0 +1,98 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.session.body
- field.field.node.session.field_session_status
- field.field.node.session.field_session_type
- field.field.node.session.field_speakers
- node.type.session
module:
- path
- text
id: node.session.default
targetEntityType: node
bundle: session
mode: default
content:
body:
type: text_textarea_with_summary
weight: 121
settings:
rows: 9
summary_rows: 3
placeholder: ''
third_party_settings: { }
region: content
created:
type: datetime_timestamp
weight: 10
region: content
settings: { }
third_party_settings: { }
field_session_status:
weight: 123
settings: { }
third_party_settings: { }
type: options_select
region: content
field_session_type:
weight: 125
settings: { }
third_party_settings: { }
type: options_select
region: content
field_speakers:
weight: 124
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
type: entity_reference_autocomplete
region: content
path:
type: path
weight: 30
region: content
settings: { }
third_party_settings: { }
promote:
type: boolean_checkbox
settings:
display_label: true
weight: 15
region: content
third_party_settings: { }
status:
type: boolean_checkbox
settings:
display_label: true
weight: 120
region: content
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
region: content
third_party_settings: { }
title:
type: string_textfield
weight: -5
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 5
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
region: content
third_party_settings: { }
hidden: { }

View file

@ -0,0 +1,53 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.session.body
- field.field.node.session.field_session_status
- field.field.node.session.field_session_type
- field.field.node.session.field_speakers
- node.type.session
module:
- options
- text
- user
id: node.session.default
targetEntityType: node
bundle: session
mode: default
content:
body:
label: hidden
type: text_default
weight: 101
settings: { }
third_party_settings: { }
region: content
field_session_status:
weight: 103
label: above
settings: { }
third_party_settings: { }
type: list_default
region: content
field_session_type:
weight: 105
label: above
settings: { }
third_party_settings: { }
type: list_default
region: content
field_speakers:
weight: 104
label: above
settings:
link: true
third_party_settings: { }
type: entity_reference_label
region: content
links:
weight: 100
settings: { }
third_party_settings: { }
region: content
hidden: { }

View file

@ -0,0 +1,29 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
- field.field.node.session.body
- node.type.session
module:
- text
- user
id: node.session.teaser
targetEntityType: node
bundle: session
mode: teaser
content:
body:
label: hidden
type: text_summary_or_trimmed
weight: 101
settings:
trim_length: 600
third_party_settings: { }
region: content
links:
weight: 100
settings: { }
third_party_settings: { }
region: content
hidden: { }

View file

@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.body
- node.type.session
module:
- text
id: node.session.body
field_name: body
entity_type: node
bundle: session
label: Body
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
display_summary: true
field_type: text_with_summary

View file

@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_session_status
- node.type.session
module:
- options
id: node.session.field_session_status
field_name: field_session_status
entity_type: node
bundle: session
label: Status
description: ''
required: false
translatable: false
default_value:
-
value: submitted
default_value_callback: ''
settings: { }
field_type: list_string

View file

@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_session_type
- node.type.session
module:
- options
id: node.session.field_session_type
field_name: field_session_type
entity_type: node
bundle: session
label: Type
description: ''
required: true
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: list_string

View file

@ -0,0 +1,27 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_speakers
- node.type.session
id: node.session.field_speakers
field_name: field_speakers
entity_type: node
bundle: session
label: Speakers
description: ''
required: true
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: 'default:user'
handler_settings:
include_anonymous: true
filter:
type: _none
target_bundles: null
sort:
field: _none
auto_create: false
field_type: entity_reference

View file

@ -0,0 +1,23 @@
langcode: en
status: true
dependencies:
module:
- node
- options
id: node.field_session_status
field_name: field_session_status
entity_type: node
type: list_string
settings:
allowed_values:
-
value: submitted
label: Submitted
allowed_values_function: ''
module: options
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View file

@ -0,0 +1,29 @@
langcode: en
status: true
dependencies:
module:
- node
- options
id: node.field_session_type
field_name: field_session_type
entity_type: node
type: list_string
settings:
allowed_values:
-
value: keynote
label: Keynote
-
value: full
label: 'Full talk (30-45 minutes)'
-
value: lightning
label: 'Lightning talk (10-15 minutes)'
allowed_values_function: ''
module: options
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View file

@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
module:
- node
- user
id: node.field_speakers
field_name: field_speakers
entity_type: node
type: entity_reference
settings:
target_type: user
module: core
locked: false
cardinality: -1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View file

@ -0,0 +1,16 @@
langcode: en
status: true
dependencies:
module:
- menu_ui
third_party_settings:
menu_ui:
available_menus: { }
parent: ''
name: Session
type: session
description: ''
help: ''
new_revision: true
preview_mode: 1
display_submitted: false

View file

@ -0,0 +1,252 @@
langcode: en
status: true
dependencies:
config:
- node.type.session
module:
- node
- user
id: user_sessions
label: 'User sessions'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'create session content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: mini
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous:
next:
style:
type: html_list
options:
grouping: { }
row_class: ''
default_row_class: true
type: ul
wrapper_class: item-list
class: ''
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
title:
id: title
table: node_field_data
field: title
entity_type: node
entity_field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
settings:
link_to_entity: true
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: string
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
filters:
status:
value: '1'
table: node_field_data
field: status
plugin_id: boolean
entity_type: node
entity_field: status
id: status
expose:
operator: ''
group: 1
type:
id: type
table: node_field_data
field: type
value:
session: session
entity_type: node
entity_field: type
plugin_id: bundle
sorts:
title:
id: title
table: node_field_data
field: title
relationship: none
group_type: group
admin_label: ''
order: ASC
exposed: false
expose:
label: ''
entity_type: node
entity_field: title
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships: { }
arguments:
uid:
id: uid
table: node_field_data
field: uid
relationship: none
group_type: group
admin_label: ''
default_action: default
exception:
value: ''
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: user
default_argument_options:
user: false
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: true
validate:
type: 'entity:user'
fail: 'not found'
validate_options:
access: true
operation: view
multiple: 0
restrict_roles: false
roles: { }
break_phrase: false
not: false
entity_type: node
entity_field: uid
plugin_id: numeric
display_extenders: { }
title: 'My submitted sessions'
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: user/%user/sessions
menu:
type: tab
title: Sessions
description: ''
expanded: false
parent: ''
weight: 0
context: '0'
menu_name: account
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }

View file

@ -0,0 +1 @@
required: true

View file

@ -0,0 +1,15 @@
name: 'DTC Sessions'
type: module
core: 8.x
dependencies:
- comment
- field
- image
- menu_ui
- node
- options
- path
- text
- user
- views
version: 8.x-1.5

View file

@ -0,0 +1,4 @@
name: DTC Sessions Test
core: 8.x
type: module
hidden: TRUE

View file

@ -0,0 +1,20 @@
<?php
namespace Drupal\dtc_sessions_test\Factory;
use Drupal\Core\Entity\EntityInterface;
/**
* A base factory class.
*/
interface FactoryInterface {
/**
* Save and return the created entity.
*
* @return \Drupal\Core\Entity\EntityInterface
* The created entity.
*/
public function save(): EntityInterface;
}

View file

@ -0,0 +1,127 @@
<?php
namespace Drupal\dtc_sessions_test\Factory;
use Drupal\Core\Entity\EntityInterface;
use Drupal\node\Entity\Node;
use Drupal\user\UserInterface;
/**
* A factory class to create a new session.
*/
class SessionFactory implements FactoryInterface {
/**
* The node type to create.
*/
const NODE_TYPE = 'session';
/**
* The session owner.
*
* @var \Drupal\user\UserInterface
*/
private $owner;
/**
* Any additional speakers to add to the session.
*
* @var array
*/
private $extraSpeakers = [];
/**
* The session title.
*
* @var string
*/
private $sessionTitle = 'A submitted session';
/**
* The type of session.
*
* @var string
*/
private $sessionType = 'full';
/**
* Set a user to be the owner of the session.
*
* @param \Drupal\user\UserInterface $owner
* The user.
*
* @return \Drupal\dtc_sessions_test\Factory\SessionFactory
*/
public function setOwner(UserInterface $owner): self {
$this->owner = $owner;
return $this;
}
/**
* Add additional speakers to the session.
*
* @param int $count
* The number of additional speakers.
*
* @return self
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function addExtraSpeakers(int $count): self {
foreach (range(1, $count) as $i) {
$this->extraSpeakers[] = (new UserFactory())
->setUsername("speaker_{$i}")
->save();
}
return $this;
}
/**
* Update the session title.
*
* @param string $title
* The session title.
*
* @return $this
*/
public function setTitle(string $title): self {
$this->sessionTitle = $title;
return $this;
}
/**
* Set the event type.
*
* @param string $type
* The event type value.
*
* @return \Drupal\dtc_sessions_test\Factory\SessionFactory
*/
public function setType(string $type): self {
$this->sessionType = $type;
return $this;
}
/**
* {@inheritdoc}
*/
public function save(): EntityInterface {
$owner = $this->owner ?? (new UserFactory())->save();
$session = Node::create([
'title' => $this->sessionTitle,
'type' => self::NODE_TYPE,
'uid' => $owner,
]);
$session->set('field_session_type', $this->sessionType);
$session->set('field_speakers', array_merge([$owner], $this->extraSpeakers));
$session->save();
return $session;
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\dtc_sessions_test\Factory;
use Drupal\Core\Entity\EntityInterface;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
/**
* A factory class to create a new user.
*/
class UserFactory implements FactoryInterface {
/**
* The user's username.
*
* @var string
*/
private $username = 'test';
/**
* The user's email address.
*
* @var string
*/
private $mail = 'test@example.com';
/**
* Set the username.
*
* @param string $name
* The username.
*
* @return \Drupal\dtc_sessions_test\Factory\UserFactory
*/
public function setUsername(string $name): self {
$this->username = $name;
return $this;
}
/**
* Set the email address.
*
* @param string $mail
* The email address.
*
* @return \Drupal\dtc_sessions_test\Factory\UserFactory
*/
public function setEmail(string $mail): self {
$this->mail = $mail;
return $this;
}
/**
* {@inheritdoc}
*/
public function save(): EntityInterface {
$user = User::create([
'name' => $this->username,
'mail' => $this->mail,
]);
$user->enforceIsNew();
$user->save();
return $user;
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace Drupal\Tests\dtc_sessions\Functional;
use Drupal\Core\Url;
use Drupal\dtc_sessions_test\Factory\SessionFactory;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\HttpFoundation\Response;
/**
* Tests for submitting sessions.
*/
class SessionSubmissionTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'dtc_sessions',
'dtc_sessions_test',
];
/**
* The session owner.
*
* @var \Drupal\user\UserInterface
*/
private $sessionOwner;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->sessionOwner = $this->createUser([
'create session content',
'edit own session content',
]);
}
/** @test */
public function anonymous_users_cannot_submit_sessions() {
$this->drupalGet(Url::fromRoute('node.add', ['node_type' => SessionFactory::NODE_TYPE]));
$this->assertSession()->statusCodeEquals(Response::HTTP_FORBIDDEN);
}
/** @test */
public function authenticated_users_can_submit_sessions() {
$this->drupalLogin($this->sessionOwner);
$this->drupalGet(Url::fromRoute('node.add', ['node_type' => SessionFactory::NODE_TYPE]));
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
}
/** @test */
public function authenticated_users_can_edit_their_submitted_sessions() {
$session = (new SessionFactory())
->setOwner($this->sessionOwner)
->save();
$this->drupalLogin($session->getOwner());
$this->drupalGet(Url::fromRoute('entity.node.edit_form', ['node' => $session->id()]));
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
}
/** @test */
public function authenticated_users_cannot_edit_their_users_sessions() {
$session = (new SessionFactory())
->setOwner($this->sessionOwner)
->save();
$this->drupalLogin($this->createUser());
$this->drupalGet(Url::fromRoute('entity.node.edit_form', ['node' => $session->id()]));
$this->assertSession()->statusCodeEquals(Response::HTTP_FORBIDDEN);
}
/** @test */
public function authenticated_users_cannot_delete_their_own_sessions() {
$session = (new SessionFactory())
->setOwner($this->sessionOwner)
->save();
$this->drupalLogin($session->getOwner());
$this->drupalGet(Url::fromRoute('entity.node.delete_form', ['node' => $session->id()]));
$this->assertSession()->statusCodeEquals(Response::HTTP_FORBIDDEN);
// Ensure that the node has not been deleted.
$this->drupalGet(Url::fromRoute('entity.node.edit_form', ['node' => $session->id()]));
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
}
/** @test */
public function users_can_view_their_own_session_nodes() {
/** @var \Drupal\node\NodeInterface $session */
$session = (new SessionFactory())->save();
/** @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
$account_switcher = $this->container->get('account_switcher');
$account_switcher->switchTo($session->getOwner());
$this->drupalGet(Url::fromRoute('entity.node.canonical', ['node' => $session->id()]));
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Drupal\Tests\dtc_sessions\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\HttpFoundation\Response;
/**
* Tests for submitting sessions.
*/
class SessionViewsTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'dtc_sessions',
'dtc_sessions_test',
'views',
];
/**
* The session owner.
*
* @var \Drupal\user\UserInterface
*/
private $sessionOwner;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->sessionOwner = $this->createUser([
'create session content',
'edit own session content',
]);
}
/** @test */
public function a_link_to_their_sessions_is_on_the_user_page() {
$this->placeBlock('local_tasks_block');
$this->drupalLogin($this->sessionOwner);
$this->assertSession()->linkByHrefExists("user/{$this->sessionOwner->id()}/sessions");
}
/** @test */
public function users_can_view_a_list_of_their_submitted_sessions() {
$this->drupalLogin($this->sessionOwner);
$this->drupalGet(Url::fromRoute('view.user_sessions.page_1', ['user' => $this->sessionOwner->id()]));
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
}
/** @test */
public function users_cannot_view_a_list_of_other_users_submitted_sessions() {
$this->drupalLogin($this->drupalCreateUser());
$this->drupalGet(Url::fromRoute('view.user_sessions.page_1', ['user' => $this->sessionOwner->id()]));
$this->assertSession()->statusCodeEquals(Response::HTTP_FORBIDDEN);
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Drupal\Tests\dtc_sessions\Kernel;
use Drupal\Core\Test\AssertMailTrait;
use Drupal\dtc_sessions_test\Factory\SessionFactory;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\user\Entity\User;
class SessionConfirmationTest extends EntityKernelTestBase {
use AssertMailTrait;
/**
* {@inheritdoc}
*/
protected $strictConfigSchema = FALSE;
/**
* {@inheritdoc}
*/
public static $modules = [
'dtc_sessions',
'hook_event_dispatcher',
'node',
'options',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig([
'node',
'dtc_sessions',
]);
}
/** @test */
public function a_confirmation_email_is_sent_to_the_owner_when_a_session_is_submitted() {
$session = (new SessionFactory())->save();
$mails = $this->getMails(['id' => 'dtc_sessions_session_confirmation']);
$this->assertCount(1, $mails);
$this->assertSame($session->getOwner()->getEmail(), $mails[0]['to']);
$this->assertSame($session, $mails[0]['params']['session']);
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\Tests\dtc_sessions\Kernel;
use Drupal\dtc_sessions_test\Factory\SessionFactory;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
class SessionSubmissionTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
protected $strictConfigSchema = FALSE;
/**
* {@inheritdoc}
*/
public static $modules = [
'dtc_sessions',
'node',
'options',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig([
'node',
'dtc_sessions',
]);
}
/** @test */
public function new_sessions_are_marked_as_submitted() {
$session = (new SessionFactory())->save();
$this->assertSame('submitted', $session->get('field_session_status')->getString());
}
/** @test */
public function users_can_be_set_as_speakers() {
$session = (new SessionFactory())->save();
$speakers = $session->get('field_speakers')->getValue();
$this->assertSame($session->getOwnerId(), $speakers[0]['target_id']);
}
/** @test */
public function a_session_can_have_multiple_speakers() {
$session = (new SessionFactory())
->addExtraSpeakers(2)
->save();
$speakers = $session->get('field_speakers')->getValue();
$this->assertCount(3, $speakers);
}
/** @test */
public function a_session_has_a_type() {
$session = (new SessionFactory())
->setType('full')
->save();
$this->assertSame('full', $session->get('field_session_type')->getString());
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\Tests\dtc_sessions\Kernel;
use Drupal\dtc_sessions_test\Factory\SessionFactory;
use Drupal\dtc_sessions_test\Factory\UserFactory;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\views\ResultRow;
class SessionViewsTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
protected $strictConfigSchema = FALSE;
/**
* {@inheritdoc}
*/
public static $modules = [
'dtc_sessions',
'node',
'options',
'views',
];
/**
* @var \Drupal\Core\Session\AccountSwitcherInterface
*/
private $accountSwitcher;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig([
'node',
'dtc_sessions',
]);
$this->accountSwitcher = $this->container->get('account_switcher');
}
/** @test */
public function a_user_can_view_a_list_of_their_submitted_sessions() {
$user1 = (new UserFactory())->setUsername('user1')->save();
$user2 = (new UserFactory())->setUsername('user2')->save();
$session1 = (new SessionFactory())->setTitle('User 1 session')->setOwner($user1)->save();
$session2 = (new SessionFactory())->setTitle('User 2 session')->setOwner($user2)->save();
$this->accountSwitcher->switchTo($user2);
$sessions = views_get_view_result('user_sessions', 'page_1', $user2->id());
$this->assertCount(1, $sessions);
$this->assertSame($session2->uuid(), $sessions[0]->_entity->uuid());
}
/** @test */
public function sessions_are_listed_alphabetically() {
$owner = $this->createUser([], ['create session content']);
(new SessionFactory())->setOwner($owner)->setTitle('Session B')->save();
(new SessionFactory())->setOwner($owner)->setTitle('Session A')->save();
(new SessionFactory())->setOwner($owner)->setTitle('Session D')->save();
(new SessionFactory())->setOwner($owner)->setTitle('Session C')->save();
$this->accountSwitcher->switchTo($owner);
$nids = array_map(function (ResultRow $row) {
return $row->_entity->id();
}, views_get_view_result('user_sessions', 'page_1', $owner->id()));
$this->assertEquals([2, 1, 4, 3], $nids);
}
}

View file

@ -0,0 +1,27 @@
<?php
use Drupal\dtc_sessions_test\Factory\UserFactory;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\user\Entity\User;
class UserRolesTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'dtc_user_roles',
'dtc_sessions',
'dtc_sessions_test',
'hook_event_dispatcher',
];
/** @test */
public function new_users_are_given_the_session_submitter_role() {
/** @var \Drupal\user\UserInterface $user */
$user = (new UserFactory())->save();
$this->assertTrue($user->hasRole('session_submitter'));
}
}

View file

View file

@ -0,0 +1 @@
/settings.local.php

View file

@ -0,0 +1,9 @@
parameters:
cors.config:
enabled: true
allowedHeaders: ['x-csrf-token','authorization','content-type','accept','origin','x-requested-with', 'access-control-allow-origin','x-allowed-header','*']
allowedMethods: ['*']
allowedOrigins: ['http://localhost:8080']
exposedHeaders: true
maxAge: false
supportsCredentials: true

View file

@ -0,0 +1,9 @@
<?php
$databases['default']['default'] = [
'driver' => 'mysql',
'host' => 'db',
'database' => getenv('MYSQL_DATABASE'),
'username' => getenv('MYSQL_USER'),
'password' => getenv('MYSQL_PASSWORD'),
];

View file