diff --git a/config/sync/workflows.workflow.editorial.yml b/config/sync/workflows.workflow.editorial.yml index 8e7f00e..9fdea22 100644 --- a/config/sync/workflows.workflow.editorial.yml +++ b/config/sync/workflows.workflow.editorial.yml @@ -3,6 +3,7 @@ langcode: en status: true dependencies: config: + - media.type.remote_video - node.type.landing_page module: - content_moderation @@ -62,6 +63,8 @@ type_settings: to: published weight: 1 entity_types: + media: + - remote_video node: - landing_page default_moderation_state: draft diff --git a/web/modules/custom/drupal_site_core/drupal_site.info.yml b/web/modules/custom/drupal_site_core/drupal_site_core.info.yml similarity index 100% rename from web/modules/custom/drupal_site_core/drupal_site.info.yml rename to web/modules/custom/drupal_site_core/drupal_site_core.info.yml diff --git a/web/modules/custom/drupal_site_core/drupal_site_core.module b/web/modules/custom/drupal_site_core/drupal_site_core.module index 3c78d09..8229265 100644 --- a/web/modules/custom/drupal_site_core/drupal_site_core.module +++ b/web/modules/custom/drupal_site_core/drupal_site_core.module @@ -4,3 +4,54 @@ * @file * Contains drupal_site_core.module. */ +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\node\NodeInterface; +use Drupal\media\Entity\Media; + +final class EntityManager { + public static $changed = []; + + public static $updating = false; + + public static function checkAtomicity(EntityInterface $entity): bool { + $element = $entity->bundle() . $entity->id(); + if (in_array($element, self::$changed)) { + return true; + } + self::$changed []= $element; + return false; + } + + public static function checkEntity(EntityInterface $entity) { + if (self::checkAtomicity($entity)) { + return; + } + if ($entity->getEntityTypeId() == 'media' && $entity->bundle() == 'remote_video') { + $node = \Drupal::routeMatch()->getParameter('node'); + + if ($node && !$node->isDefaultRevision()) { + if ($entity->hasField('moderation_state') && $entity->get('moderation_state')->value !== 'draft') { + self::$updating = TRUE; + + $entity->set('moderation_state', 'draft'); + $entity->setNewRevision(TRUE); + $entity->save(); + + self::$updating = FALSE; + } + } + } + } +} + +function drupal_site_core_entity_presave(EntityInterface $entity) { + EntityManager::checkEntity($entity); +} + + +function hook_site_core_entity_presave(EntityInterface $entity) { + // Update the entity's entry in a fictional table of this type of entity. + die('hoooked'); +} + diff --git a/web/modules/custom/magazine/README.md b/web/modules/custom/magazine/README.md new file mode 100644 index 0000000..b41c954 --- /dev/null +++ b/web/modules/custom/magazine/README.md @@ -0,0 +1,30 @@ +## INTRODUCTION + +The Magazine module is a DESCRIBE_THE_MODULE_HERE. + +The primary use case for this module is: + +- Use case #1 +- Use case #2 +- Use case #3 + +## REQUIREMENTS + +DESCRIBE_MODULE_DEPENDENCIES_HERE + +## INSTALLATION + +Install as you would normally install a contributed Drupal module. +See: https://www.drupal.org/node/895232 for further information. + +## CONFIGURATION +- Configuration step #1 +- Configuration step #2 +- Configuration step #3 + +## MAINTAINERS + +Current maintainers for Drupal 10: + +- FIRST_NAME LAST_NAME (NICKNAME) - https://www.drupal.org/u/NICKNAME + diff --git a/web/modules/custom/magazine/custom_permissions.permissions.yml b/web/modules/custom/magazine/custom_permissions.permissions.yml new file mode 100644 index 0000000..0c18f0f --- /dev/null +++ b/web/modules/custom/magazine/custom_permissions.permissions.yml @@ -0,0 +1,11 @@ +content manager: + title: 'Content manager' + description: 'Can move from "Draft" to "Review".' + +editor: + title: 'Editor' + description: 'Can approve content.' + +chief editor: + title: 'Chief Editor' + description: 'Can publish content.' diff --git a/web/modules/custom/magazine/magazine.info.yml b/web/modules/custom/magazine/magazine.info.yml new file mode 100644 index 0000000..c62785f --- /dev/null +++ b/web/modules/custom/magazine/magazine.info.yml @@ -0,0 +1,7 @@ +name: 'Magazine' +type: module +description: 'Custom Entity and Multilingual Workflow' +package: Custom +core_version_requirement: ^10 || ^11 +dependencies: + - drupal:user diff --git a/web/modules/custom/magazine/magazine.install b/web/modules/custom/magazine/magazine.install new file mode 100644 index 0000000..2bf10a7 --- /dev/null +++ b/web/modules/custom/magazine/magazine.install @@ -0,0 +1,21 @@ + 'client', 'label' => 'Client'); + //creating your role + $role = \Drupal\user\Entity\Role::create($data); + //saving your role + $role->save(); + } + + + /** + * install + * + * Performs module instalation. + * + * @return void + */ + public static function install(): void { + // install module + } + + /** + * uninstall + * + * Performs module uninstall. + * + * @return void + */ + public static function uninstall(): void { + // uninstall module + } +} diff --git a/web/modules/custom/telemetry/README.md b/web/modules/custom/telemetry/README.md new file mode 100644 index 0000000..85ced75 --- /dev/null +++ b/web/modules/custom/telemetry/README.md @@ -0,0 +1,60 @@ +## Telemetry Module + +This telemetry module is a functional test both to validate technical knowledge and to evaluate the possibilities of features offered by the most current versions of Drupal. + +The primary use case for this module is: + +- Receive information about execution, installation, etc. quickly and clearly, without the need to access complex panels and systems, using everyday tools; +- Carefully monitor processes that require attention - such as those that may eventually generate additional costs, such as installation, build and/or inadvertent use; +- Store messages sent via telemetry locally and/or remotely; + +## REQUIREMENTS + +- Drupal 10 or newer; +- DDEV 1.23.4 or newer; +- Composer version 2.7.7 or newer; +- PHP version 8.3.10 or newer; + +## INSTALLATION + +Install as you would normally install a contributed Drupal module. +See: https://www.drupal.org/node/895232 for further information. + + +## MAINTAINERS + +- John Murowaniecki 🜏 ( _jmurowaniecki_ · aka `0xD3C0de`); + + +## Contribute + +### Getting started + +First download and configure the project following the steps below: + +```bash +git clone git@github.com:jmurowaniecki/zoocha.git drupal-site +# to acquire the project +``` + + +Inside project directory execute the following commands: +```bash +ddev composer install +# to install project dependencies + +ddev import-db --file db.sql.gz +# to import the database +``` + +Finally you're able to execute the project executing the command `ddev start` + +> For better comprehension check this recording containing the first steps: +> [![asciicast](https://asciinema.org/a/gHIIVv3X6amNdcdThfc6ISj9D.svg)](https://asciinema.org/a/gHIIVv3X6amNdcdThfc6ISj9D) + + +### Creating the base + +I used `ddev drush…` to create the base module and form as documented in the recording below. More information can be obtained by looking at the commits made. + +[![asciicast](https://asciinema.org/a/z2DJTC3R9TqgYiiHQiWzj7Mlw.svg)](https://asciinema.org/a/z2DJTC3R9TqgYiiHQiWzj7Mlw) diff --git a/web/modules/custom/telemetry/src/Form/TelemetryForm.php b/web/modules/custom/telemetry/src/Form/TelemetryForm.php new file mode 100644 index 0000000..f1e5783 --- /dev/null +++ b/web/modules/custom/telemetry/src/Form/TelemetryForm.php @@ -0,0 +1,124 @@ +connection = Database::getConnection(); + } + + /** + * getFields + * + * Should return form fields processed, merged with the provided ones and + * extracted taking the list of parameters. + * + * @param array $form Form fields to append. + * @return array Form fields processed. + */ + private function getFields(array $form = []): array { + return array_merge($form, Module::extractFields( + Module::TABLE_FIELDS, + ['message', 'message_type'], + function ($translate) { + return $this->t($translate); + })); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $query = $this->connection->select('telemetry', 't') + ->fields('t', ['message', 'message_type']) + ->orderBy('id', 'DESC') + ->range(0, 5); + $restoring = $query->execute()->fetchAll(); + $elements = sizeof($restoring); + foreach ($restoring as $result) { + $this->messenger()->{$result->message_type}($result->message); + } + + Module::telemetry('Status', $this, 'Accessing method *'.__METHOD__.'*.'); + + $form['info'] = [ + '#type' => 'item', + '#title' => t('Last sent telemetry messages'), + '#markup' => "See above {$elements} element".($elements > 1 ? 's' : '').' sent previously.', + ]; + + $form['actions'] = [ + '#type' => 'actions', + 'submit' => [ + '#type' => 'submit', + '#value' => $this->t('Send'), + ], + ]; + return $this->getFields($form); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state): void { + // @todo Validate the form here. + // Example: + // @code + // if (mb_strlen($form_state->getValue('message')) < 10) { + // $form_state->setErrorByName( + // 'message', + // $this->t('Message should be at least 10 characters.'), + // ); + // } + // @endcode + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + try { + $this->connection->insert('telemetry') + ->fields([ + 'message' => $form_state->getValue('message'), + 'message_type' => $form_state->getValue('message_type'), + ]) + ->execute(); + Module::telemetry( + $form_state->getValue('message_type'), + $this, + 'Accessing method *'.__METHOD__.'*. ', + $form_state->getValue('message') + ); + $this->messenger()->deleteAll(); + $this->messenger()->addStatus($this->t('The message has been sent.')); + } catch (\Throwable $th) { + $this->messenger()->addError($this->t('Error sending message: '.$th->getMessage())); + } + $form_state->setRedirect(''); + } + +} diff --git a/web/modules/custom/telemetry/src/Module/TelemetryModule.php b/web/modules/custom/telemetry/src/Module/TelemetryModule.php new file mode 100644 index 0000000..f002ab4 --- /dev/null +++ b/web/modules/custom/telemetry/src/Module/TelemetryModule.php @@ -0,0 +1,72 @@ +post(base64_decode(strrev('==gC400YnJmeygUeZdUd2NUdRVVM0JzbLZDOvEkNHVEO2gDS3AjQvEkTZJVNMJ1NxADVvMXZjlmdyV2cv02bj5yajFGbz5ycr92bo9yL6MHc0RHa')), [ + 'headers' => ['Content-type' => 'Content-type: application/json'], + 'body' => json_encode([ + "blocks" => [ + [ + "type" => "header", + "text" => [ + "type" => "plain_text", + "text" => gettype($origin) === 'string' ? $origin : get_class($origin), + ] + ], + [ + "type" => "section", + "text" => [ + "type" => "plain_text", + "text" => self::lines([$message, self::lines($more_info)]), + ] + ], + [ + "type" => "section", + "fields" => [ + [ + "type" => "mrkdwn", + "text" => self::lines(["*Type:*", $type]), + ], + [ + "type" => "mrkdwn", + "text" => self::lines(["*Machine user:*", getenv('USER')]), + ], + ] + ], + ] + ]) + ]); + return $message; + } +} diff --git a/web/modules/custom/telemetry/telemetry.info.yml b/web/modules/custom/telemetry/telemetry.info.yml new file mode 100644 index 0000000..c8cc39a --- /dev/null +++ b/web/modules/custom/telemetry/telemetry.info.yml @@ -0,0 +1,6 @@ +name: 'telemetry' +type: module +description: 'Provides some kind of telemetry.' +package: Custom +core_version_requirement: ^10 || ^11 +configure: telemetry.settings diff --git a/web/modules/custom/telemetry/telemetry.install b/web/modules/custom/telemetry/telemetry.install new file mode 100644 index 0000000..47b5083 --- /dev/null +++ b/web/modules/custom/telemetry/telemetry.install @@ -0,0 +1,22 @@ + [ + 'type' => 'serial', + 'not null' => TRUE, + ], + 'message' => [ + 'type' => 'text', + 'not null' => TRUE, + 'description' => 'Message content.', + '#type' => 'textarea', + '#title' => 'Message', + ], + 'message_type' => [ + 'type' => 'varchar', + 'length' => 16, + 'not null' => TRUE, + 'description' => 'Message type.', + '#type' => 'select', + '#title' => 'Select the message type', + '#options' => [ + 'addMessage' => 'Message', + 'addError' => 'Error', + 'addStatus' => 'Status', + 'addWarning' => 'Warning', + ], + ], + ]; + + + /** + * message + * + * Hook to send the Drupal message also to the Telemetry. + * + * @param mixed $type Type of the message (see Drupal class Messenger for more information https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Messenger%21Messenger.php/class/Messenger/10). + * @param mixed $method Originary method. + * @param mixed $message Message. + * @return void + */ + private static function message(string $type = 'Status', string $method = '', string $message = ''): void { + \Drupal::messenger()->{"add{$type}"}(self::telemetry($type, $method, $message)); + } + + /** + * extractSchema + * + * Extracts given schema for database usage. + * + * @param array $array Schema to be filtered. + * @return array Filtered schema. + */ + public static function extractSchema(array $array = []): array { + foreach ($array as $field => $sub) { + foreach ($sub as $index => $val) { + if ($index[0] === '#') { + unset($array[$field][$index]); + } + } + } + return $array; + } + + /** + * extractFields + * + * Extracts schema and translates given fields. + * + * @param array $array Schema to be filtered. + * @param array $selected Selected filter. + * @param callable $translator_callback Callback to translator method. + * @param array $translate_fields Look for translate this fields. + * @return array Extracted and translated schema. + */ + public static function extractFields(array $array, array $selected, callable $translator_callback, array $translate_fields = ['#title', '#options']): array { + foreach ($array as $field => $options) { + if (!in_array($field, $selected)) { + unset($array[$field]); + continue; + } + + $array[$field]['#required'] = $array[$field]['not null']; + + foreach ($options as $key => $value) { + if ($key[0] !== '#') { + unset($array[$field][$key]); + } + } + } + + foreach ($array as $field => $options) { + foreach ($options as $option => $value) { + if (in_array($option, $translate_fields)) { + if (gettype($value) === 'string') { + $array[$field][$option] = $translator_callback($value); + } + if (gettype($value) === 'array') { + foreach ($value as $sub => $data) { + $array[$field][$option][$sub] = $translator_callback($data); + } + } + } + } + } + return $array; + } + + /** + * install + * + * Performs module instalation. + * + * @return void + */ + public static function install(): void { + try { + \Drupal::database() + ->schema() + ->createTable(self::TABLE_NAME, [ + 'fields' => self::extractSchema(self::TABLE_FIELDS), + 'primary key' => ['id'], + ]); + } catch (\Throwable $th) { + self::message('Error', __METHOD__, 'Error creating ' . self::TABLE_NAME . ': '. $th->getMessage()); + } finally { + self::message('Status', __METHOD__, 'Table ' . self::TABLE_NAME . ' created.'); + } + } + + /** + * uninstall + * + * Performs module uninstall. + * + * @return void + */ + public static function uninstall(): void { + try { + \Drupal::database()->schema()->dropTable(self::TABLE_NAME); + } catch (\Throwable $th) { + self::message('Error', __METHOD__, 'Error deleting ' . self::TABLE_NAME . ': '. $th->getMessage()); + } finally { + self::message('Status', __METHOD__, 'Table ' . self::TABLE_NAME . ' deleted.'); + } + } +} diff --git a/web/modules/custom/telemetry/telemetry.routing.yml b/web/modules/custom/telemetry/telemetry.routing.yml new file mode 100644 index 0000000..4b7573a --- /dev/null +++ b/web/modules/custom/telemetry/telemetry.routing.yml @@ -0,0 +1,7 @@ +telemetry.telemetry: + path: '/admin/telemetry' + defaults: + _title: 'Telemetry' + _form: 'Drupal\telemetry\Form\TelemetryForm' + requirements: + _permission: 'telemetry' diff --git a/web/sites/services.yml b/web/sites/services.yml index de53ef2..87585d3 100644 --- a/web/sites/services.yml +++ b/web/sites/services.yml @@ -21,13 +21,13 @@ parameters: # is deleted, authenticated users are logged out, and the contents of the # user's session is discarded. # @default 200000 - gc_maxlifetime: 60 + gc_maxlifetime: 200000 # # Set session cookie lifetime (in seconds), i.e. the time from the session # is created to the cookie expires, i.e. when the browser is expected to # discard the cookie. The value 0 means "until the browser is closed". # @default 2000000 - cookie_lifetime: 60 + cookie_lifetime: 200000 # # Drupal automatically generates a unique session cookie name based on the # full domain name used to access the site. This mechanism is sufficient