├── .gitlab-ci.yml
├── config
├── install
│ └── lite.settings.yml
└── schema
│ └── lite.schema.yml
├── lite.links.menu.yml
├── lite.info.yml
├── lite.routing.yml
├── lite.permissions.yml
├── composer.json
├── lite.libraries.yml
├── src
├── Plugin
│ ├── CKEditorPlugin
│ │ ├── DrupalLite.php
│ │ └── Lite.php
│ └── Filter
│ │ └── Lite.php
└── Form
│ └── LiteSettingsForm.php
├── css
└── lite_view.theme.css
├── js
├── plugins
│ └── drupallite
│ │ └── plugin.js
└── lite_view.js
├── README.md
├── lite.install
└── lite.module
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: drupal:8.6-apache
2 |
3 | stages:
4 | - build
5 | - test
6 | - metrics
--------------------------------------------------------------------------------
/config/install/lite.settings.yml:
--------------------------------------------------------------------------------
1 | tooltipTemplate: '%a by %u %t'
2 | tLocale: 'm d y'
3 | langcode: en
4 | extra_permissions: ''
5 | debug: false
6 |
--------------------------------------------------------------------------------
/lite.links.menu.yml:
--------------------------------------------------------------------------------
1 | lite.lite_settings_form:
2 | title: 'Lite'
3 | description: 'Configure lite plugin.'
4 | parent: system.admin_config_content
5 | route_name: lite.lite_settings_form
6 |
--------------------------------------------------------------------------------
/lite.info.yml:
--------------------------------------------------------------------------------
1 | name: Lite CKeditor track changes
2 | type: module
3 | description: "Integrates the lite track changes plugin for CKEditor with Drupal."
4 | package: Ckeditor
5 | configure: lite.lite_settings_form
6 | core: 8.x
7 | dependencies:
8 | - drupal:ckeditor
9 |
--------------------------------------------------------------------------------
/lite.routing.yml:
--------------------------------------------------------------------------------
1 | lite.lite_settings_form:
2 | path: '/admin/config/content/lite/settings'
3 | defaults:
4 | _form: '\Drupal\lite\Form\LiteSettingsForm'
5 | _title: 'Lite'
6 | requirements:
7 | _permission: 'administer filters'
8 | options:
9 | _admin_route: TRUE
10 |
--------------------------------------------------------------------------------
/lite.permissions.yml:
--------------------------------------------------------------------------------
1 | lite toggle:
2 | title: ' May always enable or disable tracking'
3 | description: 'User can enable or disable tracking on all text formats with Lite enabled.'
4 | lite resolve:
5 | title: 'May always resolve tracked changes'
6 | description: 'User may accept or reject any changes from tracking on all text formats with Lite enabled.'
7 | permission_callbacks:
8 | - lite_permissions
9 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "drupal/lite",
3 | "description": "Lite integrates the LITE track changes plugin for CKEditor with Drupal.",
4 | "type": "drupal-module",
5 | "homepage": "http://drupal.org/project/lite",
6 | "support": {
7 | "issues": "http://drupal.org/project/lite",
8 | "source": "http://cgit.drupalcode.org/lite"
9 | },
10 | "license": "GPL-2.0+",
11 | "require": {}
12 | }
13 |
--------------------------------------------------------------------------------
/config/schema/lite.schema.yml:
--------------------------------------------------------------------------------
1 | # Schema for the configuration files of the Lite module.
2 |
3 | lite.settings:
4 | type: config_object
5 | label: 'Lite settings'
6 | mapping:
7 | tooltipTemplate:
8 | type: text
9 | label: 'Tracking tooltip template'
10 | tLocale:
11 | type: text
12 | label: 'Tracking tooltip date format'
13 | extra_permissions:
14 | type: text
15 | label: 'Add extra permissions'
16 | debug:
17 | type: boolean
18 | label: 'Flag to print debug'
19 |
--------------------------------------------------------------------------------
/lite.libraries.yml:
--------------------------------------------------------------------------------
1 | lite:
2 | remote: http://ckeditor.com/addon/lite
3 | version: 1.2.28
4 | license:
5 | name: GPL
6 | url: http://www.gnu.org/copyleft/gpl.html
7 | gpl-compatible: true
8 | js:
9 | /libraries/lite/lite-interface.js: {}
10 | /libraries/lite/plugin.js: {}
11 | dependencies:
12 | - core/jquery
13 | - core/ckeditor
14 | opentip:
15 | remote: http://www.opentip.org/
16 | version: 2.4.6
17 | license:
18 | name: MIT
19 | url: https://github.com/enyo/opentip#license
20 | gpl-compatible: true
21 | js:
22 | /libraries/opentip/downloads/opentip-jquery.min.js: { minified: true }
23 | css:
24 | component:
25 | # Load css from Lite for consistency between Wysiwyg and front.
26 | /libraries/lite/css/opentip.css: {}
27 | dependencies:
28 | - core/jquery
29 | lite.view:
30 | version: VERSION
31 | js:
32 | js/lite_view.js: {}
33 | dependencies:
34 | - core/drupal
35 | - core/drupalSettings
36 | - core/jquery.once
37 | - core/jquery.ui.datepicker
38 | lite.theme:
39 | css:
40 | theme:
41 | /libraries/lite/css/lite.css: {}
42 | css/lite_view.theme.css: {}
43 |
--------------------------------------------------------------------------------
/src/Plugin/CKEditorPlugin/DrupalLite.php:
--------------------------------------------------------------------------------
1 | hasAssociatedFilterFormat()) {
48 | return FALSE;
49 | }
50 |
51 | // Automatically enable this plugin if the text format associated with this
52 | // text editor uses any lite button.
53 | $enabled = FALSE;
54 | $settings = $editor->getSettings();
55 |
56 | foreach ($settings['toolbar']['rows'] as $row) {
57 | foreach ($row as $group) {
58 | foreach ($group['items'] as $button) {
59 | if (strpos('lite-', $button) !== FALSE) {
60 | $enabled = TRUE;
61 | }
62 | }
63 | }
64 | }
65 | return $enabled;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/css/lite_view.theme.css:
--------------------------------------------------------------------------------
1 | /* Replicate lite css without .ICE-Tracking class for view display */
2 |
3 | span.ice-ins,
4 | ins.ice-ins,
5 | span.ice-del,
6 | del.ice-del {
7 | color: #000;
8 | }
9 |
10 | span.ice-del,
11 | del.ice-del {
12 | display: inline;
13 | text-decoration: line-through!important;
14 | color: #555;
15 | background-color: #e5ffcd!important;
16 | }
17 |
18 | .ice-ins,
19 | .ice-ins>* {
20 | background-color: #e5ffcd!important;
21 | }
22 |
23 | .ice-ins.ice-cts-1,
24 | .ice-ins.ice-cts-1>* {
25 | background-color: #e5ffcd!important;
26 | }
27 |
28 | .ice-ins.ice-cts-2,
29 | .ice-ins.ice-cts-2>* {
30 | background-color: #e3ffff!important;
31 | }
32 |
33 | .ice-ins.ice-cts-3,
34 | .ice-ins.ice-cts-3>* {
35 | background-color: #fdd!important;
36 | }
37 |
38 | .ice-del.ice-cts-1 {
39 | background-color: #e5ffcd!important;
40 | }
41 |
42 | .ice-del.ice-cts-2 {
43 | background-color: #e3ffff!important;
44 | }
45 |
46 | .ice-del.ice-cts-3 {
47 | background-color: #fdd!important;
48 | }
49 |
50 | /* Add some colors to the Lite default palette.*/
51 |
52 | ins.ice-ins.ice-cts-4,
53 | ins.ice-ins.ice-cts-4>*,
54 | .ICE-Tracking ins.ice-ins.ice-cts-4,
55 | .ICE-Tracking ins.ice-ins.ice-cts-4>*,
56 | del.ice-del.ice-cts-4,
57 | .ICE-Tracking del.ice-del.ice-cts-4 {
58 | background-color: #ffefcd!important;
59 | }
60 |
61 | ins.ice-ins.ice-cts-5,
62 | ins.ice-ins.ice-cts-5>* .ICE-Tracking ins.ice-ins.ice-cts-5,
63 | .ICE-Tracking ins.ice-ins.ice-cts-5>*,
64 | del.ice-del.ice-cts-5,
65 | .ICE-Tracking del.ice-del.ice-cts-5 {
66 | background-color: #cdd9ff!important;
67 | }
68 |
69 | ins.ice-ins.ice-cts-6,
70 | ins.ice-ins.ice-cts-6>* .ICE-Tracking ins.ice-ins.ice-cts-6,
71 | .ICE-Tracking ins.ice-ins.ice-cts-6>*,
72 | del.ice-del.ice-cts-6,
73 | .ICE-Tracking del.ice-del.ice-cts-6 {
74 | background-color: #ebcdff!important;
75 | }
76 |
77 | ins.ice-ins.ice-cts-7,
78 | ins.ice-ins.ice-cts-7>* .ICE-Tracking ins.ice-ins.ice-cts-7,
79 | .ICE-Tracking ins.ice-ins.ice-cts-7>*,
80 | del.ice-del.ice-cts-7,
81 | .ICE-Tracking del.ice-del.ice-cts-7 {
82 | background-color: #feffcd!important;
83 | }
84 |
85 | ins.ice-ins.ice-cts-8,
86 | ins.ice-ins.ice-cts-8>* .ICE-Tracking ins.ice-ins.ice-cts-8,
87 | .ICE-Tracking ins.ice-ins.ice-cts-8>*,
88 | del.ice-del.ice-cts-8,
89 | .ICE-Tracking del.ice-del.ice-cts-8 {
90 | background-color: #c4dab0!important;
91 | }
92 |
93 | ins.ice-ins.ice-cts-9,
94 | ins.ice-ins.ice-cts-9>* .ICE-Tracking ins.ice-ins.ice-cts-9,
95 | .ICE-Tracking ins.ice-ins.ice-cts-9>*,
96 | del.ice-del.ice-cts-9,
97 | .ICE-Tracking del.ice-del.ice-cts-9 {
98 | background-color: #c3dcdc!important;
99 | }
100 |
101 | ins.ice-ins.ice-cts-10,
102 | ins.ice-ins.ice-cts-10>* .ICE-Tracking ins.ice-ins.ice-cts-10,
103 | .ICE-Tracking ins.ice-ins.ice-cts-10>*,
104 | del.ice-del.ice-cts-10,
105 | .ICE-Tracking del.ice-del.ice-cts-10 {
106 | background-color: #e79c4c!important;
107 | }
108 |
--------------------------------------------------------------------------------
/js/plugins/drupallite/plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Drupal Lite extended plugin.
4 | *
5 | * @see https://github.com/loopindex/ckeditor-track-changes#api
6 | *
7 | * @ignore
8 | */
9 |
10 | (function ($, Drupal, drupalSettings, CKEDITOR) {
11 |
12 | 'use strict';
13 |
14 | var use_cases = ['toggle', 'resolve'];
15 | var user_can = {'toggle': false, 'resolve': false};
16 |
17 | CKEDITOR.plugins.add('drupallite', {
18 |
19 | // We are checking after init to be able to remove contextual menu items.
20 | afterInit: function (editor) {
21 |
22 | // If no option mean no plugin.
23 | if (typeof editor.config.lite == 'undefined') {
24 | return;
25 | }
26 |
27 | var config = editor.config.lite.options,
28 | permissions = editor.config.lite.permissions,
29 | extra_permissions = editor.config.lite.extra_permissions,
30 | format = null,
31 | node = null,
32 | key = '',
33 | remove_buttons = '',
34 | debug = false;
35 |
36 | if (editor.config.hasOwnProperty('drupal')) {
37 | format = editor.config.drupal.format;
38 | }
39 |
40 | // Main Lite settings from configuration or pre_render on element.
41 | if (drupalSettings.hasOwnProperty('lite')) {
42 | if (drupalSettings.lite.hasOwnProperty('node')) {
43 | node = drupalSettings.lite.node;
44 | }
45 | if (drupalSettings.lite.hasOwnProperty('debug')) {
46 | debug = drupalSettings.lite.debug;
47 | }
48 | debug && console.log(drupalSettings.lite);
49 | }
50 |
51 | debug && console.log(editor.config.lite);
52 |
53 | // Main permission check.
54 | checkPermission(key, permissions, user_can, debug);
55 |
56 | // Text format permission or moderation permission.
57 | if (format && extra_permissions == 'permissions_by_formats') {
58 | debug && console.log('Check extra permissions for text format: ' + format);
59 | key = '_' + format;
60 | checkPermission(key, permissions, user_can, debug);
61 | } else if (node && extra_permissions == 'permissions_by_states') {
62 | debug && console.log('Check extra permissions by states: ' + node.workflow + '_' + node.state);
63 | if (node.moderated) {
64 | key = '_' + node.workflow + '_' + node.state;
65 | checkPermission(key, permissions, user_can, debug);
66 | } else {
67 | debug && console.log('Node not moderated.');
68 | }
69 | }
70 |
71 | // Add toggle button to be removed.
72 | if (!user_can.toggle) {
73 | remove_buttons += 'lite-toggletracking,';
74 | }
75 |
76 | // Add accept and reject buttons to be removed.
77 | if (!user_can.resolve) {
78 | remove_buttons += 'lite-acceptall,lite-rejectall,lite-acceptone,lite-rejectone';
79 |
80 | // Disable contextual menu options.
81 | // https://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-removeMenuItem
82 | editor.removeMenuItem('lite-acceptone');
83 | editor.removeMenuItem('lite-rejectone');
84 | }
85 |
86 | // Remove toolbar buttons dynamically on the editor config.
87 | // https://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-removeButtons
88 | editor.config.removeButtons = remove_buttons;
89 | }
90 |
91 | });
92 |
93 | /**
94 | * Simple user permission check for the action key.
95 | *
96 | * @param string key
97 | * The key to check.
98 | * @param array permissions
99 | * The user permissions.
100 | * @param object user_can
101 | * The current user capability.
102 | * @param bool debug
103 | * Flag to print log message in the console.
104 | */
105 | function checkPermission(key, permissions, user_can, debug) {
106 | use_cases.forEach(function (use_case) {
107 | if (!user_can[use_case] && permissions.indexOf(use_case + key) !== -1) {
108 | debug && console.log(use_case + ' ' + key + ': GRANTED');
109 | user_can[use_case] = true;
110 | } else {
111 | debug && console.log(use_case + ' ' + key + ': REFUSED');
112 | }
113 | });
114 | }
115 |
116 | })(jQuery, Drupal, drupalSettings, CKEDITOR);
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Lite
2 | ===========
3 |
4 | Lite integrates the LITE track changes plugin for CKEditor with Drupal.
5 | https://ckeditor.com/cke4/addon/lite
6 |
7 | Installation
8 | ------------
9 |
10 | ### Composer (Recommended)
11 |
12 | * To download this module fork, Lite library and opentip, using Composer
13 | template for Drupal https://github.com/drupal-composer/drupal-project,
14 | add these lines to your repositories section on composer.json file:
15 |
16 | ```
17 | "repositories": [
18 | {
19 | "type": "composer",
20 | "url": "https://packages.drupal.org/8"
21 | },
22 | {
23 | "type": "vcs",
24 | "url": "https://github.com/Mogtofu33/lite"
25 | },
26 | {
27 | "type": "package",
28 | "package": {
29 | "name": "library/lite",
30 | "version": "1.2.28",
31 | "type": "drupal-library",
32 | "dist": {
33 | "url": "https://download.ckeditor.com/lite/releases/lite_1.2.28.zip",
34 | "type": "zip"
35 | }
36 | }
37 | },
38 | {
39 | "type": "package",
40 | "package": {
41 | "name": "library/opentip",
42 | "version": "2.4.6",
43 | "type": "drupal-library",
44 | "dist": {
45 | "url": "https://github.com/enyo/opentip/archive/v2.4.6.tar.gz",
46 | "type": "tar"
47 | }
48 | }
49 | }
50 | ],
51 | ```
52 |
53 | * Then run
54 |
55 | ```
56 | composer require "drupal/lite:2.x-dev" "library/lite:1.2.28" "library/opentip:2.4.6"
57 | ```
58 |
59 | ### Manual (Not recommended, use Composer)
60 |
61 | * Download this module in /modules folder.
62 | * Download the version **1.2.28** of the LITE CKEditor plugin from
63 | https://download.ckeditor.com/lite/releases/lite_1.2.28.zip and extract it to /libraries. The extracted
64 | folder must be named lite. So Lite plugin file can be accessed from
65 | _/libraries/lite/plugin.js_
66 | * **Optionnal** By default changes are only visible in the Wysywig editor, if you
67 | want to display changes on the _view mode_ with _tooltip_ support you must install
68 | **Opentip** library.
69 | Create a folder opentip in your /libraries folder.
70 | From https://github.com/enyo/opentip/archive/v2.4.6.tar.gz, download
71 | **opentip-jquery.min.js** and place it in /libraries/opentip, so your file can
72 | be accessed from
73 | _/libraries/opentip/downloads/opentip-jquery.min.js_
74 | _Note_: you can remove all files from archive excepting downloads/opentip-jquery.min.js
75 |
76 | ### Post intallation
77 |
78 | * Enable the module
79 | * Visit status report to ensure your Lite plugin is correctly loaded.
80 | * Enable any of the track changes buttons by dragging them into the active
81 | toolbar configuration for the desired text formats from the Text Formats
82 | configuration page (For example on /admin/config/content/formats/manage/basic_html).
83 | * Configure Lite options below **CKEditor plugin settings**
84 | * Enable the _Lite changes tracking_ under **Enabled filters**
85 | * If the **Limit allowed HTML tags and correct faulty HTML** filter is enabled, add or replace to the **Allowed HTML tags** under the **Filter settings** :
86 | ```
87 | ' . t('Lite integrates the LITE track changes plugin for CKEditor with Drupal.', [':url' => 'https://ckeditor.com/addon/lite']);
22 | $output .= ' ';
24 | $output .= t('You should create a specific text format and probably a content type and roles for easiest configuration of Lite.');
25 | $output .= ' ';
28 | $output .= t('From the text format settings you can enable tracking by default on Lite options. If the role do not have the permission provided by this module to toggle tracking, it will be forced and the toggle button will not be available.');
29 | $output .= ' ';
35 | $output .= t('If the Limit allowed HTML tags filter is enabled, add to the Allowed HTML tags:');
36 | $output .= '
88 | ```
89 | * Configure the Lite options under **filter settings**
90 |
91 | Configuration
92 | ------------
93 |
94 | Lite plugin default state settings are available from the text format
95 | configuration form.
96 |
97 | After the installation, you can configure specific options form
98 | /admin/config/content/lite/settings
99 |
100 | Some global permissions to allow roles to toggle or resolve changes for all text formats can be set from _People > Permissions_ (/admin/people/permissions#module-lite)
101 |
102 | Content moderation
103 | ------------
104 |
105 | If the Drupal Content Moderation module is enabled, Lite text format option by
106 | Workflow and by states will be available.
107 |
108 | Prior to text format configuration you must enable a Workflow on your content
109 | type and set the Workflow transitions permissions to your roles accordingly.
110 |
111 | Known issues
112 | ------------
113 |
114 | Lite **1.2.30** can cause an issue with images or copy/paste, see
115 | https://www.drupal.org/node/2907869
116 |
117 | If an image is added without any text it should be not tracked.
118 |
119 | If image caption is enable, the image will not be tracked.
120 |
--------------------------------------------------------------------------------
/lite.install:
--------------------------------------------------------------------------------
1 | t('LITE CKEditor plugin'),
20 | 'value' => $has_lite_version ? t('Detected version :version', [':version' => $has_lite_version]) : t('Not found'),
21 | ];
22 |
23 | if (!$has_lite_version) {
24 | $requirements['lite_library']['severity'] = REQUIREMENT_ERROR;
25 | $requirements['lite_library']['description'] = [
26 | '#prefix' => ' ',
27 | '#markup' => t('Lite module requires LITE plugin library 1.2.28. See README.md file for instructions.'),
28 | ];
29 | }
30 | else {
31 | // Check version installed.
32 | if ($has_lite_version < '1.2.28') {
33 | $requirements['lite_library']['severity'] = REQUIREMENT_WARNING;
34 | $requirements['lite_library']['description'] = [
35 | '#prefix' => ' ',
36 | '#markup' => t('Lite version detected :version outdated, please install version 1.2.28.', [':version' => $has_lite_version, ':url' => 'https://download.ckeditor.com/lite/releases/lite_1.2.28.zip']),
37 | ];
38 | }
39 | // Force version 1.2.28 as 1.2.30 is currently not working properly
40 | // see issue https://www.drupal.org/node/2907869
41 | elseif ($has_lite_version > '1.2.28') {
42 | $requirements['lite_library']['severity'] = REQUIREMENT_ERROR;
43 | $requirements['lite_library']['description'] = [
44 | '#prefix' => ' ',
45 | '#markup' => t('Lite version detected :version not supported, please install version 1.2.28.', [':version' => $has_lite_version, ':url' => 'https://download.ckeditor.com/lite/releases/lite_1.2.28.zip']),
46 | ];
47 | }
48 | }
49 |
50 | $has_opentip = _lite_verify_library('lite', 'opentip');
51 | $requirements['opentip_library'] = [
52 | 'title' => t('Opentip plugin'),
53 | 'value' => $has_opentip ? t('Enabled') : t('Not found'),
54 | ];
55 |
56 | if (!$has_opentip) {
57 | $requirements['opentip_library']['severity'] = REQUIREMENT_WARNING;
58 | $requirements['opentip_library']['description'] = [
59 | '#prefix' => ' ',
60 | '#markup' => t('OpenTip is recommended if you want to show changes in view mode for Lite track changes. See README.md file for instructions.'),
61 | ];
62 | }
63 | }
64 |
65 | return $requirements;
66 | }
67 |
68 | /**
69 | * Verify that the library files exist.
70 | *
71 | * @param string $extension
72 | * The name of the extension that registered a library.
73 | * @param string $name
74 | * The name of a registered library to retrieve.
75 | *
76 | * @return bool
77 | * TRUE if all files of this library exists, FALSE otherwise
78 | *
79 | * @see https://drupal.org/node/2231385
80 | */
81 | function _lite_verify_library($extension, $name, $check_version = FALSE) {
82 | /** @var Drupal\Core\Asset\LibraryDiscovery $library_discovery */
83 | $library_discovery = \Drupal::service('library.discovery');
84 | $library = $library_discovery->getLibraryByName($extension, $name);
85 |
86 | $exist = TRUE;
87 | if ($library['js']) {
88 | foreach ($library['js'] as $js) {
89 | if ($js['type'] == 'file') {
90 | if (!file_exists(DRUPAL_ROOT . '/' . $js['data'])) {
91 | $exist = FALSE;
92 | }
93 | }
94 | }
95 | }
96 |
97 | if ($library['css']) {
98 | foreach ($library['css'] as $css) {
99 | if ($css['type'] == 'file') {
100 | if (!file_exists(DRUPAL_ROOT . '/' . $css['data'])) {
101 | $exist = FALSE;
102 | }
103 | }
104 | }
105 | }
106 |
107 | // Return the version number instead of bool.
108 | if ($exist && $check_version) {
109 | return _lite_libraries_get_lite_version();
110 | }
111 |
112 | return $exist;
113 | }
114 |
115 | /**
116 | * Gets the version information for LIte plugin library.
117 | *
118 | * @return string|bool
119 | * A string containing the version of the library.
120 | */
121 | function _lite_libraries_get_lite_version() {
122 | // Provide options for Lite.
123 | $options = [
124 | // /* Source version: 1.2.28 */.
125 | 'pattern' => '/Source version: (\d+\.\d+\.\d+)/',
126 | 'lines' => 1,
127 | 'cols' => 30,
128 | ];
129 |
130 | $file = DRUPAL_ROOT . '/libraries/lite/plugin.js';
131 | if (!file_exists($file)) {
132 | return FALSE;
133 | }
134 | $file = fopen($file, 'r');
135 | while ($options['lines'] && $line = fgets($file, $options['cols'])) {
136 | if (preg_match($options['pattern'], $line, $version)) {
137 | fclose($file);
138 | return $version[1];
139 | }
140 | $options['lines']--;
141 | }
142 | fclose($file);
143 | }
144 |
--------------------------------------------------------------------------------
/src/Form/LiteSettingsForm.php:
--------------------------------------------------------------------------------
1 | urlGenerator = $url_generator;
47 | $this->moduleHandler = $module_handler;
48 | }
49 |
50 | /**
51 | * {@inheritdoc}
52 | */
53 | public static function create(ContainerInterface $container) {
54 | return new static(
55 | $container->get('config.factory'),
56 | $container->get('url_generator'),
57 | $container->get('module_handler')
58 | );
59 | }
60 |
61 | /**
62 | * {@inheritdoc}
63 | */
64 | protected function getEditableConfigNames() {
65 | return ['lite.settings'];
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | */
71 | public function getFormId() {
72 | return 'lite_settings_form';
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | public function buildForm(array $form, FormStateInterface $form_state) {
79 | $config = $this->config('lite.settings');
80 |
81 | $options = [
82 | '' => $this->t('- Select -'),
83 | 'permissions_by_formats' => $this->t('Add permissions by text formats'),
84 | ];
85 | if ($this->moduleHandler->moduleExists('content_moderation')) {
86 | $options['permissions_by_states'] = $this->t('Add permissions by Workflow states');
87 | }
88 | $params = [
89 | ':url' => $this->urlGenerator->generateFromRoute('user.admin_permissions',
90 | [],
91 | ['fragment' => 'module-lite']),
92 | ];
93 | $form['extra_permissions'] = [
94 | '#type' => 'select',
95 | '#title' => $this->t('Add more permissions'),
96 | '#description' => $this->t('Before changing or removing extra permission here, be sure to uncheck existing permissions on the permissions administration.
This option bring more complexity to the configuration of this module.', $params),
97 | '#options' => $options,
98 | '#default_value' => $config->get('extra_permissions'),
99 | ];
100 |
101 | $form['tooltipTemplate'] = [
102 | '#title' => $this->t('Tooltip template'),
103 | '#type' => 'textarea',
104 | '#description' => $this->t('Allow a custom template to use with Lite plugin, allow some HTML tags.
Available variables:
105 | %a The action, "added" or "deleted"
106 | %t Timestamp of the first edit action in this change span (e.g. "now", "3 minutes ago", "August 15 1972")
107 | %u the name of the user who made the change
108 | %dd double digit date of change, e.g. 02
109 | %d date of change, e.g. 2
110 | %mm double digit month of change, e.g. 09
111 | %m month of change, e.g. 9
112 | %yy double digit year of change, e.g. 11
113 | %y full month of change, e.g. 2011
114 | %nn double digit minutes of change, e.g. 09
115 | %n minutes of change, e.g. 9
116 | %hh double digit hour of change, e.g. 05
117 | %h hour of change, e.g. 5
118 | '),
119 | '#default_value' => $config->get('tooltipTemplate'),
120 | ];
121 |
122 | $form['tLocale'] = [
123 | '#title' => $this->t('Tooltip %t format'),
124 | '#type' => 'textfield',
125 | '#description' => $this->t('If using %t wildcard on tooltip template, when displaying full date you can localize date format.
See jQuery Datepicker formatDate for available formats.', [':url' => 'http://api.jqueryui.com/datepicker/#utility-formatDate']),
126 | '#default_value' => $config->get('tLocale'),
127 | ];
128 |
129 | $form['debug'] = [
130 | '#title' => $this->t('Enable debug'),
131 | '#description' => $this->t('Display information on the javascript console of the browser when using the Wysiwyg with Lite to ease configuration.'),
132 | '#type' => 'checkbox',
133 | '#default_value' => $config->get('debug'),
134 | ];
135 |
136 | return parent::buildForm($form, $form_state);
137 | }
138 |
139 | /**
140 | * {@inheritdoc}
141 | */
142 | public function submitForm(array &$form, FormStateInterface $form_state) {
143 | parent::submitForm($form, $form_state);
144 |
145 | $this->config('lite.settings')
146 | ->set('tooltipTemplate', $form_state->getValue('tooltipTemplate'))
147 | ->set('tLocale', $form_state->getValue('tLocale'))
148 | ->set('extra_permissions', $form_state->getValue('extra_permissions'))
149 | ->set('debug', $form_state->getValue('debug'))
150 | ->save();
151 | }
152 |
153 | }
154 |
--------------------------------------------------------------------------------
/js/lite_view.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Some basic behaviors and utility functions for Linkit.
4 | */
5 |
6 | (function ($, Drupal, drupalSettings) {
7 |
8 | 'use strict';
9 |
10 | Drupal.lite = Drupal.lite || {};
11 |
12 | // We use jQueryUI datepicker shipped with Drupal to format dates.
13 | var datePicker = $.datepicker.setDefaults($.datepicker.regional[drupalSettings.path.currentLanguage]);
14 |
15 | /**
16 | * Process ins and del produced by Lite plugin in the editor.
17 | *
18 | * @type {Drupal~behavior}
19 | *
20 | * @prop {Drupal~behaviorAttach} attach
21 | * Attaches lite behaviour to the Lite markup.
22 | */
23 | Drupal.behaviors.lite = {
24 | attach: function (context, settings) {
25 |
26 | // Check opentip is here.
27 | if (typeof Opentip === "undefined") {
28 | return;
29 | }
30 |
31 | // Prepare tooltip template.
32 | var tooltipTemplate = drupalSettings.lite.tooltipTemplate.replace(/\%/g, '@');
33 |
34 | $('.ice-ins, .ice-del', context).once('liteViewProcessed').each(function () {
35 | var $change = $(this);
36 | var title = Drupal.lite.makeTooltipTitle(tooltipTemplate, $change);
37 | new Opentip($change, title);
38 | });
39 |
40 | }
41 | };
42 |
43 | /**
44 | * Lite helpers functions adapatted from Lite plugin.
45 | *
46 | * These functions are just Drupal rewriten function of lite:
47 | * https://github.com/loopindex/ckeditor-track-changes/blob/master/src/lite/plugin.js.
48 | *
49 | * @namespace
50 | */
51 | Drupal.lite = {
52 |
53 | /**
54 | * Create tooltip title replicating what's done in Lite plugin.js.
55 | *
56 | * Src/lite/plugin.js#L1823
57 | *
58 | * @param {string} title
59 | * The title template fromt Lite settings.
60 | * @param {jQuery} $elem
61 | * A jQuery element containing Lite tag ins or del.
62 | *
63 | * @return {string}
64 | * The message processed through translation.
65 | */
66 | makeTooltipTitle: function (title, $elem) {
67 | // Build base object based on element data.
68 | var change = {
69 | 'type': $elem.prop("tagName"),
70 | 'time': parseInt($elem.attr('data-time')),
71 | 'lastTime': parseInt($elem.attr('data-last-change-time')),
72 | 'userName': $elem.attr('data-username')
73 | };
74 |
75 | // Prepare variable to replace in text.
76 | var time = new Date(change.time),
77 | lastTime = new Date(change.lastTime),
78 | params = {
79 | '@a': ("INS" === change.type) ? Drupal.t('added') : Drupal.t('deleted'),
80 | '@t': Drupal.lite.relativeDateFormat(time),
81 | '@u': change.userName,
82 | '@dd': datePicker.formatDate("d", time),
83 | '@d': time.getDate(),
84 | '@mm': datePicker.formatDate("mm", time),
85 | '@m': time.getMonth() + 1,
86 | '@yy': datePicker.formatDate("y", time),
87 | '@y': time.getFullYear(),
88 | '@nn': Drupal.lite.padNumber(time.getMinutes(), 2),
89 | '@n': time.getMinutes(),
90 | '@hh': Drupal.lite.padNumber(time.getHours(), 2),
91 | '@h': time.getHours(),
92 | '@T': Drupal.lite.relativeDateFormat(lastTime),
93 | '@DD': datePicker.formatDate("d", lastTime),
94 | '@D': lastTime.getDate(),
95 | '@MM': datePicker.formatDate("mm", lastTime),
96 | '@M': lastTime.getMonth() + 1,
97 | '@YY': datePicker.formatDate("y", lastTime),
98 | '@Y': lastTime.getFullYear(),
99 | '@NN': Drupal.lite.padNumber(lastTime.getMinutes(), 2),
100 | '@N': lastTime.getMinutes(),
101 | '@HH': Drupal.lite.padNumber(lastTime.getHours(), 2),
102 | '@H': lastTime.getHours(),
103 | };
104 |
105 | // Build text message using params replacment.
106 | return Drupal.t(title, params);
107 | },
108 |
109 | /**
110 | * Transform date to a relative format. From src/lite/plugin.js#L258.
111 | *
112 | * @param {object} date
113 | * The date object.
114 | *
115 | * @return {string}
116 | * The relative format string.
117 | */
118 | relativeDateFormat: function (date) {
119 | var now = new Date(),
120 | today = now.getDate(),
121 | month = now.getMonth(),
122 | year = now.getFullYear(),
123 | minutes;
124 |
125 | var t = typeof date;
126 |
127 | if (t === "string" || t === "number") {
128 | date = new Date(date);
129 | }
130 |
131 | // Today.
132 | if (today == date.getDate() && month == date.getMonth() && year == date.getFullYear()) {
133 | minutes = Math.floor((now.getTime() - date.getTime()) / 60000);
134 |
135 | if (minutes < 1) {
136 | return Drupal.t('now');
137 | } else if (minutes < 60) {
138 | return Drupal.formatPlural(minutes, '1 minute ago', '@count minutes ago');
139 | } else {
140 | return Drupal.t('on') + " " + Drupal.lite.padNumber(date.getHours(), 2) + ":" + Drupal.lite.padNumber(date.getMinutes(), 2);
141 | }
142 | // This year.
143 | } else if (year == date.getFullYear()) {
144 | // We remove year in the result.
145 | return Drupal.t('on') + " " + datePicker.formatDate(drupalSettings.lite.tLocale.replace(/y/g, ''), date);
146 | } else {
147 | return Drupal.t('on') + " " + datePicker.formatDate(drupalSettings.lite.tLocale, date);
148 | }
149 | },
150 |
151 | /**
152 | * Wrapper helper to pad number.
153 | *
154 | * @param {string} s
155 | * The string to process.
156 | * @param {integer} length
157 | * The pad offset.
158 | *
159 | * @return {string}
160 | * The padded number.
161 | */
162 | padNumber: function (s, length) {
163 | return Drupal.lite.padString(s, length, '0');
164 | },
165 |
166 | /**
167 | * Pad a string.
168 | *
169 | * @param {string} s
170 | * The string to process.
171 | * @param {integer} length
172 | * The pad offset.
173 | * @param {integer} padWith
174 | * The pad width.
175 | *
176 | * @return {string}
177 | * The padded string.
178 | */
179 | padString: function (s, length, padWith) {
180 | if (null === s || (typeof(s) === "undefined")) {
181 | s = "";
182 | } else {
183 | s = String(s);
184 | }
185 | padWith = String(padWith);
186 | var padLength = padWith.length;
187 | for (var i = s.length; i < length; i += padLength) {
188 | s = padWith + s;
189 | }
190 | return s;
191 | }
192 |
193 | };
194 |
195 | })(jQuery, Drupal, drupalSettings);
196 |
--------------------------------------------------------------------------------
/src/Plugin/Filter/Lite.php:
--------------------------------------------------------------------------------
1 | t('Show changes on view mode'),
33 | '#description' => $this->t('Display track changes in view mode with tooltips. If disable track changes will be visible only when editing.'),
34 | '#type' => 'checkbox',
35 | '#default_value' => $this->settings['view'],
36 | '#attributes' => [
37 | 'data-editor-lite' => 'view',
38 | ],
39 | '#states' => [
40 | 'visible' => [
41 | ':input[data-editor-lite="clean"]' => ['checked' => FALSE],
42 | ],
43 | ],
44 | ];
45 | $form['clean'] = [
46 | '#title' => t('Clean empty markup when hiding changes'),
47 | '#description' => $this->t('If track chnages are not displayed on view mode, when a change or a list of changes is included in a list or a container tag, changes will be hidden but not the container tag. In some cases, like for list or Blockquote, an empty markup will be visible. Use this option to remove empty tags.'),
48 | '#type' => 'checkbox',
49 | '#default_value' => $this->settings['clean'],
50 | '#attributes' => [
51 | 'data-editor-lite' => 'clean',
52 | ],
53 | '#states' => [
54 | 'visible' => [
55 | ':input[data-editor-lite="view"]' => ['checked' => FALSE],
56 | ],
57 | ],
58 | ];
59 | $form['list'] = [
60 | '#type' => 'textfield',
61 | '#title' => t('Empty HTML tags to clean'),
62 | '#default_value' => $this->settings['list'],
63 | '#description' => $this->t('Space separated list of markup to clean, for example lists or blockquote. Be warned it will remove all empty markup even if it is not related to track changes.'),
64 | '#states' => [
65 | 'visible' => [
66 | ':input[data-editor-lite="clean"]' => ['checked' => TRUE],
67 | ':input[data-editor-lite="view"]' => ['checked' => FALSE],
68 | ],
69 | ],
70 | ];
71 | return $form;
72 | }
73 |
74 | /**
75 | * {@inheritdoc}
76 | */
77 | public function process($text, $langcode) {
78 | $result = new FilterProcessResult($text);
79 |
80 | // Show changes on view mode, we need our libraries.
81 | if ($this->settings['view']) {
82 | $config = \Drupal::config('lite.settings');
83 | // $date = \Drupal::config('core.date_format.long');.
84 | $result->setAttachments([
85 | 'library' => ['lite/opentip', 'lite/lite.view', 'lite/lite.theme'],
86 | 'drupalSettings' => [
87 | 'lite' => [
88 | 'tooltipTemplate' => $config->get('tooltipTemplate'),
89 | 'tLocale' => $config->get('tLocale'),
90 | ],
91 | ],
92 | ]);
93 | }
94 | // Or clean and process markup.
95 | elseif (strpos($text, 'ice-ins') !== FALSE || strpos($text, 'ice-del') !== FALSE) {
96 | $result->setProcessedText($this->processChanges($text));
97 | }
98 |
99 | return $result;
100 | }
101 |
102 | /**
103 | * {@inheritdoc}
104 | */
105 | public function tips($long = FALSE) {
106 | return $this->t('Track changes will be use for this content if enable.');
107 | }
108 |
109 | /**
110 | * Process a markup to replace and markup.
111 | *
112 | * @param string $text
113 | * The text string to be filtered.
114 | *
115 | * @return string
116 | * Text processed.
117 | */
118 | private function processChanges($text) {
119 | $document = Html::load($text);
120 | $xpath = new \DOMXPath($document);
121 | $has_deleted_markup = FALSE;
122 |
123 | // Remove proposed insertions with the "ice-ins" class.
124 | foreach ($xpath->query("//ins[contains(concat(' ', normalize-space(@class), ' '), ' ice-ins ')]") as $node) {
125 | /** @var \DOMElement $node */
126 | $node->parentNode->removeChild($node);
127 | }
128 |
129 | // Keep proposed deletions with the "ice-del" class but remove markers.
130 | foreach ($xpath->query("//del[contains(concat(' ', normalize-space(@class), ' '), ' ice-del ')]") as $node) {
131 | /** @var \DOMElement $node */
132 | $text = $node->textContent;
133 | $this->replaceContent($node, $text);
134 | $has_deleted_markup = TRUE;
135 | }
136 |
137 | // Delete empty markup depending settings.
138 | if ($has_deleted_markup && $this->settings['clean']) {
139 | // Build query for tags to delete if empty.
140 | $xpath_query = NULL;
141 | foreach (explode(" ", $this->settings['list']) as $tag) {
142 | $xpath_query[] = "//" . $tag . "[not(normalize-space())]";
143 | }
144 | if ($xpath_query) {
145 | foreach ($xpath->query(implode(" | ", $xpath_query)) as $node) {
146 | /** @var \DOMElement $node */
147 | $node->parentNode->removeChild($node);
148 | }
149 | }
150 | }
151 |
152 | return Html::serialize($document);
153 | }
154 |
155 | /**
156 | * Replace the contents of a DOMNode.
157 | *
158 | * @param \DOMNode $node
159 | * A DOMNode object.
160 | * @param string $content
161 | * The text or HTML that will replace the contents of $node.
162 | */
163 | private function replaceContent(\DOMNode &$node, $content) {
164 | if (strlen($content)) {
165 | // Load the content into a new DOMDocument and retrieve the DOM nodes.
166 | $replacement_nodes = Html::load($content)->getElementsByTagName('body')
167 | ->item(0)
168 | ->childNodes;
169 | }
170 | else {
171 | $replacement_nodes = [$node->ownerDocument->createTextNode('')];
172 | }
173 |
174 | foreach ($replacement_nodes as $replacement_node) {
175 | // Import the replacement node from the new DOMDocument into the original
176 | // one, importing also the child nodes of the replacement node.
177 | $replacement_node = $node->ownerDocument->importNode($replacement_node, TRUE);
178 | $node->parentNode->insertBefore($replacement_node, $node);
179 | }
180 | $node->parentNode->removeChild($node);
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/src/Plugin/CKEditorPlugin/Lite.php:
--------------------------------------------------------------------------------
1 | currentUser = $current_user;
89 | $this->permissionsHandler = $permissions_handler;
90 | $this->moduleHandler = $module_handler;
91 | $this->urlGenerator = $url_generator;
92 | $this->entityTypeManager = $entity_manager;
93 |
94 | parent::__construct($configuration, $plugin_id, $plugin_definition);
95 | }
96 |
97 | /**
98 | * Creates an instance of the plugin.
99 | *
100 | * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
101 | * The container to pull out services used in the plugin.
102 | * @param array $configuration
103 | * A configuration array containing information about the plugin instance.
104 | * @param string $plugin_id
105 | * The plugin ID for the plugin instance.
106 | * @param mixed $plugin_definition
107 | * The plugin implementation definition.
108 | *
109 | * @return static
110 | * Returns an instance of this plugin.
111 | */
112 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
113 | return new static(
114 | $configuration,
115 | $plugin_id,
116 | $plugin_definition,
117 | $container->get('current_user'),
118 | $container->get('user.permissions'),
119 | $container->get('module_handler'),
120 | $container->get('url_generator'),
121 | $container->get('entity_type.manager')
122 | );
123 | }
124 |
125 | /**
126 | * {@inheritdoc}
127 | */
128 | public function getFile() {
129 | return base_path() . 'libraries/lite/plugin.js';
130 | }
131 |
132 | /**
133 | * {@inheritdoc}
134 | */
135 | public function getDependencies(Editor $editor) {
136 | return [
137 | 'drupallite',
138 | ];
139 | }
140 |
141 | /**
142 | * {@inheritdoc}
143 | */
144 | public function getConfig(Editor $editor) {
145 | $lite_settings = \Drupal::config('lite.settings');
146 | $settings = $editor->getSettings();
147 |
148 | $config['lite'] = [
149 | // Lite settings, see http://www.loopindex.com/lite/docs/
150 | 'userId' => $this->currentUser->id(),
151 | 'userName' => $this->currentUser->getDisplayName(),
152 | 'tooltipTemplate' => $lite_settings->get('tooltipTemplate'),
153 | // 'tooltips' => FALSE,
154 | // Custom settings for this plugin.
155 | 'permissions' => $this->getUserPermissions(),
156 | 'extra_permissions' => $lite_settings->get('extra_permissions'),
157 | 'options' => isset($settings['plugins']['lite']) ? $settings['plugins']['lite'] : [],
158 | ];
159 |
160 | return $config;
161 | }
162 |
163 | /**
164 | * {@inheritdoc}
165 | */
166 | public function getButtons() {
167 | $path = base_path() . 'libraries/lite';
168 |
169 | return [
170 | 'lite-acceptall' => [
171 | 'label' => t('Accept all changes'),
172 | 'image' => $path . '/icons/lite-acceptall.png',
173 | ],
174 | 'lite-rejectall' => [
175 | 'label' => t('Reject all changes'),
176 | 'image' => $path . '/icons/lite-rejectall.png',
177 | ],
178 | 'lite-acceptone' => [
179 | 'label' => t('Accept change'),
180 | 'image' => $path . '/icons/lite-acceptone.png',
181 | ],
182 | 'lite-rejectone' => [
183 | 'label' => t('Reject change'),
184 | 'image' => $path . '/icons/lite-rejectone.png',
185 | ],
186 | 'lite-toggleshow' => [
187 | 'label' => t('Show/hide tracked changes'),
188 | 'image' => $path . '/icons/lite-toggleshow.png',
189 | ],
190 | 'lite-toggletracking' => [
191 | 'label' => t('Start/stop tracking changes'),
192 | 'image' => $path . '/icons/lite-toggletracking.png',
193 | ],
194 | ];
195 | }
196 |
197 | /**
198 | * {@inheritdoc}
199 | */
200 | public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) {
201 | $settings = $editor->getSettings();
202 | $config = [];
203 |
204 | if (isset($settings['plugins']['lite'])) {
205 | $config = $settings['plugins']['lite'];
206 | }
207 |
208 | if ($this->moduleHandler->moduleExists('help')) {
209 | $params = [
210 | ':help' => $this->urlGenerator->generateFromRoute('help.page', ['name' => 'lite']),
211 | ];
212 | $form['help'] = [
213 | '#markup' => $this->t('
See the help for more information on these options.', $params),
214 | ];
215 | }
216 |
217 | $form['auto_start'] = [
218 | '#title' => t('Enable tracking changes by default'),
219 | '#description' => t('Enable Lite tracking when the editor is loaded with this text format.'),
220 | '#type' => 'checkbox',
221 | '#default_value' => isset($config['auto_start']) ? $config['auto_start'] : 1,
222 | '#attributes' => [
223 | 'data-editor-lite' => 'auto_start',
224 | ],
225 | ];
226 |
227 | $form['disable_new'] = [
228 | '#title' => t('Do not start tracking on New entity'),
229 | '#description' => t('Prevent users from becoming confused if their initial content does not show up after saving the entity without accepting any change.'),
230 | '#type' => 'checkbox',
231 | '#default_value' => isset($config['disable_new']) ? $config['disable_new'] : 0,
232 | ];
233 |
234 | if ($this->moduleHandler->moduleExists('content_moderation')) {
235 | $params = [
236 | ':url' => $this->urlGenerator->generateFromRoute('entity.workflow.collection'),
237 | ':url_lite' => $this->urlGenerator->generateFromRoute('lite.lite_settings_form'),
238 | ];
239 | $form['moderation'] = [
240 | '#title' => t('Enable content moderation support'),
241 | '#description' => $this->t('Extend Lite options by Workflows states for this text format. Can be extended using the permissions by states.', $params),
242 | '#type' => 'checkbox',
243 | '#default_value' => isset($config['moderation']) ? $config['moderation'] : 0,
244 | '#attributes' => [
245 | 'data-editor-lite-moderation' => 'enable',
246 | ],
247 | ];
248 | $form['moderation_options'] = [
249 | '#type' => 'container',
250 | '#states' => [
251 | 'visible' => [
252 | ':input[data-editor-lite-moderation="enable"]' => ['checked' => TRUE],
253 | ],
254 | ],
255 | ];
256 |
257 | /* @var \Drupal\workflows\WorkflowInterface[] $workflows */
258 | $workflows = $this->entityTypeManager->getStorage('workflow')->loadMultiple();
259 | $options = array_map(function (WorkflowInterface $workflow) {
260 | return $workflow->label();
261 | }, array_filter($workflows, function (WorkflowInterface $workflow) {
262 | return $workflow->status() && $workflow->getTypePlugin() instanceof ContentModeration;
263 | }));
264 |
265 | if (count($options)) {
266 | foreach ($options as $worflow_id => $label) {
267 |
268 | $form['moderation_options'][$worflow_id] = [
269 | '#title' => t(':label', [':label' => $label]),
270 | '#type' => 'details',
271 | ];
272 |
273 | $form['moderation_options'][$worflow_id]['enable'] = [
274 | '#title' => t('Override options for this Workflow'),
275 | '#type' => 'checkbox',
276 | '#default_value' => isset($config['moderation_options'][$worflow_id]['enable']) ? $config['moderation_options'][$worflow_id]['enable'] : 0,
277 | '#attributes' => [
278 | 'data-editor-lite-moderation' => $worflow_id,
279 | ],
280 | ];
281 |
282 | $states = [];
283 | $workflow = Workflow::load($worflow_id);
284 | // Drupal 8.3 or Drupal 8.4.
285 | if (method_exists($workflow, 'getstates')) {
286 | $states = $workflow->getStates();
287 | }
288 | elseif (method_exists($workflow->getTypePlugin(), 'getstates')) {
289 | $states = $workflow->getTypePlugin()->getStates();
290 | }
291 |
292 | foreach ($states as $state) {
293 | $state_id = $state->id();
294 | $state_label = $state->label();
295 | if (isset($config['moderation_options'][$worflow_id][$state_id]['auto_start'])) {
296 | $default = $config['moderation_options'][$worflow_id][$state_id]['auto_start'];
297 | }
298 | else {
299 | $default = 0;
300 | }
301 | $form['moderation_options'][$worflow_id][$state_id]['auto_start'] = [
302 | '#title' => t('%state: enable tracking changes by default', ['%state' => $state_label]),
303 | '#type' => 'checkbox',
304 | '#default_value' => $default,
305 | '#states' => [
306 | 'visible' => [
307 | ':input[data-editor-lite-moderation="' . $worflow_id . '"]' => ['checked' => TRUE],
308 | ],
309 | ],
310 | ];
311 |
312 | }
313 | // Open or close if we have this workflow enabled.
314 | if (isset($config['moderation_options'][$worflow_id]['enable']) && $config['moderation_options'][$worflow_id]['enable'] == 1) {
315 | $form['moderation_options'][$worflow_id]['#open'] = TRUE;
316 | }
317 | }
318 | }
319 | }
320 |
321 | return $form;
322 | }
323 |
324 | /**
325 | * Return user permissions filtered to this module.
326 | *
327 | * @return array
328 | * List of lite permissions without spaces.
329 | */
330 | private function getUserPermissions() {
331 | $permissions = $user_permissions = [];
332 |
333 | $roles = $this->currentUser->getRoles();
334 |
335 | // Specific Admin case, because Drupal return an empty array we need all
336 | // permissions if one of the role is admin.
337 | foreach ($roles as $role) {
338 | $role = Role::load($role);
339 | if ($role->isAdmin()) {
340 | $user_permissions = array_keys($this->permissionsHandler->getPermissions());
341 | continue;
342 | }
343 | }
344 |
345 | // For a regular user we load permissions.
346 | if (!count($user_permissions)) {
347 | $all_user_permissions = user_role_permissions($roles);
348 | if (count($all_user_permissions)) {
349 | $user_permissions = array_merge($all_user_permissions, $all_user_permissions);
350 | $user_permissions = end($user_permissions);
351 | }
352 | }
353 |
354 | // We filter permissions to get only lite related to be used by our plugin.
355 | foreach ($user_permissions as $permission) {
356 | if (strpos($permission, 'lite') !== FALSE) {
357 | $permissions[] = str_replace(['lite ', ' '], ['', '_'], $permission);
358 | }
359 | }
360 | return $permissions;
361 | }
362 |
363 | }
364 |
--------------------------------------------------------------------------------
/lite.module:
--------------------------------------------------------------------------------
1 | ' . t('About') . '';
21 | $output .= '' . t('Uses') . '
';
23 | $output .= '' . t('Basic usage') . '
';
27 | $output .= '
' . t('Users still can avoid tracking even when forced if they got access to the source button or if they can select an other text format without Lite enable.');
30 | $output .= '
' . t('Module allowed formats can help you force tracking on a content type.', [':url' => 'https://www.drupal.org/project/allowed_formats']);
31 | $output .= '
' . t('Note that currently Drupal Image is not well supported. If the image is added alone it should not be tracked and if caption is enable the image will never be tracked.');
32 | $output .= '
' . t('This module permissions can restrict accept / reject changes to specific roles.');
33 | $output .= '<del class="ice-del ice-cts-*" data-changedata data-userid data-cid data-last-change-time data-time data-username> <ins class="ice-ins ice-cts-*" data-changedata data-userid data-cid data-last-change-time data-time data-username>
';
37 | $output .= '
' . t('You can enable extra permissions by text formats to get a more fine grain permissions. Note that main permissions always override permissions by formats.', [':url' => $url->generate('lite.lite_settings_form')]) . '
'; 40 | $output .= '';
42 | $output .= t('If the Drupal Content Moderation module is enabled, Lite text format option by Workflow and by states will be available.', [':url' => 'https://www.drupal.org/docs/8/core/modules/content-moderation']);
43 | $output .= '
' . t('Prior to text format configuration you must enable a Workflow on your content type and set the Workflow transitions permissions to your roles accordingly.');
44 | $output .= '
';
46 | $output .= t('The Lite text format settings will allow a fine grain configuration of the Auto tracking by Workflow and by States.');
47 | $output .= '
' . t('You must ensure related roles got permission to access enabled content moderation states.');
48 | $output .= '
' . t('With Content Moderation module enable, you can enable extra permissions by states to get a more fine grain permissions.', [':url' => $url->generate('lite.lite_settings_form')]) . '
'; 50 | $output .= ''; 52 | $output .= t('If a role is not granted toggle or resolve permissions for a moderation state but has permission to move to the next transition, nothing will prevent the user for saving. You should check carrefully you settings and permissions.'); 53 | $output .= t('A debug option on Lite settings can help you check your configuration.'); 54 | $output .= '
'; 55 | return $output; 56 | 57 | break; 58 | 59 | } 60 | } 61 | 62 | /** 63 | * Implements hook_element_info_alter(). 64 | * 65 | * @see \Drupal\filter\Element\TextFormat 66 | */ 67 | function lite_element_info_alter(array &$types) { 68 | // Our process callback must run immediately after 69 | // TextFormat::processFormat(). 70 | if (isset($types['text_format']) && isset($types['text_format']['#process'])) { 71 | $search_value = ['Drupal\filter\Element\TextFormat', 'processFormat']; 72 | $key = array_search($search_value, $types['text_format']['#process']); 73 | if ($key !== FALSE) { 74 | $key++; 75 | array_splice($types['text_format']['#process'], $key, 0, 'lite_filter_process_format'); 76 | } 77 | else { 78 | $types['text_format']['#process'][] = 'lite_filter_process_format'; 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * Process callback for form elements that have a text format selector attached. 85 | * 86 | * This callback runs after filter_process_format() and performs additional 87 | * modifications to the form element. 88 | * 89 | * @see \Drupal\filter\Element\TextFormat::processFormat() 90 | */ 91 | function lite_filter_process_format(&$element, FormStateInterface $form_state, &$complete_form) { 92 | // Retrieve the form object from $form_state. 93 | $form_object = $form_state->getFormObject(); 94 | 95 | // Check to see if we're working with a content entity form. 96 | if ($form_object instanceof ContentEntityForm) { 97 | 98 | // Retrieve the entity related to the form. 99 | if (!$entity = $form_state->getFormObject()->getEntity()) { 100 | return $element; 101 | } 102 | 103 | $is_new = $entity->isNew(); 104 | // And pass it to the element as custom settings. 105 | $element['#lite_node_values'] = [ 106 | 'bundle' => $entity->bundle(), 107 | 'new' => $is_new, 108 | 'moderated' => FALSE, 109 | 'workflow' => FALSE, 110 | 'state' => NULL, 111 | ]; 112 | 113 | // Ensure service can be loaded to avoid install errors. 114 | if (\Drupal::moduleHandler()->moduleExists('content_moderation') && \Drupal::hasService('content_moderation.moderation_information')) { 115 | /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */ 116 | $moderation_info = \Drupal::service('content_moderation.moderation_information'); 117 | 118 | // Add our values to help us for processing config and state. 119 | $element['#lite_node_values']['moderated'] = $moderation_info->isModeratedEntity($entity); 120 | if ($workflow = $moderation_info->getWorkflowForEntity($entity)) { 121 | $element['#lite_node_values']['workflow'] = $workflow->id(); 122 | } 123 | if (!$is_new) { 124 | if (isset($entity->moderation_state)) { 125 | $element['#lite_node_values']['state'] = $entity->moderation_state->value; 126 | } 127 | } 128 | } 129 | // We can add our pre_render callback to disable tracking depending options. 130 | $element['#pre_render'][] = 'lite_disable_tracking'; 131 | } 132 | 133 | return $element; 134 | } 135 | 136 | /** 137 | * Pre-render function to disable text tracking depending text format settings. 138 | * 139 | * We use Lite configuration to start or disable tracking, see 140 | * http://www.loopindex.com/lite/docs/ 141 | * 142 | * @see lite_filter_process_format() 143 | */ 144 | function lite_disable_tracking($element) { 145 | 146 | if (isset($element['#lite_node_values'])) { 147 | // Recover our settings and pass it to the DrupalSettings js array for use 148 | // in our plugin. 149 | $node = $element['#lite_node_values']; 150 | $is_new = $node['new']; 151 | $element['#attached']['drupalSettings']['lite']['node'] = $node; 152 | $config = \Drupal::config('lite.settings'); 153 | $element['#attached']['drupalSettings']['lite']['debug'] = $config->get('debug'); 154 | } 155 | else { 156 | return $element; 157 | } 158 | 159 | // Retrieve the available formats for the current text_format. 160 | $format_options = $element['format']['format']['#options']; 161 | 162 | // Loop through each of the available formats, adding a JS setting which 163 | // disabled tracking depeding format settings. 164 | foreach ($format_options as $format => $label) { 165 | $tracking = TRUE; 166 | if (isset($element['#attached']['drupalSettings']['editor']['formats'][$format]['editorSettings'])) { 167 | // Just simplify the array. 168 | $editor_settings = &$element['#attached']['drupalSettings']['editor']['formats'][$format]['editorSettings']; 169 | // If drupal lite is not enable we can stop here. 170 | if (!isset($editor_settings['lite'])) { 171 | continue; 172 | } 173 | 174 | // Check if the workflow is overriden from the text format settings. 175 | $options = $editor_settings['lite']['options']; 176 | if (!count($options)) { 177 | continue; 178 | } 179 | 180 | if (isset($options['moderation'])) { 181 | if ($options['moderation'] == 0 || $node['moderated'] == FALSE || $options['moderation_options'][$node['workflow']]['enable'] == 0) { 182 | // We are not dealing with a moderated or text format settings is 183 | // disable. By default tracking is always enable, we check if we need 184 | // to disable it when not enable by default or disabled on new 185 | // entities. 186 | if ($options['auto_start'] == 0 || ($is_new && $options['disable_new'] == 1)) { 187 | $editor_settings['lite']['tracking_disabled_reason'] = 'Not enable by default or disabled on new'; 188 | $tracking = FALSE; 189 | } 190 | } 191 | // For an exisitng node, apply the setting for this Workflow state. 192 | elseif($options['moderation'] == 1) { 193 | if (isset($options['moderation_options'][$node['workflow']][$node['state']])) { 194 | $options = $options['moderation_options'][$node['workflow']][$node['state']]; 195 | if ($options['auto_start'] == 0) { 196 | $editor_settings['lite']['tracking_disabled_reason'] = 'Disable for this moderation state: ' . $node['workflow'] . '::' . $node['state']; 197 | $tracking = FALSE; 198 | } 199 | } 200 | } 201 | } 202 | 203 | // It's a new node without state, so check new entity setting. 204 | if ($is_new) { 205 | if ($options['disable_new'] == 1) { 206 | $editor_settings['lite']['tracking_disabled_reason'] = 'Not start tracking on New entity'; 207 | $tracking = FALSE; 208 | } 209 | } 210 | 211 | $editor_settings['lite']['isTracking'] = $tracking; 212 | } 213 | } 214 | 215 | return $element; 216 | } 217 | 218 | /** 219 | * Permission callback for Lite's lite.permissions.yml. 220 | * 221 | * @see lite.permissions.yml 222 | */ 223 | function lite_permissions() { 224 | $permissions = []; 225 | 226 | // We can stop here if permission by formats is not enable. 227 | $config = \Drupal::config('lite.settings'); 228 | $extra_permissions = $config->get('extra_permissions'); 229 | 230 | if (!empty($extra_permissions) && function_exists('lite_' . $extra_permissions)) { 231 | $function_name = 'lite_' . $extra_permissions; 232 | $base_permissions = [ 233 | 'lite toggle' => [ 234 | 'base_title' => '%target: May enable or disable tracking', 235 | ], 236 | 'lite resolve' => [ 237 | 'base_title' => '%target: May resolve changes', 238 | ], 239 | ]; 240 | return $function_name($base_permissions); 241 | } 242 | 243 | return $permissions; 244 | } 245 | 246 | /** 247 | * Create permissions for each format with Lite enable. 248 | */ 249 | function lite_permissions_by_formats($base_permissions) { 250 | $permissions = []; 251 | $formats = []; 252 | 253 | // Build a list of formats with lite enable. 254 | $filter_formats = filter_formats(); 255 | foreach ($filter_formats as $filter_format) { 256 | $filters = $filter_format->get('filters'); 257 | if (isset($filters['lite'])) { 258 | $formats[$filter_format->id()] = $filter_format->label(); 259 | } 260 | } 261 | 262 | // Create permissions for each text format. 263 | foreach ($formats as $id => $format_name) { 264 | foreach ($base_permissions as $base_permission => $tracking_info) { 265 | $permissions[$base_permission . ' ' . $id] = [ 266 | 'title' => t($tracking_info['base_title'], ['%target' => $format_name]), 267 | 'description' => t('Ensure user can use this format to be able to use this permission.'), 268 | ]; 269 | } 270 | } 271 | 272 | return $permissions; 273 | } 274 | 275 | /** 276 | * Permission callback for Lite's lite_content_moderation.permissions.yml. 277 | * 278 | * @see lite_content_moderation.permissions.yml 279 | */ 280 | function lite_permissions_by_states($base_permissions) { 281 | $permissions = $workflows = []; 282 | 283 | // On 8.3.x moderation apply only on nodes. 284 | $bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('node'); 285 | foreach ($bundles as $info) { 286 | if (isset($info['workflow'])) { 287 | $workflows[] = $info['workflow']; 288 | } 289 | } 290 | 291 | foreach ($workflows as $workflow_id) { 292 | // We are not sure workflow module is enable, so we use statements to use 293 | // Workflow class. 294 | // @codingStandardsIgnoreStart 295 | if ($workflow = \Drupal\workflows\Entity\Workflow::load($workflow_id)) { 296 | // @codingStandardsIgnoreEnd 297 | $states = $workflow->getTypePlugin()->getStates(); 298 | foreach ($base_permissions as $base_permission => $tracking_info) { 299 | foreach ($states as $state) { 300 | $permissions[$base_permission . ' ' . $workflow_id . ' ' . $state->id()] = [ 301 | 'title' => t($tracking_info['base_title'], ['%target' => $workflow->label() . ', ' . $state->label()]), 302 | 'description' => t('Ensure user can use this state to be able to use this permission.'), 303 | ]; 304 | } 305 | } 306 | } 307 | } 308 | 309 | return $permissions; 310 | } 311 | 312 | /** 313 | * Implements hook_ckeditor_css_alter(). 314 | */ 315 | function lite_ckeditor_css_alter(&$css, $editor) { 316 | // Add lite css overirride for consistency from the front/back. 317 | $css[] = drupal_get_path('module', 'lite') . '/css/lite_view.theme.css'; 318 | } 319 | --------------------------------------------------------------------------------