From 4168d0b58e8f027bffeb236d3500e827e6c8b1a0 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 21 Nov 2021 14:53:34 +0100 Subject: [PATCH 01/17] Split data handling into domain specific data objects --- PageSnapshot.info.json | 2 +- PageSnapshot.module | 83 +++---- ProcessVersionControl.info.json | 2 +- ProcessVersionControl.module | 126 ++++------ VersionControl.info.json | 2 +- VersionControl.module | 357 +++++---------------------- VersionControlCleanup.module | 104 +++++--- lib/CleanupModuleConfig.php | 10 +- lib/DataObject.php | 67 +++++ lib/DataStore.php | 123 ++++++++++ lib/DatabaseHelper.php | 153 ------------ lib/MarkupHelper.php | 15 +- lib/ModuleConfig.php | 4 +- lib/ProcessModuleConfig.php | 10 +- lib/data/Data.php | 345 ++++++++++++++++++++++++++ lib/data/Files.php | 306 +++++++++++++++++++++++ lib/data/Revisions.php | 405 +++++++++++++++++++++++++++++++ lib/i18n.php | 120 +++++---- res/js/VersionControl.js | 4 + res/js/VersionControl.min.js | 2 +- res/js/VersionControl.min.js.map | 2 +- 21 files changed, 1554 insertions(+), 688 deletions(-) create mode 100644 lib/DataObject.php create mode 100644 lib/DataStore.php delete mode 100644 lib/DatabaseHelper.php create mode 100644 lib/data/Data.php create mode 100644 lib/data/Files.php create mode 100644 lib/data/Revisions.php diff --git a/PageSnapshot.info.json b/PageSnapshot.info.json index 2d4c50b..86d37e4 100644 --- a/PageSnapshot.info.json +++ b/PageSnapshot.info.json @@ -1,7 +1,7 @@ { "title": "Page Snapshot", "summary": "Return page in the state it was at the given time.", - "version": "2.1.1", + "version": "2.1.2", "href": "https://modules.processwire.com/modules/version-control/", "author": "Teppo Koivula, SteveB", "requires": [ diff --git a/PageSnapshot.module b/PageSnapshot.module index bae4788..aa31ee0 100644 --- a/PageSnapshot.module +++ b/PageSnapshot.module @@ -1,4 +1,8 @@ -store = $this->modules->get('VersionControl')->getDataStore(); + // Add new method snapshot to Page objects. $this->addHook('Page::snapshot', $this, 'hookPageSnapshot'); } @@ -109,14 +123,14 @@ class PageSnapshot extends WireData implements Module { ksort($items); foreach ($items as $key => $item) { $filename = substr($item['filename'], 0, 2) . '/' . $item['filename']; - $page->$field = $this->modules->VersionControl->path . $filename; + $page->$field = $this->store->files->getPath() . $filename; $page->$field->last()->description = $item['description']; $page->$field->last()->modified = $item['modified']; $page->$field->last()->created = $item['created']; if (isset($item['tags'])) $page->$field->last()->tags = $item['tags']; - $page->$field->last()->_version_control_url = $this->modules->VersionControl->url . $filename; - $page->$field->last()->_version_control_filename = $this->modules->VersionControl->path . $filename; - $item['filename'] = $this->modules->VersionControl->path . $filename; + $page->$field->last()->_version_control_url = $this->store->files->getURL() . $filename; + $page->$field->last()->_version_control_filename = $this->store->files->getPath() . $filename; + $item['filename'] = $this->store->files->getPath() . $filename; $filedata[$field][$key] = $item; } } @@ -135,7 +149,7 @@ class PageSnapshot extends WireData implements Module { */ protected function hookPagefileInstall(HookEvent $event) { if ($this->install_pagefiles) return; - if (strpos($event->arguments[0], $this->modules->VersionControl->path) === 0) { + if (strpos($event->arguments[0], $this->store->files->getPath()) === 0) { $event->object->basename = $event->arguments[0]; $event->replace = true; } @@ -190,15 +204,13 @@ class PageSnapshot extends WireData implements Module { } // revision info + $revision = null; $page->_version_control_revision = null; if ($revision_id) { if (!is_integer($revision_id)) { throw new WireException("Revision ID must be an integer"); } - $stmt = $this->database->prepare("SELECT timestamp FROM " . VersionControl::TABLE_REVISIONS . " WHERE id = :revision_id"); - $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); - $stmt->execute(); - $revision = $stmt->fetch(\PDO::FETCH_ASSOC); + $revision = $this->store->revisions->getData($revision_id, ['timestamp']); if (!$revision) { throw new WireException('Revision doesn\'t exist: ' . $revision_id); } @@ -210,64 +222,41 @@ class PageSnapshot extends WireData implements Module { $time = $revision_id ? strtotime($revision['timestamp']) : time(); } - // include repeater pages - $page_ids = [':p0' => $page->id]; + // prepare a list of page IDs, including nested repeater pages + $page_ids = [$page->id]; if ($this->modules->isInstalled('FieldtypeRepeater')) { - $p_num = 0; foreach ($page->fields as $field) { if ($field->type instanceof FieldtypeRepeater) { $subfields = $this->templates->get($field->template_id)->versionControlFields; if (count($subfields)) { foreach ($page->get($field->name) as $repeater_page) { - ++$p_num; - $page_ids[':p' . $p_num] = $repeater_page->id; + $page_ids[] = $repeater_page->id; } } } } } - // find values - $where = $revision_id ? 't1.id <= :revision_id AND ' : ''; - $stmt = $this->database->prepare(' - SELECT t1.pages_id, t1.id AS revision, t2.fields_id, t2.property, t2.data - FROM ( - SELECT MAX(t1.id) id, t1.pages_id, t2.fields_id - FROM ' . VersionControl::TABLE_REVISIONS . ' AS t1, ' . VersionControl::TABLE_DATA . ' AS t2 - WHERE ' . $where . 't1.pages_id IN (' . implode(',', array_keys($page_ids)) . ') AND t1.timestamp <= :time AND t2.revisions_id = t1.id - GROUP BY t1.pages_id, t2.fields_id, t2.property - ) AS t1 - INNER JOIN ' . VersionControl::TABLE_DATA . ' AS t2 - ON t2.revisions_id = t1.id AND t2.fields_id = t1.fields_id - GROUP BY revision, t1.pages_id, t2.fields_id, t2.property, t2.data - ORDER BY revision ASC - '); - if ($where) { - $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); - } - foreach ($page_ids as $p_num => $p_id) { - $stmt->bindValue($p_num, $p_id, \PDO::PARAM_INT); - } - $stmt->bindValue(':time', date('Y-m-d H:i:s', $time), \PDO::PARAM_STR); - $stmt->execute(); + // fetch page data from the database + $page_data = $this->store->data->getForPage($page_ids, $time, $revision_id); - // generate data (associative array) + // format data $data = []; - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - $field = $this->fields->get($row['fields_id']); - if ($row['pages_id'] != $page->id) { - $repeater_page = $this->pages->get($row['pages_id']); + foreach ($page_data as $data_row) { + $field = $this->fields->get($data_row['fields_id']); + if ($data_row['pages_id'] != $page->id) { + $repeater_page = $this->pages->get($data_row['pages_id']); if ($repeater_page->id) { $grandparent = $repeater_page->parent()->parent()->name; if (strpos($grandparent, 'for-field-') === 0) { $repeater_field = $this->fields->get((int) substr($grandparent, 10))->name; - $data[$repeater_field][$repeater_page . '.' . $field . '.' . $row['property']] = $row['data']; + $data[$repeater_field][$repeater_page . '.' . $field . '.' . $data_row['property']] = $data_row['data']; } } } else { - $data[$field . '.' . $row['property']] = $row['data']; - if (!$revision_id && $row['revision'] > $page->_version_control_revision) { - $page->_version_control_revision = $row['revision']; + $data[$field . '.' . $data_row['property']] = $data_row['data']; + if (!$revision_id && $data_row['revision'] > $page->_version_control_revision) { + $page->_version_control_revision = $data_row['revision']; } } } diff --git a/ProcessVersionControl.info.json b/ProcessVersionControl.info.json index a9af026..8d5f715 100644 --- a/ProcessVersionControl.info.json +++ b/ProcessVersionControl.info.json @@ -1,7 +1,7 @@ { "title": "Process Version Control", "summary": "Provides the interface required by Version Control.", - "version": "2.5.0", + "version": "2.5.1", "href": "https://modules.processwire.com/modules/version-control/", "author": "Teppo Koivula", "permission": "version-control", diff --git a/ProcessVersionControl.module b/ProcessVersionControl.module index e77dea3..51d4275 100644 --- a/ProcessVersionControl.module +++ b/ProcessVersionControl.module @@ -2,7 +2,8 @@ namespace ProcessWire; -use \VersionControl\i18n; +use VersionControl\DataStore; +use VersionControl\i18n; /** * Process Version Control @@ -27,6 +28,13 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul 'diff_efficiency_cleanup_edit_cost' => 4, ]; + /** + * Reference to the data store + * + * @var DataStore + */ + protected $store; + /** * Populate the default config data * @@ -40,6 +48,17 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul } } + /** + * Initialization function + */ + public function init() { + + // Get a reference to the data store. + $this->store = $this->modules->get('VersionControl')->getDataStore(); + + parent::init(); + } + /** * Module configuration * @@ -51,7 +70,8 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul * @return InputfieldWrapper */ public function getModuleConfigInputfields(array $data): InputfieldWrapper { - $this->wire('modules')->get('VersionControl')->initClassLoader(); + // Note: Version Control is required for class autoloading. + $this->wire('modules')->get('VersionControl'); return $this->wire(new \VersionControl\ProcessModuleConfig($data))->getFields(); } @@ -67,7 +87,7 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul } /** - * Return revision data for specific page + * Return revision info for specific page * * Page is defined by GET param 'pages_id' and additional settings with GET param 'settings'. * Supported settings are 'empty' (to render placeholders for fields with no stored data) and @@ -88,7 +108,7 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul if (!$this->pageEditable($page)) { throw new WirePermissionException('Permission denied (Page not editable)'); } - $page_ids = [':p0' => $pages_id]; + $page_ids = [$pages_id]; // Should date formatting occur? $defaults = static::$defaultData; @@ -98,15 +118,13 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul $settings = $this->input->get->settings; // Include Repeater pages. - $p_num = 0; $repeater_fields = []; foreach ($page->fields as $field) { if ($field->type instanceof FieldtypeRepeater) { $subfields = $this->templates->get($field->template_id)->versionControlFields; - if (count($subfields)) { + if ($subfields !== null && !empty($subfields)) { foreach ($page->get($field->name) as $repeater_page) { - ++$p_num; - $page_ids[':p' . $p_num] = $repeater_page->id; + $page_ids[] = $repeater_page->id; foreach ($subfields as $subfield) { $repeater_fields[] = $subfield . "_repeater" . $repeater_page->id; } @@ -116,25 +134,13 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul } // Find values. - // - MIN() function calls are required in order to avoid errors when MySQL is using SQL - // mode ONLY_FULL_GROUP_BY. - // - Some MIN() function calls may not be necessary for MySQL 5.7.5+. - $stmt = $this->database->prepare(" - SELECT MIN(r.pages_id) pages_id, MIN(f.name) field_name, MIN(r.timestamp) timestamp, MIN(r.users_id) users_id, MIN(r.username) username, MIN(d.revisions_id) revisions_id, MIN(d.property) property, MIN(d.data) data - FROM fields f, " . VersionControl::TABLE_REVISIONS . " r, " . VersionControl::TABLE_DATA . " d - WHERE r.pages_id IN (" . implode(',', array_keys($page_ids)) . ") AND d.revisions_id = r.id AND f.id = d.fields_id - GROUP BY r.id, f.id - ORDER BY f.id, d.id DESC - "); - foreach ($page_ids as $p_num => $p_id) { - $stmt->bindValue($p_num, $p_id, \PDO::PARAM_INT); - } - $stmt->execute(); + $page_data = $this->store->revisions->getForPage($page_ids); // Fetch enabled fields. $enabled_fields = []; - if (count($page->template->versionControlFields)) { - foreach ($page->template->versionControlFields as $field) { + $template_fields = $page->template->versionControlFields; + if ($template_fields !== null && count($template_fields)) { + foreach ($template_fields as $field) { if (!$this->modules->VersionControl->enable_locked_fields) { $field_object = $this->wire('fields')->get($field); if ($field_object && $field_object->id) { @@ -150,16 +156,15 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul // Parse data. $data = []; - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + foreach ($page_data as $row) { if (!in_array($row['field_name'], $enabled_fields)) { continue; } $item = [ 'users_id' => null, 'username' => !empty($row['username']) ? $this->sanitizer->name($row['username']) : null, - 'revision' => $row['revisions_id'] ?? null, + 'revision' => $row['revision'] ?? null, 'date' => $row['timestamp'] ?? null, - 'data' => $row['data'] ?? null, ]; if (isset($row['users_id']) && $user = $this->users->get((int) $row['users_id'])) { $item['users_id'] = $user->id; @@ -196,7 +201,7 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul // Return JSON or markup depending on provided settings. if (isset($settings['render']) && $settings['render'] === 'json') { header('Content-type: application/json'); - return json_encode($data); + return json_encode($data, $settings['json_flags'] ?? 0); } return $this->wire(new \VersionControl\MarkupHelper())->renderFieldRevisions($data, $page); } @@ -258,22 +263,12 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul } // Find values. - $stmt = $this->database->prepare(" - SELECT r.id, d.data - FROM fields AS f, " . VersionControl::TABLE_REVISIONS . " AS r, " . VersionControl::TABLE_DATA . " AS d - WHERE r.id IN(:r1, :r2) AND d.revisions_id = r.id AND f.name = :field_name AND d.fields_id = f.id - ORDER BY r.id ASC - LIMIT 2 - "); - $stmt->bindValue(':r1', $r1, \PDO::PARAM_INT); - $stmt->bindValue(':r2', $r2, \PDO::PARAM_INT); - $stmt->bindValue(':field_name', $field_name, \PDO::PARAM_STR); - $stmt->execute(); + $field_data = $this->store->data->getForField($field_name, [$r1, $r2]); // Render output. $data = []; - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - $id = $row['id'] == $r1 ? "r1" : "r2"; + foreach ($field_data as $row) { + $id = $row['revision'] == $r1 ? "r1" : "r2"; if ($field->type == "FieldtypePage") { $data[$id] = []; if (preg_match("/^(?:[1-9][0-9]*\|?)*(?input->get->settings; // Find values. - $stmt = $this->database->prepare(" - SELECT r.pages_id, d.fields_id, d.property, d.data - FROM fields AS f, " . VersionControl::TABLE_REVISIONS . " AS r, " . VersionControl::TABLE_DATA . " AS d - WHERE f.name = :field_name AND r.id = :revision_id AND d.revisions_id = r.id AND d.fields_id = f.id - "); - $stmt->bindValue(':field_name', $field_name, \PDO::PARAM_STR); - $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_STR); - $stmt->execute(); + $field_data = $this->store->data->getForField($field_name, $revision_id); // Generate data (an associative array). $page = null; $field = null; $data = []; - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + foreach ($field_data as $row) { $page = $page ?: $this->pages->get((int) $row['pages_id']); $field = $field ?: $this->fields->get((int) $row['fields_id']); if ($field->type == "FieldtypeDatetime") { @@ -434,8 +421,8 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul } $value['filename'] = substr($value['filename'], 0, 2) . '/' . $value['filename']; $file->_version_control_basename = substr($value['filename'], strpos($value['filename'], ".")+1); - $file->_version_control_url = $this->modules->VersionControl->url . $value['filename']; - $file->_version_control_filename = $this->modules->VersionControl->path . $value['filename']; + $file->_version_control_url = $this->store->files->getURL() . $value['filename']; + $file->_version_control_filename = $this->store->files->getPath() . $value['filename']; $files->add($file); $value['filename'] = $files->path . $value['filename']; $filedata[$field->name][] = $value; @@ -473,7 +460,7 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul * @param HookEvent $event */ protected function hookPagefileInstall(HookEvent $event) { - if (strpos($event->arguments[0], $this->modules->VersionControl->path) !== 0) return; + if (strpos($event->arguments[0], $this->store->files->getPath()) !== 0) return; $event->object->basename = $event->arguments[0]; $event->replace = true; } @@ -506,8 +493,8 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul protected function hookPageimageSize(HookEvent $event) { if (!$event->return->_version_control_filename) return; $filename = substr($event->return->basename, 0, 2) . '/variations/' . $event->return->basename; - $event->return->_version_control_url = $this->modules->VersionControl->url . $filename; - $event->return->_version_control_filename = $this->modules->VersionControl->path . $filename; + $event->return->_version_control_url = $this->store->files->getURL() . $filename; + $event->return->_version_control_filename = $this->store->files->getPath() . $filename; rename($event->return->pagefiles->path() . $event->return->basename, $event->return->filename); } @@ -568,25 +555,15 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul // Validate provided revision ID. $revision_id = (int) $this->input->post->revision; - $stmt = $this->database->prepare("SELECT timestamp FROM " . VersionControl::TABLE_REVISIONS . " WHERE id = :revision_id"); - $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); - $stmt->execute(); - $revision = $stmt->fetch(\PDO::FETCH_ASSOC); + $revision = $this->store->revisions->getData($revision_id, ['timestamp']); if (!$revision) { throw new WireException("Revision doesn't exist: $revision_id"); } - // Sanitize and store the comment text. - $comment = $this->input->post->comment; - if (mb_strlen($comment) > 255) { - $comment = mb_substr($comment, 0, 255); - } - $stmt = $this->database->prepare("UPDATE " . VersionControl::TABLE_REVISIONS . " SET comment = :comment WHERE id = :revision_id"); - $stmt->bindValue(':comment', $comment, \PDO::PARAM_STR); - $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); - $stmt->execute(); - - return $comment; + // Update comment text and return updated value or null in case of a failure. + return $this->store->revisions->update($revision_id, [ + 'comment' => $this->input->post->comment, + ])['comment'] ?? null; } /** @@ -631,11 +608,8 @@ class ProcessVersionControl extends Process implements Module, ConfigurableModul throw new WirePermissionException(i18n::getText('Permission denied (Page not editable)')); } - // Get revision from database. - $stmt = $this->database->prepare("SELECT timestamp FROM " . VersionControl::TABLE_REVISIONS . " WHERE id = :revision_id"); - $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); - $stmt->execute(); - $revision = $stmt->fetch(\PDO::FETCH_ASSOC); + // Get revision timestamp from database. + $revision = $this->store->revisions->getData($revision_id, ['timestamp']); if (!$revision) { throw new WireException(sprintf( i18n::getText('Revision doesn\'t exist: %d'), diff --git a/VersionControl.info.json b/VersionControl.info.json index 1288b11..5ef497b 100644 --- a/VersionControl.info.json +++ b/VersionControl.info.json @@ -1,7 +1,7 @@ { "title": "Version Control", "summary": "Version control features for page content.", - "version": "2.4.10", + "version": "2.5.0", "href": "https://modules.processwire.com/modules/version-control/", "author": "Teppo Koivula", "installs": [ diff --git a/VersionControl.module b/VersionControl.module index 3152976..a26c1c0 100644 --- a/VersionControl.module +++ b/VersionControl.module @@ -2,7 +2,8 @@ namespace ProcessWire; -use \VersionControl\i18n; +use VersionControl\DataStore; +use VersionControl\i18n; /** * Version Control @@ -43,6 +44,8 @@ class VersionControl extends WireData implements Module, ConfigurableModule { 'FieldtypeImage', 'FieldtypeSelector', 'FieldtypeOptions', + 'FieldtypeRepeater', + 'FieldtypeRepeaterMatrix', ], 'enabled_templates' => [], 'enable_all_templates' => false, @@ -50,7 +53,7 @@ class VersionControl extends WireData implements Module, ConfigurableModule { ]; /** - * Container for field data + * Container for page data * * @var array */ @@ -78,38 +81,18 @@ class VersionControl extends WireData implements Module, ConfigurableModule { protected $users_cache = []; /** - * Name of the revisions database table + * IDs of pages for which users have been cached * - * Revisions table keeps track of revision numbers, which are system wide, and stores important metadata, such as - * dates, user IDs, and page IDs. - * - * @var string - */ - const TABLE_REVISIONS = 'version_control__revisions'; - - /** - * Name of the database table containing actual field values - * - * @var string - */ - const TABLE_DATA = 'version_control__data'; - - /** - * Name of the databae table containing metadata for stored files - * - * Having file metadata stored in the database makes tasks such as mime type checking and fetching file sizes and - * file hashes fast. Another benefit is easier and more efficient cleanup for orphaned files. - * - * @var string + * @var array */ - const TABLE_FILES = 'version_control__files'; + protected $users_cache_pages = []; /** - * Name of the database table connecting file metadata with field values + * Data store instance * - * @var string + * @var DataStore */ - const TABLE_DATA_FILES = 'version_control__data_files'; + protected $store; /** * Populate the default config data @@ -137,6 +120,20 @@ class VersionControl extends WireData implements Module, ConfigurableModule { return $this->wire(new \VersionControl\ModuleConfig($data))->getFields(); } + /** + * Get the data store instance + * + * @internal + * + * @return DataStore + */ + public function getDataStore(): DataStore { + if (!$this->store) { + $this->store = $this->wire(new DataStore); + } + return $this->store; + } + /** * Initialization function */ @@ -145,6 +142,9 @@ class VersionControl extends WireData implements Module, ConfigurableModule { // Init class autoloader. $this->initClassLoader(); + // Instantiate data store. + $this->store = $this->getDataStore(); + // Handle saving config data for the companion modules. $this->addHookBefore('Modules::saveModuleConfigData(VersionControl)', $this, 'saveConfigData'); @@ -197,14 +197,20 @@ class VersionControl extends WireData implements Module, ConfigurableModule { } /** - * Add VersionControl namespace to ProcessWire's class autoloader + * Add VersionControl namespaces to ProcessWire's class autoloader */ public function initClassLoader() { - if ($this->wire('classLoader')->hasNamespace('VersionControl')) return; + if ($this->wire('classLoader')->hasNamespace('VersionControl')) { + return; + } $this->wire('classLoader')->addNamespace( 'VersionControl', $this->wire('config')->paths->get('VersionControl') . 'lib/' ); + $this->wire('classLoader')->addNamespace( + 'VersionControl\Data', + $this->wire('config')->paths->get('VersionControl') . 'lib/data/' + ); } /** @@ -272,7 +278,7 @@ class VersionControl extends WireData implements Module, ConfigurableModule { */ protected function replaceTextHashes(HookEvent $event) { if (!$this->input->get->revision) return; - $url = preg_quote($this->modules->VersionControl->url, "#"); + $url = preg_quote($this->store->files->getURL(), "#"); $event->return = preg_replace("#(?return); } @@ -387,7 +393,7 @@ class VersionControl extends WireData implements Module, ConfigurableModule { $count = 0; foreach ($data as $item) { $data_item = [ - 'original_filename' => $item->filename, + '_original_filename' => $item->filename, 'filename' => hash_file('sha1', $item->filename) . "." . $item->basename, 'description' => $item->description, 'modified' => $item->modified, @@ -443,41 +449,15 @@ class VersionControl extends WireData implements Module, ConfigurableModule { } if (!$page_data) return; - // Fetch current revision (parent) and user details. - $parent = $page->_version_control_parent; - $users_id = $this->user->id; - $username = $this->user->name; - // Revision ID placeholder. - $revisions_id = 0; + $revisions_id = null; foreach ($page_data as $fields_id => $field_data) { // If revision isn't assigned yet, get next available revision ID. if (!$revisions_id) { - $stmt = $this->database->prepare("INSERT INTO " . self::TABLE_REVISIONS . " (parent, pages_id, users_id, username, timestamp) VALUES (:parent, :pages_id, :users_id, :username, :timestamp)"); - $stmt->bindValue(':parent', $parent ?: null, \PDO::PARAM_INT); - $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); - $stmt->bindValue(':users_id', $users_id, \PDO::PARAM_INT); - $stmt->bindValue(':username', $username, \PDO::PARAM_STR); - $stmt->bindValue(':timestamp', date('Y-m-d H:i:s'), \PDO::PARAM_STR); - $stmt->execute(); - $revisions_id = $this->database->lastInsertId(); + $revisions_id = $this->store->revisions->add($page, $this->user); $page->_version_control_revision = $revisions_id; - // If parent isn't assigned yet, use ID of previous revision. - if (!$parent) { - $stmt = $this->database->prepare("SELECT id FROM " . self::TABLE_REVISIONS . " WHERE pages_id = :pages_id AND id < :revisions_id ORDER BY id DESC LIMIT 1"); - $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); - $stmt->bindValue(':revisions_id', $revisions_id, \PDO::PARAM_INT); - $stmt->execute(); - $result = $stmt->fetch(\PDO::FETCH_ASSOC); - if ($result) { - $stmt = $this->database->prepare("UPDATE " . self::TABLE_REVISIONS . " SET parent = :parent WHERE id = :revisions_id"); - $stmt->bindValue(':parent', (int) $result['id'], \PDO::PARAM_INT); - $stmt->bindValue(':revisions_id', $revisions_id, \PDO::PARAM_INT); - $stmt->execute(); - } - } } // Insert field data to another table. @@ -489,68 +469,8 @@ class VersionControl extends WireData implements Module, ConfigurableModule { $data = $field->type->___sleepValue($page, $field, $data); } - $file_id = null; - - // Dot means that this is multipart property (n.data), which can (at least for the time being) used to - // identify file/image fields. - if (strpos($property, ".") && !is_null($data)) { - - // Decode JSON data to get proper file information; copy the original file to storage (unless it's - // already there). - $data = json_decode($data, true); - $dir = $this->path . substr($data['filename'], 0, 2) . "/"; - $file = $dir . $data['filename']; - - // If this is an existing file, fetch ID from files table; otherwise store as a new file and add - // a row to said table. - if (is_file($file)) { - $stmt = $this->database->prepare("SELECT id FROM " . self::TABLE_FILES . " WHERE filename = :filename"); - $stmt->bindValue(':filename', $data['filename'], \PDO::PARAM_STR); - $stmt->execute(); - $result = $stmt->fetch(\PDO::FETCH_ASSOC); - $file_id = $result['id']; - } else { - if (!is_dir($dir)) wireMkdir($dir . "variations", true); - copy($data['original_filename'], $file); - $mime_type = ''; - if ($this->use_fileinfo) { - // PECL fileinfo extension is not enabled by default in Windows, so check for that first. - $finfo = finfo_open(FILEINFO_MIME_TYPE); - $mime_type = finfo_file($finfo, $file); - finfo_close($finfo); - } else if ($this->use_mime_content_type) { - $mime_type = mime_content_type($file); - } - $size = filesize($file); - if (empty($size)) { - $size = 0; - } - $stmt = $this->database->prepare("INSERT INTO " . self::TABLE_FILES . " (filename, mime_type, size) VALUES (:filename, :mime_type, :size)"); - $stmt->bindValue(':filename', $data['filename'], \PDO::PARAM_STR); - $stmt->bindValue(':mime_type', $mime_type, \PDO::PARAM_STR); - $stmt->bindValue(':size', $size, \PDO::PARAM_INT); - $stmt->execute(); - $file_id = $this->database->lastInsertId(); - } - unset($data['original_filename']); - - // Re-encode the JSON data. - $data = json_encode($data); - } - $stmt = $this->database->prepare("INSERT INTO " . self::TABLE_DATA . " (revisions_id, fields_id, property, data) VALUES (:revisions_id, :fields_id, :property, :data)"); - $stmt->bindValue(':revisions_id', $revisions_id, \PDO::PARAM_INT); - $stmt->bindValue(':fields_id', $fields_id, \PDO::PARAM_INT); - $stmt->bindValue(':property', $property, \PDO::PARAM_STR); - $stmt->bindValue(':data', $data, \PDO::PARAM_STR); - $stmt->execute(); - if ($file_id) { - // If data row is related to a file, add a new row to the junction table. - $data_id = $this->database->lastInsertId(); - $stmt = $this->database->prepare("INSERT INTO " . self::TABLE_DATA_FILES . " (data_id, files_id) VALUES (:data_id, :file_id)"); - $stmt->bindValue(':data_id', $data_id, \PDO::PARAM_INT); - $stmt->bindValue(':file_id', $file_id, \PDO::PARAM_INT); - $stmt->execute(); - } + // Store data in database. + $this->store->data->saveForField($fields_id, [$property => $data], $revisions_id); } // Remove processed items from the page data. @@ -705,60 +625,13 @@ class VersionControl extends WireData implements Module, ConfigurableModule { } // Cache usernames for later use. - if (empty($this->users_cache)) { - $this->cacheUsers($page); - } - - // Gather WHERE rules (page, filters). - $where = []; - $where['r.pages_id = :pages_id'] = [':pages_id', $page->id, \PDO::PARAM_INT]; - if (isset($filters['users_id']) && $filters['users_id'] == (int) $filters['users_id']) { - $where['r.users_id = :users_id'] = [':users_id', $filters['users_id'], \PDO::PARAM_INT]; - } - $where_str = "WHERE " . implode(" AND ", array_keys($where)); - - // Total count of rows in database table. - $stmt = $this->database->prepare("SELECT COUNT(*) AS total FROM " . self::TABLE_REVISIONS . " r $where_str"); - foreach ($where as $value) $stmt->bindValue($value[0], $value[1], $value[2]); - $stmt->execute(); - $result = $stmt->fetch(\PDO::FETCH_ASSOC); - $total = (int) $result['total']; - - // Put together a LIMIT clause. - if ($limit) { - $limit = (int) $limit; - $start = (int) $start; - if ($start > $total) { - $start = $total-$limit; - if ($start < 0) $start = 0; - } - } + $this->cacheUsers($page); // Fetch and parse history rows. - $sql = " - SELECT r.id, r.users_id, r.username, GROUP_CONCAT(CONCAT_WS(':', d.fields_id, d.property)) changes, r.timestamp, r.comment - FROM " . self::TABLE_REVISIONS . " r - LEFT OUTER JOIN " . self::TABLE_DATA . " d - ON d.revisions_id = r.id - $where_str - GROUP BY r.id - ORDER BY r.timestamp DESC, r.id DESC - "; - if ($limit) { - $sql .= " LIMIT :start, :limit"; - } - $stmt = $this->database->prepare($sql); - foreach ($where as $value) { - $stmt->bindValue($value[0], $value[1], $value[2]); - } - if ($limit) { - $stmt->bindValue(':start', $start, \PDO::PARAM_INT); - $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT); - } - $stmt->execute(); $data = []; $properties = []; - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $history = $this->store->revisions->getPageHistory($page, $start, $limit, $filters); + foreach ($history['rows'] as $row) { if ($row['changes']) { $changes = []; $changes_fields = explode(",", $row['changes']); @@ -803,7 +676,7 @@ class VersionControl extends WireData implements Module, ConfigurableModule { return [ 'data' => $data, - 'total' => $total, + 'total' => $history['total'], 'start' => $start, 'limit' => $limit, ]; @@ -815,39 +688,11 @@ class VersionControl extends WireData implements Module, ConfigurableModule { * @param Page $page */ protected function cacheUsers(Page $page) { - $users = []; - $stmt = $this->database->prepare("SELECT DISTINCT users_id FROM " . self::TABLE_REVISIONS . " WHERE pages_id = :pages_id"); - $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); - $stmt->execute(); - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - $user = $this->wire('users')->get((int) $row['users_id']); - if ($user->id) { - $users[$user->name] = $user->id; - continue; - } - $stmt = $this->database->prepare("SELECT username FROM " . self::TABLE_REVISIONS . " WHERE users_id = :users_id LIMIT 1"); - $stmt->bindValue(':users_id', $row['users_id'], \PDO::PARAM_INT); - $stmt->execute(); - $user = $stmt->fetch(\PDO::FETCH_ASSOC); - $users[$user['username']] = $row['users_id']; - } - ksort($users); - $this->users_cache = array_flip($users); - } - - /** - * Get users cache - * - * This method automatically populates the users cache if it doesn't exist yet. - * - * @param Page $page - * @return array - */ - public function getUsersCache(Page $page) { - if (empty($this->users_cache)) { - $this->users_cache = $this->cacheUsers($page); + if (!empty($this->users_cache_pages[$page->id])) { + return; } - return $this->users_cache; + $this->users_cache = array_merge($this->users_cache, $this->store->revisions->getPageUsers($page)); + $this->users_cache_pages[$page->id] = true; } /** @@ -892,19 +737,13 @@ class VersionControl extends WireData implements Module, ConfigurableModule { $page = $event->object; if (!$this->isEnabledTemplate($page->template)) return; - $revision = (int) $page->_version_control_revision; - if (!$revision) { - $stmt = $this->database->prepare("SELECT id FROM " . self::TABLE_REVISIONS . " WHERE pages_id = :pages_id ORDER BY id DESC LIMIT 1"); - $stmt->bindValue(':pages_id', (int) $page->id, \PDO::PARAM_INT); - $stmt->execute(); - $result = $stmt->fetch(\PDO::FETCH_ASSOC); - if ($result) { - $revision = (int) $result['id']; - $page->_version_control_revision = $revision; - } + if ($page->_version_control_revision && (int) $page->_version_control_revision == $page->_version_control_revision) { + $event->return = $page->_version_control_revision; + return; } - $event->return = $revision ?: null; + $page->_version_control_revision = $this->store->revisions->getPageRevision($page); + $event->return = $page->_version_control_revision; } /** @@ -950,16 +789,9 @@ class VersionControl extends WireData implements Module, ConfigurableModule { // If there were no revisions in cache, fetch from the database (with limit applied if one was provided). if (!is_array($revisions)) { - $sql = "SELECT id, timestamp FROM " . self::TABLE_REVISIONS . " WHERE pages_id = :pages_id ORDER BY id DESC"; - if ($limit) $sql .= " LIMIT :limit"; - $stmt = $this->database->prepare($sql); - $stmt->bindValue(':pages_id', (int) $page->id, \PDO::PARAM_INT); - if ($limit) $stmt->bindValue(':limit', (int) $limit, \PDO::PARAM_INT); - $stmt->execute(); - $revisions = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); $page->_version_control_revisions = [ 'limit' => $limit, - 'revisions' => $revisions, + 'revisions' => $this->store->revisions->getPageRevisions($page, $limit), ]; } @@ -974,26 +806,22 @@ class VersionControl extends WireData implements Module, ConfigurableModule { public function install() { // If Version Control For Text Fields is found, cancel installation to avoid a conflict. - if (is_dir($this->config->paths->siteModules . "VersionControlForTextFields")) { - throw new WireException("VersionControlForTextFields has to be removed first"); + if (is_dir($this->config->paths->siteModules . 'VersionControlForTextFields') || $this->modules->isInstalled('VersionControlForTextFields')) { + throw new WireException('VersionControlForTextFields has to be removed first'); } // Create database tables and attempt to import Version Control for Text Fields data. + // Note: during installation init is not triggered, so we'll need to init class autoloader manually. $this->initClassLoader(); - $db = $this->wire(new \VersionControl\DatabaseHelper()); - $db->createTables(); - $db->versionControlForTextFieldsImport(); + $store = $this->wire(new DataStore); + $store->install(); } /** - * Clean up when the module is unintalled + * Clean up when the module is uninstalled */ public function uninstall() { - $this->initClassLoader(); - $this->wire(new \VersionControl\DatabaseHelper())->dropTables(); - if (is_dir($this->path)) { - wireRmdir($this->path, true); - } + $this->store->uninstall(); } /** @@ -1125,20 +953,16 @@ class VersionControl extends WireData implements Module, ConfigurableModule { // Find out which (if any) templates were removed and cleanup data. $removed_templates = array_diff($old_templates, $new_templates); if (!empty($removed_templates)) { - $t1 = self::TABLE_REVISIONS; - $t2 = self::TABLE_DATA; foreach ($removed_templates as $template) { $template = $this->templates->get((int) $template); - $page_ids = array_values($this->pages->find("template={$template}, include=all")->getArray()); - $page_count = count($page_ids); + $page_count = $this->pages->count("template=" . $template->name . ", include=all"); if (!empty($page_count)) { - $stmt = $this->database->prepare("DELETE $t1, $t2 FROM $t1, $t2 WHERE $t1.pages_id IN (" . rtrim(str_repeat('?, ', $page_count), ', ') . ") AND $t2.revisions_id = $t1.id"); - $stmt->execute($page_ids); + $this->store->data->deleteForTemplate($template->id); $this->cleanupFiles(); $this->message(sprintf( i18n::getText('Removed stored data for %d page using template %s', 'Removed stored data for %d pages using template %s', $page_count), $page_count, - $template + $template->name )); } } @@ -1158,9 +982,7 @@ class VersionControl extends WireData implements Module, ConfigurableModule { // If field doesn't exist, this point should never be reached; data belonging to removed fields is // cleaned up elsewhere. - $stmt = $this->database->prepare('DELETE FROM ' . self::TABLE_DATA . ' WHERE fields_id = :fields_id'); - $stmt->bindValue(':fields_id', $fields_id, \PDO::PARAM_INT); - $stmt->execute(); + $this->store->data->deleteForField($fields_id); $this->cleanupFiles(); $this->message(sprintf( i18n::getText('Removed stored data for field %s'), @@ -1187,51 +1009,6 @@ class VersionControl extends WireData implements Module, ConfigurableModule { return false; } - /** - * Custom getter for caching runtime variables - * - * Note: we use the files directory of the page associated with ProcessVersionControl as file storage. This method - * finds and caches the path and URL of this directory, and creates it if it doesn't exist yet. - * - * @param string $key - * @return mixed - */ - public function __get($key) { - switch ($key) { - case "path": - case "url": - if (!array_key_exists($key, $this->data)) { - $processModuleID = $this->wire('modules')->getModuleID('ProcessVersionControl'); - if ($processModuleID) { - $processPage = $this->wire('pages')->get('template=admin, process=' . $processModuleID . ', name=version-control'); - if ($processPage->id) { - $pagefilesManager = $this->wire(new PagefilesManager($processPage)); - $this->data['path'] = $pagefilesManager->path(); - $this->data['url'] = $pagefilesManager->url(); - if (!is_dir($this->data['path'])) { - wireMkdir($this->data['path']); - } - } - } - } - return $this->data[$key] ?? null; - break; - case "use_fileinfo": - if (!array_key_exists($key, $this->data)) { - $this->data[$key] = extension_loaded('fileinfo') && function_exists('finfo_open'); - } - return $this->data[$key]; - break; - case "use_mime_content_type": - if (!array_key_exists($key, $this->data)) { - $this->data[$key] = function_exists('mime_content_type'); - } - return $this->data[$key]; - break; - } - return parent::get($key); - } - /** * Method overloading support * diff --git a/VersionControlCleanup.module b/VersionControlCleanup.module index 2cbe5b8..d7b91dd 100644 --- a/VersionControlCleanup.module +++ b/VersionControlCleanup.module @@ -1,4 +1,11 @@ -store = $this->modules->get('VersionControl')->getDataStore(); + // Remove expired data rows daily. $this->addHook("LazyCron::everyDay", $this, 'cleanup'); @@ -88,7 +105,6 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu * @return InputfieldWrapper */ public function getModuleConfigInputfields(array $data) { - $this->wire('modules')->get('VersionControl')->initClassLoader(); return $this->wire(new \VersionControl\CleanupModuleConfig($data))->getFields(); } @@ -100,16 +116,10 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu // check if this cleanup method is disabled if (!$this->data_max_age) return; - // prepare params - $t1 = VersionControl::TABLE_REVISIONS; - $t2 = VersionControl::TABLE_DATA; - $interval = $this->database->escapeStr($this->data_max_age); - - // prepare and execute statement - $stmt = $this->database->prepare("DELETE $t1, $t2 FROM $t1, $t2 WHERE $t1.timestamp < DATE_SUB(NOW(), INTERVAL $interval) AND $t2.revisions_id = $t1.id"); - $stmt->execute(); + // remove data older than specified max age settings + $this->store->data->purge($this->data_max_age); - // request cleanup for files + // cleanup files $this->cleanupFiles(); } @@ -119,17 +129,28 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu public function cleanupFiles() { // get base path for files - $files_path = $this->modules->get('VersionControl')->path; + $files_path = $this->store->files->getPath(); if (empty($files_path)) { // bail out early if files path can't be found return; } // first clean up data files junction table - $this->database->query("DELETE FROM " . VersionControl::TABLE_DATA_FILES . " WHERE data_id NOT IN (SELECT DISTINCT id FROM " . VersionControl::TABLE_DATA . ")"); + $this->database->query(" + DELETE FROM " . Files::TABLE_DATA_FILES . " + WHERE data_id NOT IN ( + SELECT DISTINCT id FROM " . Data::TABLE . " + ) + "); // find files without connections to stored data rows - $stmt = $this->database->prepare("SELECT * FROM " . VersionControl::TABLE_FILES . " WHERE id NOT IN (SELECT DISTINCT files_id from " . VersionControl::TABLE_DATA_FILES . ")"); + $stmt = $this->database->prepare(" + SELECT * + FROM " . Files::TABLE . " + WHERE id NOT IN ( + SELECT DISTINCT files_id from " . Files::TABLE_DATA_FILES . " + ) + "); $stmt->execute(); $stmt_del = null; while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { @@ -148,7 +169,7 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu // if containing directory is now empty, remove that too if (count(scandir($dir)) == 1) wireRmdir($dir, true); // delete related row from files database table - if (!$stmt_del) $stmt_del = $this->database->prepare("DELETE FROM " . VersionControl::TABLE_FILES . " WHERE id = :id"); + if (!$stmt_del) $stmt_del = $this->database->prepare("DELETE FROM " . Files::TABLE . " WHERE id = :id"); $stmt_del->bindValue(':id', (int) $row['id'], \PDO::PARAM_INT); $stmt_del->execute(); } @@ -168,9 +189,17 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu if (!$this->data_row_limit) return; $ids = []; - $t1 = VersionControl::TABLE_REVISIONS; - $t2 = VersionControl::TABLE_DATA; - $stmt = $this->database->prepare("SELECT $t1.id FROM $t1, $t2 WHERE $t1.pages_id = :pages_id AND $t2.fields_id = :fields_id AND $t2.revisions_id = $t1.id ORDER BY timestamp DESC LIMIT :offset, 18446744073709551615"); + $t1 = Revisions::TABLE; + $t2 = Data::TABLE; + $stmt = $this->database->prepare(" + SELECT $t1.id + FROM $t1, $t2 + WHERE $t1.pages_id = :pages_id + AND $t2.fields_id = :fields_id + AND $t2.revisions_id = $t1.id + ORDER BY timestamp DESC + LIMIT :offset, 18446744073709551615 + "); $stmt->bindValue(':pages_id', (int) $pages_id, \PDO::PARAM_INT); $stmt->bindValue(':fields_id', (int) $fields_id, \PDO::PARAM_INT); $stmt->bindValue(':offset', (int) $this->data_row_limit, \PDO::PARAM_INT); @@ -201,12 +230,18 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu if (!in_array('deleted_pages', $this->cleanup_methods)) return; $page = $event->arguments[0]; + + // if deleted data is for a Repeater item and parent still exists, we don't want to delete it yet + // @todo Make sure that data for Repeater pages gets deleted later; currently it's left orphaned + if ($page instanceof RepeaterPage) { + $for_page = $page->getForPage(); + if ($for_page->id) { + return; + } + } + if ($page instanceof Page) { - $t1 = VersionControl::TABLE_REVISIONS; - $t2 = VersionControl::TABLE_DATA; - $stmt = $this->database->prepare("DELETE $t1, $t2 FROM $t1 LEFT OUTER JOIN $t2 ON $t2.revisions_id = $t1.id WHERE $t1.pages_id = :pages_id"); - $stmt->bindValue(':pages_id', (int) $page->id, \PDO::PARAM_INT); - $stmt->execute(); + $this->store->data->deleteForPage($page); $this->cleanupFiles(); } } @@ -223,9 +258,7 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu $field = $event->arguments[0]; if ($field instanceof Field) { - $stmt = $this->database->prepare("DELETE FROM " . VersionControl::TABLE_DATA . " WHERE fields_id = :fields_id"); - $stmt->bindValue(':fields_id', (int) $field->id, \PDO::PARAM_INT); - $stmt->execute(); + $this->store->data->deleteForField($field); $this->cleanupFiles(); } } @@ -243,7 +276,14 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu $page = $event->arguments[0]; if ($page instanceof Page) { $fields = implode(",", array_keys($page->template->fields->getArray())); - $stmt = $this->database->prepare("DELETE FROM " . VersionControl::TABLE_DATA . " WHERE revisions_id IN (SELECT id FROM " . VersionControl::TABLE_REVISIONS . " WHERE pages_id = :pages_id) AND fields_id NOT IN ($fields)"); + $stmt = $this->database->prepare(" + DELETE FROM " . Data::TABLE . " + WHERE revisions_id IN ( + SELECT id FROM " . Revisions::TABLE . " + WHERE pages_id = :pages_id + ) + AND fields_id NOT IN ($fields) + "); $stmt->bindValue(':pages_id', (int) $page->id, \PDO::PARAM_INT); $stmt->execute(); $this->cleanupFiles(); @@ -288,15 +328,7 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu // if we got this far and removed fields were found, delete data rows if (!empty($removed_fields)) { foreach ($removed_fields_by_template as $template_id => $field_ids) { - $field_count = count($field_ids); - if ($field_count) { - $page_ids = array_values($this->pages->find("template={$template_id}, include=all")->getArray()); - $page_count = count($page_ids); - if ($page_count) { - $stmt = $this->database->prepare("DELETE FROM " . VersionControl::TABLE_DATA . " WHERE fields_id IN (" . rtrim(str_repeat('?, ', $field_count), ', ') . ") AND revisions_id IN (SELECT id FROM " . VersionControl::TABLE_REVISIONS . " WHERE pages_id IN (" . rtrim(str_repeat('?, ', $page_count), ', ') . "))"); - $stmt->execute(array_merge($field_ids, $page_ids)); - } - } + $this->store->data->deleteForTemplate($template_id, $field_ids); } $this->cleanupFiles(); } diff --git a/lib/CleanupModuleConfig.php b/lib/CleanupModuleConfig.php index 0db01ee..a84f27f 100644 --- a/lib/CleanupModuleConfig.php +++ b/lib/CleanupModuleConfig.php @@ -2,15 +2,15 @@ namespace VersionControl; -use ProcessWire\InputfieldWrapper, - ProcessWire\Inputfield, - ProcessWire\VersionControl, - ProcessWire\VersionControlCleanup; +use ProcessWire\InputfieldWrapper; +use ProcessWire\Inputfield; +use ProcessWire\VersionControl; +use ProcessWire\VersionControlCleanup; /** * Version Control Cleanup Config * - * @version 1.0.0 + * @version 1.0.1 * @author Teppo Koivula * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 */ diff --git a/lib/DataObject.php b/lib/DataObject.php new file mode 100644 index 0000000..74b23d7 --- /dev/null +++ b/lib/DataObject.php @@ -0,0 +1,67 @@ + + * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 + */ +abstract class DataObject extends \ProcessWire\Wire { + + /** + * Common prefix for database tables + * + * @var string + */ + const TABLE_PREFIX = 'version_control__'; + + /** + * Helper method for creating tables + * + * @param string $table Table name + * @param array $schema Table schema + * @throws WireDatabaseException if table already exists + */ + protected function createTable(string $table, array $schema) { + $table = $this->database->escapeStr($table); + $engine = $this->wire('config')->dbEngine; + $charset = $this->wire('config')->dbCharset; + $stmt = $this->database->prepare('SHOW TABLES LIKE \'' . $table . '\''); + $stmt->execute(); + if (count($stmt->fetchAll()) == 1) { + throw new WireDatabaseException(sprintf( + i18n::getText('Table %s already exists'), + $table + )); + } + $this->database->query('CREATE TABLE ' . $table . ' (' . implode(', ', $schema) . ') ENGINE = ' . $engine . ' DEFAULT CHARSET=' . $charset); + $this->message(sprintf( + i18n::getText('Created table: %s'), + $table + )); + } + + /** + * Helper method for dropping tables + * + * @param string $table Table name + */ + protected function dropTable(string $table) { + $table = $this->database->escapeStr($table); + $stmt = $this->database->prepare('SHOW TABLES LIKE \'' . $table . '\''); + $stmt->execute(); + if (count($stmt->fetchAll()) == 1) { + $this->database->query('DROP TABLE ' . $table); + $this->message(sprintf( + i18n::getText('Dropped table: %s'), + $table + )); + } + } + +} diff --git a/lib/DataStore.php b/lib/DataStore.php new file mode 100644 index 0000000..9995acb --- /dev/null +++ b/lib/DataStore.php @@ -0,0 +1,123 @@ + + * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 + */ +class DataStore extends \ProcessWire\Wire { + + /** + * Array of all available data objects + * + * @var array + */ + protected $data_objects = [ + 'revisions' => '\VersionControl\Data\Revisions', + 'data' => '\VersionControl\Data\Data', + 'files' => '\VersionControl\Data\Files', + ]; + + /** + * Revisions data object + * + * @var \VersionControl\Data\Revisions + */ + protected $revisions; + + /** + * Data data object + * + * @var \VersionControl\Data\Data + */ + protected $data; + + /** + * Files data object + * + * @var \VersionControl\Data\Files + */ + protected $files; + + /** + * Set up required data structures + */ + public function install() { + array_walk($this->data_objects, function(string $class_name, string $data_object) { + ($this->$data_object ?: $this->__get($data_object))->install(); + }); + $this->importVersionControlForTextFieldsData(); + } + + /** + * Tear down data structures + */ + public function uninstall() { + array_walk($this->data_objects, function(string $class_name, string $data_object) { + ($this->$data_object ?: $this->__get($data_object))->uninstall(); + }); + } + + /** + * Import data from Version Control For Text Fields + */ + protected function importVersionControlForTextFieldsData() { + $main_table = count($this->database->query("SHOW TABLES LIKE 'version_control_for_text_fields'")->fetchAll()) == 1; + $data_table = count($this->database->query("SHOW TABLES LIKE 'version_control_for_text_fields__data'")->fetchAll()) == 1; + if ($main_table && $data_table) { + $stmt_select_data_row = $this->database->prepare("SELECT property, data FROM version_control_for_text_fields__data WHERE version_control_for_text_fields_id = :id"); + $stmt_insert_revision = $this->database->prepare("INSERT INTO " . Revisions::TABLE . " (parent, pages_id, users_id, username, timestamp) VALUES (:parent, :pages_id, :users_id, :username, :timestamp)"); + $stmt_insert_data_row = $this->database->prepare("INSERT INTO " . Data::TABLE . " (revisions_id, fields_id, property, data) VALUES (:revisions_id, :fields_id, :property, :data)"); + $result = $this->database->query("SELECT * FROM version_control_for_text_fields"); + $parent = null; + while ($row = $result->fetch(\PDO::FETCH_ASSOC)) { + $stmt_insert_revision->bindValue(':parent', $parent, \PDO::PARAM_INT); + $stmt_insert_revision->bindValue(':pages_id', (int) $row['pages_id'], \PDO::PARAM_INT); + $stmt_insert_revision->bindValue(':users_id', (int) $row['users_id'], \PDO::PARAM_INT); + $stmt_insert_revision->bindValue(':username', $row['username'], \PDO::PARAM_STR); + $stmt_insert_revision->bindValue(':timestamp', $row['timestamp'], \PDO::PARAM_STR); + $stmt_insert_revision->execute(); + $revisions_id = $this->database->lastInsertId(); + $stmt_select_data_row->bindValue(':id', (int) $row['id'], \PDO::PARAM_INT); + $stmt_select_data_row->execute(); + while ($data_row = $stmt_select_data_row->fetch(\PDO::FETCH_ASSOC)) { + $stmt_insert_data_row->bindValue(':revisions_id', $revisions_id, \PDO::PARAM_INT); + $stmt_insert_data_row->bindValue(':fields_id', (int) $row['fields_id'], \PDO::PARAM_INT); + $stmt_insert_data_row->bindValue(':property', $data_row['property'], \PDO::PARAM_STR); + $stmt_insert_data_row->bindValue(':data', $data_row['data'], \PDO::PARAM_STR); + $stmt_insert_data_row->execute(); + } + $parent = $revisions_id; + } + $this->message(i18n::getText('Imported existing Version Control For Text Fields data')); + } + } + + /** + * Magic getter method + * + * @param string $name Property name + * @return mixed + */ + public function __get($name) { + if (isset($this->data_objects[$name])) { + if ($this->$name === null) { + $this->$name = new $this->data_objects[$name]; + } + return $this->$name; + } + return parent::__get($name); + } + +} diff --git a/lib/DatabaseHelper.php b/lib/DatabaseHelper.php deleted file mode 100644 index 7fc406c..0000000 --- a/lib/DatabaseHelper.php +++ /dev/null @@ -1,153 +0,0 @@ - - * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 - */ -class DatabaseHelper extends \ProcessWire\Wire { - - /** - * Create database tables - */ - public function createTables() { - - // revisions table bundles individual data rows into site-wide revisions - $this->createTable(VersionControl::TABLE_REVISIONS, [ - 'id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', - 'parent INT UNSIGNED DEFAULT NULL', - 'pages_id INT UNSIGNED NOT NULL', - 'users_id INT UNSIGNED DEFAULT NULL', - 'username VARCHAR(255) DEFAULT NULL', - 'timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP', - 'comment VARCHAR(255) DEFAULT NULL', - 'KEY pages_id (pages_id)', - ]); - - // data table, contains actual content for edited fields - $this->createTable(VersionControl::TABLE_DATA, [ - 'id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', - 'revisions_id INT UNSIGNED NOT NULL', - 'fields_id INT UNSIGNED NOT NULL', - 'property VARCHAR(255) NOT NULL', - 'data MEDIUMTEXT DEFAULT NULL', - 'KEY revisions_id (revisions_id)', - 'KEY fields_id (fields_id)', - ]); - - // files table contains one row for each stored file - $this->createTable(VersionControl::TABLE_FILES, [ - 'id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', - 'filename VARCHAR(255) NOT NULL', - 'mime_type VARCHAR(255)', - 'size INT UNSIGNED NOT NULL', - ]); - - // junction table: connects files to data rows - $this->createTable(VersionControl::TABLE_DATA_FILES, [ - 'data_id INT UNSIGNED NOT NULL', - 'files_id INT UNSIGNED NOT NULL', - 'PRIMARY KEY (data_id, files_id)', - ]); - } - - /** - * Drop database tables - */ - public function dropTables() { - $this->dropTable(VersionControl::TABLE_REVISIONS); - $this->dropTable(VersionControl::TABLE_DATA); - $this->dropTable(VersionControl::TABLE_FILES); - $this->dropTable(VersionControl::TABLE_DATA_FILES); - } - - /** - * Helper method for dropping tables - * - * @param string $table Table name - */ - protected function dropTable(string $table) { - $table = $this->database->escapeStr($table); - $stmt = $this->database->prepare("SHOW TABLES LIKE '$table'"); - $stmt->execute(); - if (count($stmt->fetchAll()) == 1) { - $this->database->query("DROP TABLE $table"); - $this->message(sprintf( - i18n::getText('Dropped table: %s'), - $table - )); - } - } - - /** - * Helper method for creating tables - * - * @param string $table Table name - * @param array $schema Table schema - * @throws WireDatabaseException if table already exists - */ - protected function createTable(string $table, array $schema) { - $table = $this->database->escapeStr($table); - $engine = $this->wire('config')->dbEngine; - $charset = $this->wire('config')->dbCharset; - $stmt = $this->database->prepare("SHOW TABLES LIKE '$table'"); - $stmt->execute(); - if (count($stmt->fetchAll()) == 1) { - throw new WireDatabaseException(sprintf( - i18n::getText('Table %s already exists'), - $table - )); - } - $sql = "CREATE TABLE $table ("; - $sql .= implode(', ', $schema); - $sql .= ") ENGINE = " . $engine . " DEFAULT CHARSET=" . $charset; - $this->database->query($sql); - $this->message(sprintf( - i18n::getText('Created table: %s'), - $table - )); - } - - - /** - * Import data from Version Control For Text Fields - */ - protected function versionControlForTextFieldsImport() { - $main_table = count($this->database->query("SHOW TABLES LIKE 'version_control_for_text_fields'")->fetchAll()) == 1; - $data_table = count($this->database->query("SHOW TABLES LIKE 'version_control_for_text_fields__data'")->fetchAll()) == 1; - if ($main_table && $data_table) { - $stmt_select_data_row = $this->database->prepare("SELECT property, data FROM version_control_for_text_fields__data WHERE version_control_for_text_fields_id = :id"); - $stmt_insert_revision = $this->database->prepare("INSERT INTO " . self::TABLE_REVISIONS . " (parent, pages_id, users_id, username, timestamp) VALUES (:parent, :pages_id, :users_id, :username, :timestamp)"); - $stmt_insert_data_row = $this->database->prepare("INSERT INTO " . self::TABLE_DATA . " (revisions_id, fields_id, property, data) VALUES (:revisions_id, :fields_id, :property, :data)"); - $result = $this->database->query("SELECT * FROM version_control_for_text_fields"); - $parent = null; - while ($row = $result->fetch(\PDO::FETCH_ASSOC)) { - $stmt_insert_revision->bindValue(':parent', $parent, \PDO::PARAM_INT); - $stmt_insert_revision->bindValue(':pages_id', (int) $row['pages_id'], \PDO::PARAM_INT); - $stmt_insert_revision->bindValue(':users_id', (int) $row['users_id'], \PDO::PARAM_INT); - $stmt_insert_revision->bindValue(':username', $row['username'], \PDO::PARAM_STR); - $stmt_insert_revision->bindValue(':timestamp', $row['timestamp'], \PDO::PARAM_STR); - $stmt_insert_revision->execute(); - $revisions_id = $this->database->lastInsertId(); - $stmt_select_data_row->bindValue(':id', (int) $row['id'], \PDO::PARAM_INT); - $stmt_select_data_row->execute(); - while ($data_row = $stmt_select_data_row->fetch(\PDO::FETCH_ASSOC)) { - $stmt_insert_data_row->bindValue(':revisions_id', $revisions_id, \PDO::PARAM_INT); - $stmt_insert_data_row->bindValue(':fields_id', (int) $row['fields_id'], \PDO::PARAM_INT); - $stmt_insert_data_row->bindValue(':property', $data_row['property'], \PDO::PARAM_STR); - $stmt_insert_data_row->bindValue(':data', $data_row['data'], \PDO::PARAM_STR); - $stmt_insert_data_row->execute(); - } - $parent = $revisions_id; - } - $this->message(i18n::getText('Imported existing Version Control For Text Fields data')); - } - } - -} diff --git a/lib/MarkupHelper.php b/lib/MarkupHelper.php index a936b53..672e86d 100644 --- a/lib/MarkupHelper.php +++ b/lib/MarkupHelper.php @@ -2,17 +2,15 @@ namespace VersionControl; -use \VersionControl\i18n; - -use \ProcessWire\Inputfield; -use \ProcessWire\InputfieldWrapper; -use \ProcessWire\Page; -use \ProcessWire\ProcessVersionControl; +use ProcessWire\Inputfield; +use ProcessWire\InputfieldWrapper; +use ProcessWire\Page; +use ProcessWire\ProcessVersionControl; /** * Version Control Markup Helper * - * @version 0.1.1 + * @version 0.1.2 * @author Teppo Koivula * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 */ @@ -131,7 +129,8 @@ public function getHistoryTab(array $data, Page $page): InputfieldWrapper { $field = $this->modules->get('InputfieldSelect'); $field->attr('id+name', 'users_id'); $field->addOption('', i18n::getText('All')); - $field->addOptions($this->modules->get('VersionControl')->getUsersCache($page)); + $store = $this->wire('modules')->get('VersionControl')->getDataStore(); + $field->addOptions($store->revisions->getPageUsers($page)); $field->value = $this->input->get->users_id; $field->label = i18n::getText('Filter by Author'); $field->description = i18n::getText('When selected, only revisions authored by specific user will be shown.'); diff --git a/lib/ModuleConfig.php b/lib/ModuleConfig.php index 4598b78..d467bdd 100644 --- a/lib/ModuleConfig.php +++ b/lib/ModuleConfig.php @@ -9,7 +9,7 @@ /** * Version Control Config * - * @version 1.2.0 + * @version 1.2.1 * @author Teppo Koivula * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 */ @@ -166,7 +166,7 @@ public function getFields() { protected function getCompatibleFieldtypeDiff(array $compatible_fieldtypes): string { // get a diff by comparing module default setting value and current setting value - $base = \ProcessWire\VersionControl::$defaultData['compatible_fieldtypes']; + $base = VersionControl::$defaultData['compatible_fieldtypes']; $diff = array_filter([ 'added' => implode(', ', array_diff($compatible_fieldtypes, $base)), 'removed' => implode(', ', array_filter(array_diff($base, $compatible_fieldtypes), function($fieldtype) { diff --git a/lib/ProcessModuleConfig.php b/lib/ProcessModuleConfig.php index ac523cf..026ea74 100644 --- a/lib/ProcessModuleConfig.php +++ b/lib/ProcessModuleConfig.php @@ -2,15 +2,15 @@ namespace VersionControl; -use ProcessWire\InputfieldWrapper, - ProcessWire\Inputfield, - ProcessWire\VersionControl, - ProcessWire\ProcessVersionControl; +use ProcessWire\InputfieldWrapper; +use ProcessWire\Inputfield; +use ProcessWire\VersionControl; +use ProcessWire\ProcessVersionControl; /** * Process Version Control Config * - * @version 1.0.0 + * @version 1.0.1 * @author Teppo Koivula * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 */ diff --git a/lib/data/Data.php b/lib/data/Data.php new file mode 100644 index 0000000..219226d --- /dev/null +++ b/lib/data/Data.php @@ -0,0 +1,345 @@ + + * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 + */ +class Data extends DataObject { + + /** + * Name of the database table containing actual field values + * + * @var string + */ + const TABLE = DataObject::TABLE_PREFIX . 'data'; + + /** + * Set up data structures + */ + public function install() { + $this->createTable(self::TABLE, [ + 'id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', + 'revisions_id INT UNSIGNED NOT NULL', + 'fields_id INT UNSIGNED NOT NULL', + 'property VARCHAR(255) NOT NULL', + 'data MEDIUMTEXT DEFAULT NULL', + 'KEY revisions_id (revisions_id)', + 'KEY fields_id (fields_id)', + ]); + } + + /** + * Tear down data structures + */ + public function uninstall() { + $this->dropTable(self::TABLE); + } + + /** + * Get page data, optionally matching provided revision ID and/or timestamp + * + * Note that this method contains two distinct SQL queries, depending on whether we're looking for page data in a + * specific revision or at specific time, or if we're looking for data for page spanning over multiple revisions. + * For consistency both queries return an identical set of columns: + * + * ``` + * [0] => Array + * ( + * [pages_id] => 7477 + * [revision] => 5728 + * [fields_id] => 1 + * [property] => data + * [data] => So Long, and Thanks for All the Fish + * [field_name] => title + * [timestamp] => 1980-05-01 08:34:50 + * [users_id] => 42 + * [username] => arthur + * ) + * // etc. + * ``` + * + * @param array $page_ids + * @param int|null $time + * @param int|null $revision_id + * @return array|null + */ + public function getForPage(array $page_ids, ?int $time = null, ?int $revision_id = null): ?array { + $page_ids = array_values(array_filter($page_ids, 'is_int')); + if (empty($page_ids)) { + return null; + } + $stmt = null; + if ($time || $revision_id) { + // if timestamp or revision ID is provided, we'll need a nested subquery to get the latest combination of + // page/field/property for our page (and any nested pages, such as repeater items) + $stmt = $this->database->prepare(' + SELECT + r.pages_id, r.id revision, d.fields_id, d.property, d.data, + f.name field_name, r.timestamp, r.users_id, r.username + FROM ( + SELECT + MAX(r.id) id, r.pages_id, d.fields_id, + MAX(r.users_id) users_id, MAX(r.username) username, MAX(r.timestamp) timestamp + FROM ' . Revisions::TABLE . ' r, ' . self::TABLE . ' d + WHERE + r.pages_id IN (:p' . implode(', :p', array_keys($page_ids)) . ') + AND d.revisions_id = r.id + ' . ($revision_id ? 'AND r.id <= :revision_id' : '') . ' + ' . ($time ? 'AND r.timestamp <= :time' : '') . ' + GROUP BY r.pages_id, d.fields_id, d.property + ) r + INNER JOIN ' . self::TABLE . ' d + ON d.revisions_id = r.id AND d.fields_id = r.fields_id + INNER JOIN fields f + ON f.id = d.fields_id + GROUP BY revision, r.pages_id, d.fields_id, d.property, d.data + ORDER BY revision ASC + '); + if ($revision_id) { + $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); + } + if ($time) { + $stmt->bindValue(':time', date('Y-m-d H:i:s', $time), \PDO::PARAM_STR); + } + } else { + // we're interested in the data for provided page/pages, including past revisions, with newer revisions + // appearing before older revisions + $stmt = $this->database->prepare(' + SELECT + r.pages_id, r.id revision, d.fields_id, d.property, d.data, + f.name field_name, r.timestamp, r.users_id, r.username + FROM ' . Revisions::TABLE . ' r + INNER JOIN ' . self::TABLE . ' d + ON d.revisions_id = r.id + INNER JOIN fields f + ON f.id = d.fields_id + WHERE + r.pages_id IN (:p' . implode(', :p', array_keys($page_ids)) . ') + GROUP BY + r.pages_id, revision, d.fields_id, d.property, d.data, + field_name, r.timestamp, users_id, r.username, d.id + ORDER BY r.pages_id, d.fields_id, d.id DESC + '); + } + foreach ($page_ids as $p_num => $p_id) { + $stmt->bindValue(':p' . $p_num, $p_id, \PDO::PARAM_INT); + } + $stmt->execute(); + $data = $stmt->fetchAll(\PDO::FETCH_ASSOC); + return $data === false ? null : $data; + } + + /** + * Get field data with revision ID(s) + * + * @param int|string|Field $field + * @param int|array $revision_ids + * @return array|null + */ + public function getForField($field, $revision_ids): ?array { + + // validate field ID + $fields_id = $this->getFieldID($field); + if ($fields_id === null) { + return null; + } + + // validate revision ID(s) + if (is_int($revision_ids)) { + $revision_ids = [$revision_ids]; + } else { + $revision_ids = array_values(array_filter($revision_ids, 'is_int')); + if (empty($revision_ids)) { + return null; + } + } + + // fetch and return data + $stmt = $this->database->prepare(" + SELECT r.id revision, r.pages_id, d.fields_id, d.property, d.data + FROM " . Revisions::TABLE . " r, " . self::TABLE . " d + WHERE d.fields_id = :fields_id AND r.id IN(:r" . implode(', :r', array_keys($revision_ids)) . ") AND d.revisions_id = r.id + ORDER BY revision + "); + $stmt->bindValue(':fields_id', $fields_id, \PDO::PARAM_INT); + foreach ($revision_ids as $r_num => $r_id) { + $stmt->bindValue(':r' . $r_num, $r_id, \PDO::PARAM_INT); + } + $stmt->execute(); + $data = $stmt->fetchAll(\PDO::FETCH_ASSOC); + return $data === false ? null : $data; + } + + /** + * Store field data in database + * + * @param int|string|Field $field + * @param array $field_data + * @param int $revisions_id + */ + public function saveForField($field, array $field_data, int $revisions_id) { + $fields_id = $this->getFieldID($field); + if ($fields_id === null) { + return; + } + foreach ($field_data as $property => $data) { + + $file = null; + + // dot means that this is multipart property (n.data), which can (at least for the time being) be used to + // identify file/image fields + if (strpos($property, ".") && !is_null($data)) { + $data = json_decode($data, true); + $file = [ + 'filename' => $data['filename'] ?? '', + 'source_filename' => $data['_original_filename'] ?? null, + ]; + unset($data['_original_filename']); + $data = json_encode($data); + } + + // insert field data to the data table + $stmt = $this->database->prepare("INSERT INTO " . self::TABLE . " (revisions_id, fields_id, property, data) VALUES (:revisions_id, :fields_id, :property, :data)"); + $stmt->bindValue(':revisions_id', $revisions_id, \PDO::PARAM_INT); + $stmt->bindValue(':fields_id', $fields_id, \PDO::PARAM_INT); + $stmt->bindValue(':property', $property, \PDO::PARAM_STR); + $stmt->bindValue(':data', $data, \PDO::PARAM_STR); + $data_id = $stmt->execute() ? $this->database->lastInsertId() : null; + + // if data row is related to a file, store file data + if ($file !== null) { + (new Files)->add($file['filename'], $file['source_filename'], $data_id); + } + } + } + + /** + * Remove data for provided template, optionally limited to specific field(s) + * + * @param int $templates_id + * @param int|string|Field|array|null $fields + * @return bool + */ + public function deleteForTemplate(int $templates_id, $fields = null): bool { + if ($fields !== null) { + $field_ids = array_filter(is_array($fields) ? array_map(function($field) { + return $this->getFieldID($field); + }, $fields) : [$this->getFieldID($fields)]); + if (empty($field_ids)) { + return false; + } + $stmt = $this->database->prepare(" + DELETE FROM " . self::TABLE . " + WHERE fields_id IN (:f" . implode(', :f', array_keys($field_ids)) . ") + AND revisions_id IN ( + SELECT id FROM " . Revisions::TABLE . " + WHERE pages_id IN ( + SELECT id FROM pages WHERE templates_id = :templates_id + ) + ) + "); + foreach ($field_ids as $f_num => $f_id) { + $stmt->bindValue(':f' . $f_num, $f_id, \PDO::PARAM_INT); + } + $stmt->bindValue(':templates_id', $templates_id, \PDO::PARAM_INT); + return $stmt->execute(); + } + $stmt = $this->database->prepare(" + DELETE " . Revisions::TABLE . ", " . self::TABLE . " + FROM " . Revisions::TABLE . ", " . self::TABLE . " + WHERE " . Revisions::TABLE . ".pages_id IN ( + SELECT id FROM pages WHERE templates_id = " . $templates_id . " + ) + AND " . self::TABLE . ".revisions_id = " . Revisions::TABLE . ".id + "); + return $stmt->execute(); + } + + /** + * Remove data for provided field + * + * @param int|string|Field $field + * @return bool + */ + public function deleteForField($field): bool { + $fields_id = $this->getFieldID($field); + if ($fields_id === null) { + return false; + } + $stmt = $this->database->prepare("DELETE FROM " . self::TABLE . " WHERE fields_id = :fields_id"); + $stmt->bindValue(':fields_id', $fields_id, \PDO::PARAM_INT); + return $stmt->execute(); + } + + /** + * Remove data for provided page + * + * @param Page $page + */ + public function deleteForPage(Page $page): bool { + if (!$page->id) { + return false; + } + $stmt = $this->database->prepare(" + DELETE " . Revisions::TABLE . ", " . self::TABLE . " + FROM " . Revisions::TABLE . " + LEFT OUTER JOIN " . self::TABLE . " ON " . self::TABLE . ".revisions_id = " . Revisions::TABLE . ".id + WHERE " . Revisions::TABLE . ".pages_id = :pages_id + "); + $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); + return $stmt->execute(); + } + + /** + * Remove data older than provided max age (interval) + * + * @param string $max_age + * @return bool + */ + public function purge(string $max_age): bool { + $max_age = $this->database->escapeStr($max_age); + if (empty($max_age)) { + return false; + } + $stmt = $this->database->prepare(" + DELETE " . Revisions::TABLE . ", " . self::TABLE . " + FROM " . Revisions::TABLE . ", " . self::TABLE . " + WHERE " . Revisions::TABLE . ".timestamp < DATE_SUB(NOW(), INTERVAL " . $max_age . ") + AND " . self::TABLE . ".revisions_id = " . Revisions::TABLE . ".id + "); + return $stmt->execute(); + } + + /** + * Get field ID based on mixed value input + * + * @param int|string|Field $field + * @return int|null + */ + protected function getFieldID($field): ?int { + if (is_int($field)) { + return $field; + } + if (is_string($field)) { + $fields_name = strpos($field, '_repeater') ? preg_replace('/_repeater[0-9]+$/', '', $field) : $field; + $fields_name = $this->sanitizer->fieldName($fields_name); + $field = $this->fields->get($fields_name); + return $field->id; + } + if ($field instanceof Field) { + return$field->id; + } + return null; + } + +} diff --git a/lib/data/Files.php b/lib/data/Files.php new file mode 100644 index 0000000..192e428 --- /dev/null +++ b/lib/data/Files.php @@ -0,0 +1,306 @@ + + * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 + */ +class Files extends DataObject { + + /** + * Name of the databae table containing metadata for stored files + * + * Having file metadata stored in the database makes tasks such as mime type checking and fetching file sizes and + * file hashes fast. Another benefit is easier and more efficient cleanup for orphaned files. + * + * @var string + */ + const TABLE = DataObject::TABLE_PREFIX . 'files'; + + /** + * Name of the database table connecting file metadata with field values + * + * @var string + */ + const TABLE_DATA_FILES = DataObject::TABLE_PREFIX . 'data_files'; + + /** + * Use fileinfo extension? + * + * @var bool|null + */ + protected static $use_fileinfo = null; + + /** + * Use fileinfo extension? + * + * @var bool|null + */ + protected static $use_mime_content_type = null; + + /** + * Path for stored files + * + * @var string|null + */ + protected static $path = null; + + /** + * URL for stored files + * + * @var string|null + */ + protected static $url = null; + + /** + * Set up data structures + */ + public function install() { + $this->createTable(self::TABLE, [ + 'id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', + 'filename VARCHAR(255) NOT NULL', + 'mime_type VARCHAR(255)', + 'size INT UNSIGNED NOT NULL', + ]); + $this->createTable(self::TABLE_DATA_FILES, [ + 'data_id INT UNSIGNED NOT NULL', + 'files_id INT UNSIGNED NOT NULL', + 'PRIMARY KEY (data_id, files_id)', + ]); + } + + /** + * Tear down data structures + */ + public function uninstall() { + $this->dropTable(self::TABLE); + $this->dropTable(self::TABLE_DATA_FILES); + $path = $this->getPath(); + if (is_dir($path)) { + wireRmdir($path, true); + $this->message(sprintf( + i18n::getText('Removed directory: %s'), + $path + )); + } + } + + /** + * Add new file + * + * Note that if the file already exist, this method will return the database ID for the existing file. + * + * @param string $filename + * @param string $source_filename Optional source file + * @param string $data_id Optional data table row ID + * @return int|null ID for stored file, or null in case of an failure + */ + public function add(string $filename, ?string $source_filename = null, ?int $data_id = null): ?int { + + // if source file was not provided, assume first argument to contain source file + if ($source_filename === null) { + $source_filename = $filename; + $filename = hash_file('sha1', $filename) . "." . basename($filename); + } + + // bail out early if source filename is empty or source file doesn't exist + if (empty($source_filename) || !is_file($source_filename)) { + return null; + } + + // define destination for stored file data + $dir = $this->getPath() . substr($filename, 0, 2) . "/"; + $file = $dir . $filename; + + // if this is an existing file, attempt to fetch an ID from the database + if (is_file($file)) { + $file_id = $this->getID($filename); + if ($file_id !== null) { + return $file_id; + } + } + + // make sure that directory for files exists, bail out early if it doesn't exist and can't be created + // note: we're intentionally creating the variations directory here, since it may be needed later on + if (!is_dir($dir) && !wireMkdir($dir . 'variations', true)) { + return null; + } + + // attempt to copy the source file to target location, bail out early if copy operation fails + if (!wireCopy($source_filename, $file)) { + return null; + } + + // insert database row for the file and return file ID on success + $stmt = $this->database->prepare("INSERT INTO " . self::TABLE . " (filename, mime_type, size) VALUES (:filename, :mime_type, :size)"); + $stmt->bindValue(':filename', $filename, \PDO::PARAM_STR); + $stmt->bindValue(':mime_type', $this->getMimeType($file), \PDO::PARAM_STR); + $stmt->bindValue(':size', filesize($file) ?: 0, \PDO::PARAM_INT); + $file_id = $stmt->execute() ? $this->database->lastInsertId() : null; + + // if file was successfully and data ID was provided, join the two + if ($file_id !== null && $data_id !== null) { + $stmt = $this->database->prepare("INSERT INTO " . self::TABLE_DATA_FILES . " (data_id, files_id) VALUES (:data_id, :file_id)"); + $stmt->bindValue(':data_id', $data_id, \PDO::PARAM_INT); + $stmt->bindValue(':file_id', $file_id, \PDO::PARAM_INT); + } + + return $file_id; + } + + /** + * Get data for stored file + * + * @param int $file_id + * @return array|null + */ + public function getData(int $file_id, array $keys = []): ?array { + + // filter and validate keys array + $allowed_keys = [ + 'id', + 'filename', + 'mime_type', + 'size', + ]; + if (empty($keys)) { + $keys = $allowed_keys; + } else { + $keys = array_filter($keys, function($key) { + return in_array($key, $allowed_keys); + }); + if (empty($keys)) { + return null; + } + } + + // fetch and return data + $stmt = $this->database->prepare("SELECT " . implode(", ", $keys) . " FROM " . self::TABLE . " WHERE id = :file_id"); + $stmt->bindValue(':file_id', $file_id, \PDO::PARAM_INT); + $stmt->execute(); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + return $result === false ? null : $result['id']; + } + + /** + * Remove data for provided file + * + * @param int $files_id + * @return bool + */ + public function delete(int $files_id): bool { + $stmt = $this->database->prepare(" + DELETE " . self::TABLE . ", " . self::TABLE_DATA_FILES . " + FROM " . self::TABLE . ", " . self::TABLE_DATA_FILES . " + WHERE " . self::TABLE . ".id = :files_id + AND " . self::TABLE_DATA_FILES . ".files_id = :files_id + "); + $stmt->bindValue(':files_id', $files_id, \PDO::PARAM_INT); + return $stmt->execute(); + } + + /** + * Get path for stored files + * + * @return string|null + */ + public function getPath(): ?string { + if (!self::$path) { + $this->setupFileStore(); + } + return self::$path; + } + + /** + * Get URL for stored files + * + * @return string|null + */ + public function getURL(): ?string { + if (!self::$url) { + $this->setupFileStore(); + } + return self::$url; + } + + /** + * Setup file storage + * + * @throws WireException if files directory doesn't exist and creating it fails + */ + protected function setupFileStore() { + if (self::$path) return; + $process_module_id = $this->wire('modules')->getModuleID('ProcessVersionControl'); + if ($process_module_id) { + $process_page = $this->wire('pages')->get('template=admin, process=' . $process_module_id); + if ($process_page->id) { + $filemanager = $this->wire(new PagefilesManager($process_page)); + self::$path = $filemanager->path(); + self::$url = $filemanager->url(); + if (!is_dir(self::$path) && !wireMkdir(self::$path)) { + throw new WireException(sprintf( + 'Creating directory failed: %s', + self::$path + )); + } + } + } + } + + /** + * Get ID of a stored file by filename + * + * @param string $filename + * @return int|null + */ + protected function getID(string $filename): ?int { + $stmt = $this->database->prepare("SELECT id FROM " . self::TABLE . " WHERE filename = :filename"); + $stmt->bindValue(':filename', $filename, \PDO::PARAM_STR); + $stmt->execute(); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + return $result === false ? null : $result['id']; + } + + /** + * Get mime content type for file + * + * @param string $filename + * @return string + */ + protected function getMimeType(string $filename): string { + + // PECL fileinfo extension is not enabled by default in Windows, so check for that first + if (self::$use_fileinfo || self::$use_fileinfo === null || extension_loaded('fileinfo') && function_exists('finfo_open')) { + self::$use_fileinfo = true; + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime_type = finfo_file($finfo, $filename); + finfo_close($finfo); + return $mime_type ?: ''; + } + self::$use_fileinfo = false; + + // check if mime_content_type function is available + if (self::$use_fileinfo || self::$use_fileinfo === null || function_exists('mime_content_type')) { + return mime_content_type($filename) ?: ''; + } + self::$use_fileinfo = false; + + return ''; + } + +} diff --git a/lib/data/Revisions.php b/lib/data/Revisions.php new file mode 100644 index 0000000..8eebbdd --- /dev/null +++ b/lib/data/Revisions.php @@ -0,0 +1,405 @@ + + * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 + */ +class Revisions extends DataObject { + + /** + * Name of the revisions database table + * + * Revisions table keeps track of revision numbers, which are system wide, and stores important metadata, such as + * dates, user IDs, and page IDs. + * + * @var string + */ + const TABLE = DataObject::TABLE_PREFIX . 'revisions'; + + /** + * Set up data structures + */ + public function install() { + $this->createTable(self::TABLE, [ + 'id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', + 'parent INT UNSIGNED DEFAULT NULL', + 'pages_id INT UNSIGNED NOT NULL', + 'users_id INT UNSIGNED DEFAULT NULL', + 'username VARCHAR(255) DEFAULT NULL', + 'timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP', + 'comment VARCHAR(255) DEFAULT NULL', + 'KEY pages_id (pages_id)', + ]); + } + + /** + * Tear down data structures + */ + public function uninstall() { + $this->dropTable(self::TABLE); + } + + /** + * Add new revision + * + * @param Page $page + * @param User $user + * @return int|null Revision ID, or null in case of a failure + */ + public function add(Page $page, User $user): ?int { + + // bail out early if page doesn't have an ID (NullPage) + if (!$page->id) { + return null; + } + + // add new row to the revisions database table + $stmt = $this->database->prepare("INSERT INTO " . self::TABLE . " (parent, pages_id, users_id, username, timestamp) VALUES (:parent, :pages_id, :users_id, :username, :timestamp)"); + $stmt->bindValue(':parent', $page->_version_control_parent, \PDO::PARAM_INT); + $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); + $stmt->bindValue(':users_id', $user->id, \PDO::PARAM_INT); + $stmt->bindValue(':username', $user->name, \PDO::PARAM_STR); + $stmt->bindValue(':timestamp', date('Y-m-d H:i:s'), \PDO::PARAM_STR); + $stmt->execute(); + $revision_id = $this->database->lastInsertId(); + + // if parent isn't assigned yet, attempt to fetch and use the ID of the previous revision for this page + if (!$page->_version_control_parent) { + $stmt = $this->database->prepare("SELECT id FROM " . self::TABLE . " WHERE pages_id = :pages_id AND id < :revision_id ORDER BY id DESC LIMIT 1"); + $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); + $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); + $stmt->execute(); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + if ($result) { + $stmt = $this->database->prepare("UPDATE " . self::TABLE . " SET parent = :parent WHERE id = :revision_id"); + $stmt->bindValue(':parent', (int) $result['id'], \PDO::PARAM_INT); + $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); + $stmt->execute(); + } + } + + return $revision_id; + } + + /** + * Update revision data + * + * @param int $revision_id + * @param array $data + * @return array|null Stored data + */ + public function update(int $revision_id, array $data): ?array { + + // filter and validate data array + $allowed_keys = [ + 'parent' => [ + 'type' => \PDO::PARAM_INT, + ], + 'pages_id' => [ + 'type' => \PDO::PARAM_INT, + ], + 'users_id' => [ + 'type' => \PDO::PARAM_INT, + ], + 'username' => [ + 'type' => \PDO::PARAM_STR, + ], + 'timestamp' => [ + 'type' => \PDO::PARAM_STR, + ], + 'comment' => [ + 'type' => \PDO::PARAM_STR, + 'func' => function($value) { + return mb_strlen($value) > 255 ? mb_substr($value, 0, 255) : $value; + }, + ], + ]; + $data = array_filter($data, function($key) use ($allowed_keys) { + return isset($allowed_keys[$key]); + }, ARRAY_FILTER_USE_KEY); + if (empty($data)) { + return false; + } + + // update data + $stmt = $this->database->prepare(' + UPDATE ' . self::TABLE . ' + SET ' . implode(', ', array_map(function($key) { return $key . ' = :' . $key; }, array_keys($data))) . ' + WHERE id = :revision_id + '); + $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); + foreach ($data as $key => &$value) { + if (isset($allowed_keys[$key]['func'])) { + $value = $allowed_keys[$key]['func']($value); + } + $stmt->bindValue(':' . $key, $value, $allowed_keys[$key]['type']); + } + $status = $stmt->execute(); + + // return updated data, or null in case of a failure + return $status === false ? null : $data; + } + + /** + * Get revision data + * + * @param int $revision_id + * @param array $keys + * @return array|null + */ + public function getData(int $revision_id, array $keys = []): ?array { + + // filter and validate keys array + $allowed_keys = [ + 'id', + 'parent', + 'pages_id', + 'users_id', + 'username', + 'timestamp', + 'comment', + ]; + if (empty($keys)) { + $keys = $allowed_keys; + } else { + $keys = array_filter($keys, function($key) use ($allowed_keys) { + return in_array($key, $allowed_keys); + }); + if (empty($keys)) { + return null; + } + } + + // fetch and return data + $stmt = $this->database->prepare("SELECT " . implode(", ", $keys) . " FROM " . self::TABLE . " WHERE id = :revision_id"); + $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); + $stmt->execute(); + $revision = $stmt->fetch(\PDO::FETCH_ASSOC); + return $revision === false ? null : $revision; + } + + /** + * Get an array of users related to provided page + * + * @param Page $page + * @return array + */ + public function getPageUsers(Page $page): array { + if (!$page->id) { + return []; + } + $users = []; + $stmt = $this->database->prepare("SELECT DISTINCT users_id FROM " . self::TABLE . " WHERE pages_id = :pages_id"); + $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); + $stmt->execute(); + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $user = $this->wire('users')->get((int) $row['users_id']); + if ($user->id) { + $users[$user->name] = $user->id; + continue; + } + $stmt = $this->database->prepare("SELECT username FROM " . self::TABLE . " WHERE users_id = :users_id LIMIT 1"); + $stmt->bindValue(':users_id', $row['users_id'], \PDO::PARAM_INT); + $stmt->execute(); + $user = $stmt->fetch(\PDO::FETCH_ASSOC); + if ($user !== false) { + $users[$user['username']] = $row['users_id']; + } + } + ksort($users); + $users = array_flip($users); + return $users; + } + + /** + * Get current revision ID for provided page + * + * @param Page $page + * @return int|null $revisions_id + */ + public function getPageRevision(Page $page): ?int { + if (!$page->id) { + return null; + } + $stmt = $this->database->prepare("SELECT id FROM " . self::TABLE . " WHERE pages_id = :pages_id ORDER BY id DESC LIMIT 1"); + $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); + $stmt->execute(); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + return $result === false ? null : (int) $result['id']; + } + + /** + * Get a list of revisions (id => timestamp) for provided page + * + * @param Page $page + * @param int|null $limit + * @return array|null + */ + public function getPageRevisions(Page $page, ?int $limit = null): ?array { + if (!$page->id) { + return null; + } + $sql = "SELECT id, timestamp FROM " . self::TABLE . " WHERE pages_id = :pages_id ORDER BY id DESC"; + if ($limit) { + $sql .= " LIMIT :limit"; + } + $stmt = $this->database->prepare($sql); + $stmt->bindValue(':pages_id', $page->id, \PDO::PARAM_INT); + if ($limit) { + $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT); + } + $stmt->execute(); + $revisions = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); + return $revisions === false ? null : $revisions; + } + + /** + * Get full history for provided page + * + * This method primarily exists for the "history" tab in admin. + * + * @param Page $page + * @param int|null $start + * @param int|null $limit + * @param array $filters + * @return array|null + */ + public function getPageHistory(Page $page, ?int $start = null, ?int $limit = null, array $filters = []): array { + + // bail out early if page doesn't have an ID (NullPage) + if (!$page->id) { + return [ + 'rows' => [], + 'total' => 0, + ]; + } + + // prepare WHERE rules (page, filters) + $where = []; + $where['r.pages_id = :pages_id'] = [':pages_id', $page->id, \PDO::PARAM_INT]; + if (isset($filters['users_id']) && $filters['users_id'] == (int) $filters['users_id']) { + $where['r.users_id = :users_id'] = [':users_id', $filters['users_id'], \PDO::PARAM_INT]; + } + $where_str = "WHERE " . implode(" AND ", array_keys($where)); + + // fetch the total count of matching rows + $stmt = $this->database->prepare("SELECT COUNT(*) AS total FROM " . self::TABLE . " r " . $where_str); + foreach ($where as $value) { + $stmt->bindValue($value[0], $value[1], $value[2]); + } + $stmt->execute(); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + $total = $result === false ? 0 : (int) $result['total']; + + // bail out early if there were no results + if ($total === 0) { + return [ + 'rows' => [], + 'total' => 0, + ]; + } + + // prepare LIMIT clause + if ($limit) { + $start = $start ?? 0; + if ($start > $total) { + $start = $total - $limit; + $start = $start < 0 ? 0 : $start; + } + } + + // prepare and execute SQL query + $sql = " + SELECT r.id, r.users_id, r.username, GROUP_CONCAT(CONCAT_WS(':', d.fields_id, d.property)) changes, r.timestamp, r.comment + FROM " . self::TABLE . " r + LEFT OUTER JOIN " . Data::TABLE . " d + ON d.revisions_id = r.id + " . $where_str . " + GROUP BY r.id + ORDER BY r.timestamp DESC, r.id DESC + "; + if ($limit) { + $sql .= "LIMIT :start, :limit"; + } + $stmt = $this->database->prepare($sql); + foreach ($where as $value) { + $stmt->bindValue($value[0], $value[1], $value[2]); + } + if ($limit) { + $stmt->bindValue(':start', $start, \PDO::PARAM_INT); + $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT); + } + $stmt->execute(); + + // fetch history rows + $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + return [ + 'rows' => $rows === false ? [] : $rows, + 'total' => $total, + ]; + } + + /** + * Get revision info for provided page(s) + * + * @param array $page_ids + * @return array|null + */ + public function getForPage(array $page_ids): ?array { + $page_ids = array_values(array_filter($page_ids, 'is_int')); + if (empty($page_ids)) { + return null; + } + $stmt = $this->database->prepare(' + SELECT + r.pages_id, r.id revision, d.fields_id, + f.name field_name, r.timestamp, r.users_id, r.username + FROM ' . self::TABLE . ' r + INNER JOIN ' . Data::TABLE . ' d + ON d.revisions_id = r.id + INNER JOIN fields f + ON f.id = d.fields_id + WHERE + r.pages_id IN (:p' . implode(', :p', array_keys($page_ids)) . ') + GROUP BY + r.pages_id, revision, d.fields_id, + field_name, r.timestamp, r.users_id, r.username + ORDER BY r.pages_id, d.fields_id, revision DESC + '); + foreach ($page_ids as $p_num => $p_id) { + $stmt->bindValue(':p' . $p_num, $p_id, \PDO::PARAM_INT); + } + $stmt->execute(); + $data = $stmt->fetchAll(\PDO::FETCH_ASSOC); + return $data === false ? null : $data; + } + + /** + * Remove revision by ID + * + * @param int $revision_id + * @return bool + */ + public function delete(int $revision_id): bool { + $stmt = $this->database->prepare(" + DELETE " . self::TABLE . ", " . Data::TABLE . " + FROM " . self::TABLE . ", " . Data::TABLE . " + WHERE " . self::TABLE . ".id = :revision_id + AND " . Data::TABLE . ".revisions_id = " . self::TABLE . ".id + "); + $stmt->bindValue(':revision_id', $revision_id, \PDO::PARAM_INT); + return $stmt->execute(); + } + +} diff --git a/lib/i18n.php b/lib/i18n.php index e6af817..9a245ac 100644 --- a/lib/i18n.php +++ b/lib/i18n.php @@ -39,57 +39,55 @@ class i18n { */ public static function getAll(): array { return static::$translations ?? [ - __('Dropped table: %s'), - __('Table %s already exists'), - __('Created table: %s'), - __('Imported existing Version Control For Text Fields data'), - __('History'), // Tab Label: History - [ - _n('Revision', 'Revisions', 1), - _n('Revision', 'Revisions', 2), - ], - __('Author'), - __('Changes'), - __('Timestamp'), - __('Comment'), - __('Edit comment'), - __('Restore revision'), - __('Preview revision'), - __('No history of changes found.'), - __('History'), - __('Filters'), - __('All'), - __('Filter by Author'), - __('When selected, only revisions authored by specific user will be shown.'), - __('Restore'), - __('Compare'), - __('Current revision'), - __('Stored revisions for field %s'), - __('There are no earlier versions of this field available'), - __('Unrecognized path'), - __('Page reverted to revision #%d'), - __('Page doesn\'t exist: %d'), - __('Permission denied (Page not viewable)'), - __('Permission denied (Page not editable)'), - __('Revision doesn\'t exist: %d'), - __('Compare with current'), - __('Editing this data is currently disabled. Restore it by saving the page or switch to current version first.'), - __('Type in comment text for this revision (max 255 characters)'), - __('This is the page as it appeared on %s. Click here to close the preview.'), - __('Are you sure that you want to revert this page to an earlier revision?'), - __('Show side by side'), - __('Show as a list'), - __('There is no difference between these revisions.'), - __('Toggle a list of revisions'), - [ - _n('Populated data for %d page using template %s', 'Populated data for %d pages using template %s', 1), - _n('Populated data for %d page using template %s', 'Populated data for %d pages using template %s', 2), - ], - [ - _n('Removed stored data for %d page using template %s', 'Removed stored data for %d pages using template %s', 1), - _n('Removed stored data for %d page using template %s', 'Removed stored data for %d pages using template %s', 2), - ], - __('Removed stored data for field %s'), + __('Removed directory: %s'), + __('History'), // Tab Label: History + __('Revision'), + __('Author'), + __('Changes'), + __('Timestamp'), + __('Comment'), + __('Edit comment'), + __('Restore revision'), + __('Preview revision'), + __('No history of changes found.'), + __('History'), + __('Filters'), + __('All'), + __('Filter by Author'), + __('When selected, only revisions authored by specific user will be shown.'), + __('Restore'), + __('Compare'), + __('Current revision'), + __('Stored revisions for field %s'), + __('There are no earlier versions of this field available'), + __('Table %s already exists'), + __('Created table: %s'), + __('Dropped table: %s'), + __('Imported existing Version Control For Text Fields data'), + __('Unrecognized path'), + __('Page reverted to revision #%d'), + __('Page doesn\'t exist: %d'), + __('Permission denied (Page not viewable)'), + __('Permission denied (Page not editable)'), + __('Revision doesn\'t exist: %d'), + __('Compare with current'), + __('Editing this data is currently disabled. Restore it by saving the page or switch to current version first.'), + __('Type in comment text for this revision (max 255 characters)'), + __('This is the page as it appeared on %s. Click here to close the preview.'), + __('Are you sure that you want to revert this page to an earlier revision?'), + __('Show side by side'), + __('Show as a list'), + __('There is no difference between these revisions.'), + __('Toggle a list of revisions'), + [ + _n('Populated data for %d page using template %s', 'Populated data for %d pages using template %s', 1), + _n('Populated data for %d page using template %s', 'Populated data for %d pages using template %s', 2), + ], + [ + _n('Removed stored data for %d page using template %s', 'Removed stored data for %d pages using template %s', 1), + _n('Removed stored data for %d page using template %s', 'Removed stored data for %d pages using template %s', 2), + ], + __('Removed stored data for field %s'), ]; } @@ -97,33 +95,33 @@ public static function getAll(): array { * Get single translation by key * * @param string $key - * @param int $count + * @param int $count * @return string|null */ public static function get(string $key, int $count = 1): ?string { if (static::$translations === null) { static::$translations = static::getAll(); } - $text = static::$translations[$key] ?? null; + $text = static::$translations[$key] ?? null; return is_array($text) ? $text[$count > 1 ? 1 : 0] : $text; } /** * Get single translation by source text - * - * This method can be used to access both singular and plural translations. If a string has both versions, provide - * singular version as the first argument, plural as the second one, and the quantity as the third argument. For - * strings that only have one version, you just need to provide the first argument. + * + * This method can be used to access both singular and plural translations. If a string has both versions, provide + * singular version as the first argument, plural as the second one, and the quantity as the third argument. For + * strings that only have one version, you just need to provide the first argument. * * @param string $text * @param string|null $text_plural - * @param int $count + * @param int $count * @return string */ public static function getText(string $text, ?string $text_plural = null, int $count = 1): string { - if ($text_plural !== null) { - return _n($text, $text_plural, $count, __FILE__); - } + if ($text_plural !== null) { + return _n($text, $text_plural, $count, __FILE__); + } return __($text, __FILE__); } } diff --git a/res/js/VersionControl.js b/res/js/VersionControl.js index 7339d8e..910c990 100644 --- a/res/js/VersionControl.js +++ b/res/js/VersionControl.js @@ -239,6 +239,10 @@ $(function() { $if.find('.asmContainer').remove(); $select.asmSelect(options); } + if ($if.hasClass('InputfieldImage') || $if.hasClass('InputfieldFile')) { + // Trigger InputfieldImage() manually. + InputfieldImage($); + } } else { update($if, $content, settings, field, cache[field + "." + revision]); } diff --git a/res/js/VersionControl.min.js b/res/js/VersionControl.min.js index a2d7d99..12fcbd4 100644 --- a/res/js/VersionControl.min.js +++ b/res/js/VersionControl.min.js @@ -1 +1 @@ -$(function(){function d(t,i,e,n,s){var r;"Input"==e.render?(r=i.children("p.description:first"),e=i.children("p.notes:first"),i.html(s).prepend(r).append(e),t.hasClass("InputfieldImage")||t.hasClass("InputfieldFile")?(InputfieldImage($),i.prepend('
'),$(".version-control-overlay").attr("title",f.i18n.editDisabled).on("click",function(){alert($(this).attr("title"))}).hover(function(){$(this).parent(".version-control-overlay-parent").addClass("version-control-overlay-parent--hover")},function(){$(this).parent(".version-control-overlay-parent").removeClass("version-control-overlay-parent--hover")}).parent(".InputfieldContent").addClass("version-control-overlay-parent")):t.hasClass("Inputfield_permissions")&&($(".Inputfield_permissions .Inputfield_permissions > .InputfieldContent").insertAfter($(".Inputfield_permissions:first > .InputfieldContent:first")),$(".Inputfield_permissions:first > .InputfieldContent:first").remove()),t.find(".InputfieldAsmSelect").length&&(e=t.find("select[multiple=multiple]"),i="undefined"==typeof config?{sortable:!0}:config[e.attr("id")],e.asmSelect(i)),t.find(".langTabs").length&&"function"==typeof setupLanguageTabs&&setupLanguageTabs(t)):$.each(s,function(i,e){i=(i=i.replace("data",""))&&"__"+i;"undefined"!=typeof tinyMCE&&tinyMCE.get("Inputfield_"+n+i)?tinyMCE.get("Inputfield_"+n+i).setContent(e):t.find(".InputfieldCKEditorInline").length?t.find(".InputfieldCKEditorInline").html(e):"undefined"!=typeof CKEDITOR&&CKEDITOR.instances["Inputfield_"+n+i]&&CKEDITOR.instances["Inputfield_"+n+i].setData(e)})}function e(i,e){var t=$("#field-revisions--"+i);t.length&&(e=void 0===e?!t.attr("aria-hidden"):Boolean(e),$(".field-revisions-toggle[data-field="+i+"]").toggleClass("field-revisions-toggle--active",!e).attr("aria-expanded",!e),e?t.addClass("field-revisions--sliding").slideUp("fast",function(){t.removeClass("field-revisions--sliding").removeAttr("style").attr("aria-hidden",!0).find("a, button").attr("tabindex",-1),InputfieldColumnWidths()}):t.addClass("field-revisions--animatable field-revisions--sliding").slideDown("fast",function(){t.removeClass("field-revisions--sliding").removeAttr("aria-hidden").focus().find("a, button").removeAttr("tabindex"),InputfieldColumnWidths()}),$(window).trigger("resize.revisions-table"))}var f=config.VersionControl,c={},r=$('');window.versionControlPrepareInputfields=function(){var i;$("#version-control-data > div:not(.version-control--prepared)").each(function(){if($(this).data("revision")){var i=$(".Inputfield_"+$(this).data("field"));if(!i.length)return;if($(this).find("a, button").attr("tabindex",-1),$(this).find(".field-revision__current-label:first").attr("aria-hidden",!1),i.find("> label").addClass("version-control--with-history").data("version-control--field",$(this).data("field")).before($(this)),$(this).find("tr:eq(1)").addClass("ui-state-active"),i.hasClass("InputfieldTinyMCE")||i.hasClass("InputfieldCKEditor"))return;i=i.find(".InputfieldContent:first")||i.find("div.ui-widget-content:first");c[$(this).data("field")+"."+$(this).data("revision")]=i.clone(!0,!0)}$(this).addClass("version-control--prepared")}),$(".version-control--with-history:not(.version-control--prepared)").each(function(){var i=$("").hide().appendTo($(this)).css("color"),e=$('').addClass("field-revisions-toggle").attr("title",f.i18n.toggleRevisions).attr("aria-expanded",!1).attr("data-field",$(this).data("version-control--field")).prop("disabled",$("#field-revisions--"+$(this).data("version-control--field")).find("tr").length<2).css("color",i),i=($("").addClass("version-control--visually-hidden").text(f.i18n.toggleRevisions).appendTo(e),$(this).find(".toggle-icon"));i.length?i.after(e):$(this).append(e),$(this).addClass("version-control--prepared")}),$(".field-revisions").off("click.revisions-restore").on("click.revisions-restore",".field-revision__button--restore, .field-revision__current",function(){var i=$(this).parents(".field-revision:first");if(i.hasClass("ui-state-active"))return!1;var e={render:"Input"},t=$(this).parents(".field-revisions:first"),n=i.find(".field-revision__current:first"),s=$(this).parents(".Inputfield:first"),r=t.data("field");s.find(".field-revisions .ui-state-active").removeClass("ui-state-active"),s.find(".field-revision__current-label").attr("aria-hidden",!0),i.addClass("ui-state-active"),n.find(".field-revision__current-label").attr("aria-hidden",!1),$(".compare-revisions").remove(),$(".field-revision__button--diff").removeClass("active");var o=s.find(".InputfieldContent:first")||s.find("div.ui-widget-content:first"),a=$('').hide().css({height:o.innerHeight()+"px",backgroundColor:o.css("background-color")});(s.hasClass("InputfieldTinyMCE")||s.hasClass("InputfieldCKEditor"))&&(e={render:"JSON"});var l=i.data("revision");return c[r+"."+l]?"JSON"!=e.render&&l==t.data("revision")?(o.replaceWith(c[r+"."+l].clone(!0,!0)),s.find(".InputfieldFileList").length&&(s.find(".InputfieldFileInit").removeClass("InputfieldFileInit"),s.trigger("reloaded")),s.find(".InputfieldAsmSelect").length&&(i=s.find("select[multiple=multiple]"),t="undefined"==typeof config?{sortable:!0}:config[i.attr("id")],i.appendTo(s.find(".InputfieldAsmSelect")).show(),s.find(".asmContainer").remove(),i.asmSelect(t))):d(s,o,e,r,c[r+"."+l]):(o.css("position","relative").prepend(a.fadeIn(250)),$.get(f.processPage+"field",{revision:l,field:r,settings:e},function(i){c[r+"."+l]=i,d(s,o,e,r,c[r+"."+l]),a.fadeOut(350,function(){$(this).remove()})})),!1}),$(".field-revisions-toggle").off("click.toggleRevisions").on("click.toggleRevisions",function(i){i.preventDefault(),e($(this).data("field"))}),$(window).off("keyup.field-revisions").on("keyup.field-revisions",function(i){"Esc"!=i.key&&"Escape"!=i.key||(i=$(document.activeElement)).hasClass("field-revisions")&&(i=i.data("field"),e(i),$(".field-revisions-toggle[data-field="+i+"]").focus())}),$(window).off("resize.revisions-table").on("resize.revisions-table",function(){clearTimeout(i),i=setTimeout(function(){$(".field-revisions:not(.field-revisions--animatable)").each(function(){$table=$(this).find("table"),$table.length&&$(this).width()<$table.outerWidth()?$(this).addClass("field-revisions--scrollable").find("> div").trigger("scroll.revisions-table"):$(this).removeClass("field-revisions--scrollable")})},250)}).trigger("resize.revisions-table"),$(".field-revisions > div").off("scroll.revisions-table").on("scroll.revisions-table",function(){$(this).parent().toggleClass("field-revisions--scrollable-start",!$(this).scrollLeft()).toggleClass("field-revisions--scrollable-end",$(this)[0].scrollWidth-$(this).scrollLeft()==$(this).outerWidth())}),$(".field-revisions").off("click.revisions-diff").on("click.revisions-diff",".field-revision__button--diff",function(){var i,e,t,n,s;return $(".compare-revisions").remove(),$(".field-revision__button--diff").not(this).removeClass("field-revisions__button--active"),$(this).toggleClass("field-revisions__button--active"),$(this).hasClass("field-revisions__button--active")&&($(this).attr("aria-expanded",!0),i=$(this).parents(".field-revisions:first"),e=$(this).parents(".field-revision:first"),t=i.data("field"),i=i.find(".ui-state-active:first").data("revision"),e=e.data("revision"),t=f.processPage+"diff/?revisions="+i+":"+e+"&field="+t,n=$("
").attr("tabindex",-1).addClass("compare-revisions").insertAfter($(this)).focus(),s=$(this).parents("tr:first"),n.prepend(r).load(t,function(){s.find("ul.page-diff").length?"function"!=typeof enableDiffSwitch?$.getScript(f.moduleDir+"diff_switch.min.js",function(){enableDiffSwitch(f)}):enableDiffSwitch(f):o();$("").addClass("field-revision__button compare-revisions__close fa fas fa-times").attr("tabindex",0).on("click",function(i){i.preventDefault(),$(this).parent().prev().focus().trigger("click")}).prependTo(n)})),!1})};var o=function(i,e){var t,i=i||document.getElementById("r1").value,e=e||document.getElementById("r2").value;if(i==e)t=""+f.i18n.noDiff+"";else{if("function"!=typeof diff_match_patch)return $.getScript(f.moduleDir+"res/js/diff_match_patch/diff_match_patch.js",function(){o(i,e)}),!1;var n=new diff_match_patch;n.Diff_Timeout=f.diff.timeout,n.Diff_EditCost=f.diff.editCost;var s=(new Date).getTime(),r=n.diff_main(i,e);(new Date).getTime();f.diff.cleanup&&n["diff_cleanup"+f.diff.cleanup](r),t=n.diff_prettyHtml(r)}document.getElementById("diff").innerHTML=t};$.get(f.processPage+"page",{pages_id:f.pageID,settings:{empty:!0,render:"HTML"}},function(i){$("body").prepend(i),versionControlPrepareInputfields(),$(document).on("reloaded",".Inputfield",function(){versionControlPrepareInputfields()})})}); \ No newline at end of file +$(function(){function d(t,i,e,n,s){var r;"Input"==e.render?(r=i.children("p.description:first"),e=i.children("p.notes:first"),i.html(s).prepend(r).append(e),t.hasClass("InputfieldImage")||t.hasClass("InputfieldFile")?(InputfieldImage($),i.prepend('
'),$(".version-control-overlay").attr("title",f.i18n.editDisabled).on("click",function(){alert($(this).attr("title"))}).hover(function(){$(this).parent(".version-control-overlay-parent").addClass("version-control-overlay-parent--hover")},function(){$(this).parent(".version-control-overlay-parent").removeClass("version-control-overlay-parent--hover")}).parent(".InputfieldContent").addClass("version-control-overlay-parent")):t.hasClass("Inputfield_permissions")&&($(".Inputfield_permissions .Inputfield_permissions > .InputfieldContent").insertAfter($(".Inputfield_permissions:first > .InputfieldContent:first")),$(".Inputfield_permissions:first > .InputfieldContent:first").remove()),t.find(".InputfieldAsmSelect").length&&(e=t.find("select[multiple=multiple]"),i="undefined"==typeof config?{sortable:!0}:config[e.attr("id")],e.asmSelect(i)),t.find(".langTabs").length&&"function"==typeof setupLanguageTabs&&setupLanguageTabs(t)):$.each(s,function(i,e){i=(i=i.replace("data",""))&&"__"+i;"undefined"!=typeof tinyMCE&&tinyMCE.get("Inputfield_"+n+i)?tinyMCE.get("Inputfield_"+n+i).setContent(e):t.find(".InputfieldCKEditorInline").length?t.find(".InputfieldCKEditorInline").html(e):"undefined"!=typeof CKEDITOR&&CKEDITOR.instances["Inputfield_"+n+i]&&CKEDITOR.instances["Inputfield_"+n+i].setData(e)})}function e(i,e){var t=$("#field-revisions--"+i);t.length&&(e=void 0===e?!t.attr("aria-hidden"):Boolean(e),$(".field-revisions-toggle[data-field="+i+"]").toggleClass("field-revisions-toggle--active",!e).attr("aria-expanded",!e),e?t.addClass("field-revisions--sliding").slideUp("fast",function(){t.removeClass("field-revisions--sliding").removeAttr("style").attr("aria-hidden",!0).find("a, button").attr("tabindex",-1),InputfieldColumnWidths()}):t.addClass("field-revisions--animatable field-revisions--sliding").slideDown("fast",function(){t.removeClass("field-revisions--sliding").removeAttr("aria-hidden").focus().find("a, button").removeAttr("tabindex"),InputfieldColumnWidths()}),$(window).trigger("resize.revisions-table"))}var f=config.VersionControl,c={},r=$('');window.versionControlPrepareInputfields=function(){var i;$("#version-control-data > div:not(.version-control--prepared)").each(function(){if($(this).data("revision")){var i=$(".Inputfield_"+$(this).data("field"));if(!i.length)return;if($(this).find("a, button").attr("tabindex",-1),$(this).find(".field-revision__current-label:first").attr("aria-hidden",!1),i.find("> label").addClass("version-control--with-history").data("version-control--field",$(this).data("field")).before($(this)),$(this).find("tr:eq(1)").addClass("ui-state-active"),i.hasClass("InputfieldTinyMCE")||i.hasClass("InputfieldCKEditor"))return;i=i.find(".InputfieldContent:first")||i.find("div.ui-widget-content:first");c[$(this).data("field")+"."+$(this).data("revision")]=i.clone(!0,!0)}$(this).addClass("version-control--prepared")}),$(".version-control--with-history:not(.version-control--prepared)").each(function(){var i=$("").hide().appendTo($(this)).css("color"),e=$('').addClass("field-revisions-toggle").attr("title",f.i18n.toggleRevisions).attr("aria-expanded",!1).attr("data-field",$(this).data("version-control--field")).prop("disabled",$("#field-revisions--"+$(this).data("version-control--field")).find("tr").length<2).css("color",i),i=($("").addClass("version-control--visually-hidden").text(f.i18n.toggleRevisions).appendTo(e),$(this).find(".toggle-icon"));i.length?i.after(e):$(this).append(e),$(this).addClass("version-control--prepared")}),$(".field-revisions").off("click.revisions-restore").on("click.revisions-restore",".field-revision__button--restore, .field-revision__current",function(){var i=$(this).parents(".field-revision:first");if(i.hasClass("ui-state-active"))return!1;var e={render:"Input"},t=$(this).parents(".field-revisions:first"),n=i.find(".field-revision__current:first"),s=$(this).parents(".Inputfield:first"),r=t.data("field");s.find(".field-revisions .ui-state-active").removeClass("ui-state-active"),s.find(".field-revision__current-label").attr("aria-hidden",!0),i.addClass("ui-state-active"),n.find(".field-revision__current-label").attr("aria-hidden",!1),$(".compare-revisions").remove(),$(".field-revision__button--diff").removeClass("active");var o=s.find(".InputfieldContent:first")||s.find("div.ui-widget-content:first"),a=$('').hide().css({height:o.innerHeight()+"px",backgroundColor:o.css("background-color")});(s.hasClass("InputfieldTinyMCE")||s.hasClass("InputfieldCKEditor"))&&(e={render:"JSON"});var l=i.data("revision");return c[r+"."+l]?"JSON"!=e.render&&l==t.data("revision")?(o.replaceWith(c[r+"."+l].clone(!0,!0)),s.find(".InputfieldFileList").length&&(s.find(".InputfieldFileInit").removeClass("InputfieldFileInit"),s.trigger("reloaded")),s.find(".InputfieldAsmSelect").length&&(i=s.find("select[multiple=multiple]"),t="undefined"==typeof config?{sortable:!0}:config[i.attr("id")],i.appendTo(s.find(".InputfieldAsmSelect")).show(),s.find(".asmContainer").remove(),i.asmSelect(t)),(s.hasClass("InputfieldImage")||s.hasClass("InputfieldFile"))&&InputfieldImage($)):d(s,o,e,r,c[r+"."+l]):(o.css("position","relative").prepend(a.fadeIn(250)),$.get(f.processPage+"field",{revision:l,field:r,settings:e},function(i){c[r+"."+l]=i,d(s,o,e,r,c[r+"."+l]),a.fadeOut(350,function(){$(this).remove()})})),!1}),$(".field-revisions-toggle").off("click.toggleRevisions").on("click.toggleRevisions",function(i){i.preventDefault(),e($(this).data("field"))}),$(window).off("keyup.field-revisions").on("keyup.field-revisions",function(i){"Esc"!=i.key&&"Escape"!=i.key||(i=$(document.activeElement)).hasClass("field-revisions")&&(i=i.data("field"),e(i),$(".field-revisions-toggle[data-field="+i+"]").focus())}),$(window).off("resize.revisions-table").on("resize.revisions-table",function(){clearTimeout(i),i=setTimeout(function(){$(".field-revisions:not(.field-revisions--animatable)").each(function(){$table=$(this).find("table"),$table.length&&$(this).width()<$table.outerWidth()?$(this).addClass("field-revisions--scrollable").find("> div").trigger("scroll.revisions-table"):$(this).removeClass("field-revisions--scrollable")})},250)}).trigger("resize.revisions-table"),$(".field-revisions > div").off("scroll.revisions-table").on("scroll.revisions-table",function(){$(this).parent().toggleClass("field-revisions--scrollable-start",!$(this).scrollLeft()).toggleClass("field-revisions--scrollable-end",$(this)[0].scrollWidth-$(this).scrollLeft()==$(this).outerWidth())}),$(".field-revisions").off("click.revisions-diff").on("click.revisions-diff",".field-revision__button--diff",function(){var i,e,t,n,s;return $(".compare-revisions").remove(),$(".field-revision__button--diff").not(this).removeClass("field-revisions__button--active"),$(this).toggleClass("field-revisions__button--active"),$(this).hasClass("field-revisions__button--active")&&($(this).attr("aria-expanded",!0),i=$(this).parents(".field-revisions:first"),e=$(this).parents(".field-revision:first"),t=i.data("field"),i=i.find(".ui-state-active:first").data("revision"),e=e.data("revision"),t=f.processPage+"diff/?revisions="+i+":"+e+"&field="+t,n=$("
").attr("tabindex",-1).addClass("compare-revisions").insertAfter($(this)).focus(),s=$(this).parents("tr:first"),n.prepend(r).load(t,function(){s.find("ul.page-diff").length?"function"!=typeof enableDiffSwitch?$.getScript(f.moduleDir+"diff_switch.min.js",function(){enableDiffSwitch(f)}):enableDiffSwitch(f):o();$("").addClass("field-revision__button compare-revisions__close fa fas fa-times").attr("tabindex",0).on("click",function(i){i.preventDefault(),$(this).parent().prev().focus().trigger("click")}).prependTo(n)})),!1})};var o=function(i,e){var t,i=i||document.getElementById("r1").value,e=e||document.getElementById("r2").value;if(i==e)t=""+f.i18n.noDiff+"";else{if("function"!=typeof diff_match_patch)return $.getScript(f.moduleDir+"res/js/diff_match_patch/diff_match_patch.js",function(){o(i,e)}),!1;var n=new diff_match_patch;n.Diff_Timeout=f.diff.timeout,n.Diff_EditCost=f.diff.editCost;var s=(new Date).getTime(),r=n.diff_main(i,e);(new Date).getTime();f.diff.cleanup&&n["diff_cleanup"+f.diff.cleanup](r),t=n.diff_prettyHtml(r)}document.getElementById("diff").innerHTML=t};$.get(f.processPage+"page",{pages_id:f.pageID,settings:{empty:!0,render:"HTML"}},function(i){$("body").prepend(i),versionControlPrepareInputfields(),$(document).on("reloaded",".Inputfield",function(){versionControlPrepareInputfields()})})}); \ No newline at end of file diff --git a/res/js/VersionControl.min.js.map b/res/js/VersionControl.min.js.map index 7e631b7..a90c4cc 100644 --- a/res/js/VersionControl.min.js.map +++ b/res/js/VersionControl.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["res/js/VersionControl.js"],"names":["$","update","$if","$content","settings","field","data","before","render","children","after","html","prepend","append","hasClass","InputfieldImage","attr","moduleConfig","i18n","editDisabled","on","alert","this","hover","parent","addClass","removeClass","insertAfter","remove","find","length","$select","options","config","sortable","asmSelect","setupLanguageTabs","each","property","value","language","replace","tinyMCE","get","setContent","CKEDITOR","instances","setData","toggleRevisions","state","$revisions","undefined","Boolean","toggleClass","slideUp","removeAttr","InputfieldColumnWidths","slideDown","focus","window","trigger","VersionControl","cache","$spinner","versionControlPrepareInputfields","revisions_table_resize_timeout","$cacheobj","clone","revisions_toggle_color","hide","appendTo","css","$revisions_toggle","prop","$toggle_icon","text","off","$revision","parents","$current","$loading","height","innerHeight","backgroundColor","revision","replaceWith","show","fadeIn","processPage","fadeOut","event","preventDefault","key","$active_element","document","activeElement","clearTimeout","setTimeout","$table","width","outerWidth","scrollLeft","scrollWidth","r1","r2","href","$compare_revisions","$parent","not","load","enableDiffSwitch","getScript","moduleDir","enableDiffMatchPatch","prev","prependTo","ds","getElementById","noDiff","diff_match_patch","dmp","Diff_Timeout","diff","timeout","Diff_EditCost","editCost","ms_start","Date","getTime","d","diff_main","cleanup","diff_prettyHtml","innerHTML","pages_id","pageID","empty"],"mappings":"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"} \ No newline at end of file +{"version":3,"sources":["res/js/VersionControl.js"],"names":["$","update","$if","$content","settings","field","data","before","render","children","after","html","prepend","append","hasClass","InputfieldImage","attr","moduleConfig","i18n","editDisabled","on","alert","this","hover","parent","addClass","removeClass","insertAfter","remove","find","length","$select","options","config","sortable","asmSelect","setupLanguageTabs","each","property","value","language","replace","tinyMCE","get","setContent","CKEDITOR","instances","setData","toggleRevisions","state","$revisions","undefined","Boolean","toggleClass","slideUp","removeAttr","InputfieldColumnWidths","slideDown","focus","window","trigger","VersionControl","cache","$spinner","versionControlPrepareInputfields","revisions_table_resize_timeout","$cacheobj","clone","revisions_toggle_color","hide","appendTo","css","$revisions_toggle","prop","$toggle_icon","text","off","$revision","parents","$current","$loading","height","innerHeight","backgroundColor","revision","replaceWith","show","fadeIn","processPage","fadeOut","event","preventDefault","key","$active_element","document","activeElement","clearTimeout","setTimeout","$table","width","outerWidth","scrollLeft","scrollWidth","r1","r2","href","$compare_revisions","$parent","not","load","enableDiffSwitch","getScript","moduleDir","enableDiffMatchPatch","prev","prependTo","ds","getElementById","noDiff","diff_match_patch","dmp","Diff_Timeout","diff","timeout","Diff_EditCost","editCost","ms_start","Date","getTime","d","diff_main","cleanup","diff_prettyHtml","innerHTML","pages_id","pageID","empty"],"mappings":"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"} \ No newline at end of file From 9987ec27e599c36c2cea95a96bea384c97b4cb0b Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 21 Nov 2021 15:23:57 +0100 Subject: [PATCH 02/17] Disable Repeater specific features for now --- VersionControl.module | 4 ++-- VersionControlCleanup.module | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/VersionControl.module b/VersionControl.module index a26c1c0..8e65a43 100644 --- a/VersionControl.module +++ b/VersionControl.module @@ -44,8 +44,8 @@ class VersionControl extends WireData implements Module, ConfigurableModule { 'FieldtypeImage', 'FieldtypeSelector', 'FieldtypeOptions', - 'FieldtypeRepeater', - 'FieldtypeRepeaterMatrix', + // 'FieldtypeRepeater', + // 'FieldtypeRepeaterMatrix', ], 'enabled_templates' => [], 'enable_all_templates' => false, diff --git a/VersionControlCleanup.module b/VersionControlCleanup.module index d7b91dd..be58da9 100644 --- a/VersionControlCleanup.module +++ b/VersionControlCleanup.module @@ -231,14 +231,14 @@ class VersionControlCleanup extends WireData implements Module, ConfigurableModu $page = $event->arguments[0]; + // @todo enable after figuring out a way to clean up once the parent page has been removed // if deleted data is for a Repeater item and parent still exists, we don't want to delete it yet - // @todo Make sure that data for Repeater pages gets deleted later; currently it's left orphaned - if ($page instanceof RepeaterPage) { - $for_page = $page->getForPage(); - if ($for_page->id) { - return; - } - } + // if ($page instanceof RepeaterPage) { + // $for_page = $page->getForPage(); + // if ($for_page->id) { + // return; + // } + // } if ($page instanceof Page) { $this->store->data->deleteForPage($page); From ea3d67d4e6c7640d4f82068e99ed1eefbe16ef4b Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Mon, 31 Jan 2022 18:31:46 +0100 Subject: [PATCH 03/17] Fix PHP 8.1 deprecation (ucfirst with null) --- VersionControl.module | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VersionControl.module b/VersionControl.module index 8e65a43..4702afc 100644 --- a/VersionControl.module +++ b/VersionControl.module @@ -526,7 +526,7 @@ class VersionControl extends WireData implements Module, ConfigurableModule { 'diff' => [ 'timeout' => (int) $this->diff_timeout, 'editCost' => (int) $this->diff_efficiency_cleanup_edit_cost, - 'cleanup' => ucfirst($this->diff_cleanup), + 'cleanup' => $this->diff_cleanup ? ucfirst($this->diff_cleanup) : '', ], 'pageID' => $page->id, 'moduleDir' => $this->config->urls->get('VersionControl'), From 0483b97960f0a977fc5adc45b719a689c6a6a3bb Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Mon, 31 Jan 2022 21:53:33 +0200 Subject: [PATCH 04/17] Fix sorting and AJAX upload for image/file inputs restored from cache --- res/js/HistoryTab.min.js | 2 +- res/js/HistoryTab.min.js.map | 2 +- res/js/VersionControl.js | 26 +++++++++++++++++++++++--- res/js/VersionControl.min.js | 2 +- res/js/VersionControl.min.js.map | 2 +- res/js/diff_switch.min.js.map | 2 +- 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/res/js/HistoryTab.min.js b/res/js/HistoryTab.min.js index da046a1..560a4c7 100644 --- a/res/js/HistoryTab.min.js +++ b/res/js/HistoryTab.min.js @@ -1 +1 @@ -$(function(){var o=config.VersionControl;$("#VersionControlHistory").on("click",".history-tab__button--edit-comment",function(){var t=$(this).parent("td").prev("td"),e=$(this).data("revision"),i=prompt(o.i18n.commentPrompt,t.text());return null!==i&&$.post(o.processPage+"comment",{revision:e,comment:i},function(e){t.text(e).effect("highlight",{},500)}),!1}),$("#VersionControlHistory").on("change","#history_filters select",function(e){var t="";$(this).parents("#history_filters:first").find("input, select").each(function(){t&&(t+="&"),t+=$(this).attr("name")+"="+$(this).val(),e.stopPropagation()}),window.location.search=t});var e=$("#VersionControlHistory");e.length&&e.children("ul:first").data("active")&&$("#_"+e.attr("id")).trigger("click"),$("#ProcessPageEdit").on("submit",function(){$(this).find("#history_filters").remove()}),e.on("click",".history-tab__button--preview",function(){var t=$(this);return $("body").append('
').append('
'),$("#preview iframe").on("load",function(e){$("#preview").addClass("loaded"),$(e.target.contentWindow).on("beforeunload",function(e){e.preventDefault(),i(t)})}),$("#preview-overlay").fadeIn(),$("#preview").show().animate({right:0},500,function(){$("body").addClass("version-control--preview")}),$(document).on("keyup.preview",function(){i(t)}).on("click.preview",function(){i(t)}),!1});var i=function(e){return $(window).off("blur.preview"),$(document).off("keyup.preview"),$(document).off("click.preview"),$("body").removeClass("version-control--preview"),$("#preview-overlay").fadeOut(function(){$(this).remove()}),$("#preview").animate({right:"-80%"},500,function(){$(this).remove()}),e.parents("tr:first").effect("highlight",{},1500),!1};e.on("click",".history-tab__button--restore",function(){return confirm(o.i18n.confirmRestore)})}); \ No newline at end of file +$(function(){var o=config.VersionControl,e=($("#VersionControlHistory").on("click",".history-tab__button--edit-comment",function(){var t=$(this).parent("td").prev("td"),e=$(this).data("revision"),i=prompt(o.i18n.commentPrompt,t.text());return null!==i&&$.post(o.processPage+"comment",{revision:e,comment:i},function(e){t.text(e).effect("highlight",{},500)}),!1}),$("#VersionControlHistory").on("change","#history_filters select",function(e){var t="";$(this).parents("#history_filters:first").find("input, select").each(function(){t&&(t+="&"),t+=$(this).attr("name")+"="+$(this).val(),e.stopPropagation()}),window.location.search=t}),$("#VersionControlHistory")),i=(e.length&&e.children("ul:first").data("active")&&$("#_"+e.attr("id")).trigger("click"),$("#ProcessPageEdit").on("submit",function(){$(this).find("#history_filters").remove()}),e.on("click",".history-tab__button--preview",function(){var t=$(this);return $("body").append('
').append('
'),$("#preview iframe").on("load",function(e){$("#preview").addClass("loaded"),$(e.target.contentWindow).on("beforeunload",function(e){e.preventDefault(),i(t)})}),$("#preview-overlay").fadeIn(),$("#preview").show().animate({right:0},500,function(){$("body").addClass("version-control--preview")}),$(document).on("keyup.preview",function(){i(t)}).on("click.preview",function(){i(t)}),!1}),function(e){return $(window).off("blur.preview"),$(document).off("keyup.preview"),$(document).off("click.preview"),$("body").removeClass("version-control--preview"),$("#preview-overlay").fadeOut(function(){$(this).remove()}),$("#preview").animate({right:"-80%"},500,function(){$(this).remove()}),e.parents("tr:first").effect("highlight",{},1500),!1});e.on("click",".history-tab__button--restore",function(){return confirm(o.i18n.confirmRestore)})}); \ No newline at end of file diff --git a/res/js/HistoryTab.min.js.map b/res/js/HistoryTab.min.js.map index 5134466..aa7f321 100644 --- a/res/js/HistoryTab.min.js.map +++ b/res/js/HistoryTab.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["res/js/HistoryTab.js"],"names":["$","moduleConfig","config","VersionControl","on","$container","this","parent","prev","revision","data","comment","prompt","i18n","commentPrompt","text","post","processPage","effect","event","params","parents","find","each","attr","val","stopPropagation","window","location","search","$tab","length","children","trigger","remove","$a","append","e","addClass","target","contentWindow","preventDefault","closePreview","fadeIn","show","animate","right","document","$button","off","removeClass","fadeOut","confirm","confirmRestore"],"mappings":"AAAAA,EAAE,WAGE,IAAIC,EAAeC,OAAOC,eAG1BH,EAAE,0BAA0BI,GAAG,QAAS,qCAAsC,WAC1E,IAAIC,EAAaL,EAAEM,MAAMC,OAAO,MAAMC,KAAK,MACvCC,EAAWT,EAAEM,MAAMI,KAAK,YACxBC,EAAUC,OAAOX,EAAaY,KAAKC,cAAeT,EAAWU,QAMjE,OALgB,OAAZJ,GACAX,EAAEgB,KAAKf,EAAagB,YAAc,UAAW,CAAER,SAAUA,EAAUE,QAASA,GAAW,SAASD,GAC5FL,EAAWU,KAAKL,GAAMQ,OAAO,YAAa,GAAI,QAG/C,IAIXlB,EAAE,0BAA0BI,GAAG,SAAU,0BAA2B,SAASe,GACzE,IAAIC,EAAS,GACbpB,EAAEM,MAAMe,QAAQ,0BAA0BC,KAAK,iBAAiBC,KAAK,WAC7DH,IAAQA,GAAU,KACtBA,GAAUpB,EAAEM,MAAMkB,KAAK,QAAU,IAAMxB,EAAEM,MAAMmB,MAC/CN,EAAMO,oBAEVC,OAAOC,SAASC,OAAST,IAI7B,IAAIU,EAAO9B,EAAE,0BACT8B,EAAKC,QAAUD,EAAKE,SAAS,YAAYtB,KAAK,WAC9CV,EAAE,KAAO8B,EAAKN,KAAK,OAAOS,QAAQ,SAItCjC,EAAE,oBAAoBI,GAAG,SAAU,WAC/BJ,EAAEM,MAAMgB,KAAK,oBAAoBY,WAIrCJ,EAAK1B,GAAG,QAAS,gCAAiC,WAC9C,IAAI+B,EAAKnC,EAAEM,MAwBX,OAvBAN,EAAE,QACGoC,OAAO,oCACPA,OAAO,kCAAoCD,EAAGX,KAAK,QAAU,8BAClExB,EAAE,mBAAmBI,GAAG,OAAQ,SAASiC,GACrCrC,EAAE,YAAYsC,SAAS,UACvBtC,EAAEqC,EAAEE,OAAOC,eAAepC,GAAG,eAAgB,SAASiC,GAClDA,EAAEI,iBACFC,EAAaP,OAGrBnC,EAAE,oBAAoB2C,SACtB3C,EAAE,YACG4C,OACAC,QAAQ,CAAEC,MAAO,GAAK,IAAK,WACxB9C,EAAE,QAAQsC,SAAS,8BAE3BtC,EAAE+C,UACG3C,GAAG,gBAAiB,WACjBsC,EAAaP,KAEhB/B,GAAG,gBAAiB,WACjBsC,EAAaP,MAEd,IASX,IAAIO,EAAe,SAASM,GAYxB,OAXAhD,EAAE2B,QAAQsB,IAAI,gBACdjD,EAAE+C,UAAUE,IAAI,iBAChBjD,EAAE+C,UAAUE,IAAI,iBAChBjD,EAAE,QAAQkD,YAAY,4BACtBlD,EAAE,oBAAoBmD,QAAQ,WAC1BnD,EAAEM,MAAM4B,WAEZlC,EAAE,YAAY6C,QAAQ,CAAEC,MAAO,QAAU,IAAK,WAC1C9C,EAAEM,MAAM4B,WAEZc,EAAQ3B,QAAQ,YAAYH,OAAO,YAAa,GAAI,OAC7C,GAIXY,EAAK1B,GAAG,QAAS,gCAAiC,WAC9C,OAAOgD,QAAQnD,EAAaY,KAAKwC"} \ No newline at end of file +{"version":3,"sources":["res/js//HistoryTab.js"],"names":["$","moduleConfig","config","VersionControl","$tab","on","$container","this","parent","prev","revision","data","comment","prompt","i18n","commentPrompt","text","post","processPage","effect","event","params","parents","find","each","attr","val","stopPropagation","window","location","search","closePreview","length","children","trigger","remove","$a","append","e","addClass","target","contentWindow","preventDefault","fadeIn","show","animate","right","document","$button","off","removeClass","fadeOut","confirm","confirmRestore"],"mappings":"AAAAA,EAAE,WAGE,IAAIC,EAAeC,OAAOC,eA2BtBC,GAxBJJ,EAAE,0BAA0BK,GAAG,QAAS,qCAAsC,WAC1E,IAAIC,EAAaN,EAAEO,MAAMC,OAAO,MAAMC,KAAK,MACvCC,EAAWV,EAAEO,MAAMI,KAAK,YACxBC,EAAUC,OAAOZ,EAAaa,KAAKC,cAAeT,EAAWU,QAMjE,OALgB,OAAZJ,GACAZ,EAAEiB,KAAKhB,EAAaiB,YAAc,UAAW,CAAER,SAAUA,EAAUE,QAASA,GAAW,SAASD,GAC5FL,EAAWU,KAAKL,GAAMQ,OAAO,YAAa,GAAI,QAG/C,IAIXnB,EAAE,0BAA0BK,GAAG,SAAU,0BAA2B,SAASe,GACzE,IAAIC,EAAS,GACbrB,EAAEO,MAAMe,QAAQ,0BAA0BC,KAAK,iBAAiBC,KAAK,WAC7DH,IAAQA,GAAU,KACtBA,GAAUrB,EAAEO,MAAMkB,KAAK,QAAU,IAAMzB,EAAEO,MAAMmB,MAC/CN,EAAMO,oBAEVC,OAAOC,SAASC,OAAST,IAIlBrB,EAAE,2BA6CT+B,GA5CA3B,EAAK4B,QAAU5B,EAAK6B,SAAS,YAAYtB,KAAK,WAC9CX,EAAE,KAAOI,EAAKqB,KAAK,OAAOS,QAAQ,SAItClC,EAAE,oBAAoBK,GAAG,SAAU,WAC/BL,EAAEO,MAAMgB,KAAK,oBAAoBY,WAIrC/B,EAAKC,GAAG,QAAS,gCAAiC,WAC9C,IAAI+B,EAAKpC,EAAEO,MAwBX,OAvBAP,EAAE,QACGqC,OAAO,oCACPA,OAAO,kCAAoCD,EAAGX,KAAK,QAAU,8BAClEzB,EAAE,mBAAmBK,GAAG,OAAQ,SAASiC,GACrCtC,EAAE,YAAYuC,SAAS,UACvBvC,EAAEsC,EAAEE,OAAOC,eAAepC,GAAG,eAAgB,SAASiC,GAClDA,EAAEI,iBACFX,EAAaK,OAGrBpC,EAAE,oBAAoB2C,SACtB3C,EAAE,YACG4C,OACAC,QAAQ,CAAEC,MAAO,GAAK,IAAK,WACxB9C,EAAE,QAAQuC,SAAS,8BAE3BvC,EAAE+C,UACG1C,GAAG,gBAAiB,WACjB0B,EAAaK,KAEhB/B,GAAG,gBAAiB,WACjB0B,EAAaK,MAEd,IASQ,SAASY,GAYxB,OAXAhD,EAAE4B,QAAQqB,IAAI,gBACdjD,EAAE+C,UAAUE,IAAI,iBAChBjD,EAAE+C,UAAUE,IAAI,iBAChBjD,EAAE,QAAQkD,YAAY,4BACtBlD,EAAE,oBAAoBmD,QAAQ,WAC1BnD,EAAEO,MAAM4B,WAEZnC,EAAE,YAAY6C,QAAQ,CAAEC,MAAO,QAAU,IAAK,WAC1C9C,EAAEO,MAAM4B,WAEZa,EAAQ1B,QAAQ,YAAYH,OAAO,YAAa,GAAI,OAC7C,IAIXf,EAAKC,GAAG,QAAS,gCAAiC,WAC9C,OAAO+C,QAAQnD,EAAaa,KAAKuC"} \ No newline at end of file diff --git a/res/js/VersionControl.js b/res/js/VersionControl.js index 910c990..c145261 100644 --- a/res/js/VersionControl.js +++ b/res/js/VersionControl.js @@ -31,8 +31,9 @@ $(function() { var after = $content.children('p.notes:first'); $content.html(data).prepend(before).append(after); if ($if.hasClass('InputfieldImage') || $if.hasClass('InputfieldFile')) { - // Trigger InputfieldImage() manually. + // Trigger InputfieldImage() and the "reloaded" event manually. InputfieldImage($); + $if.trigger('reloaded'); // Image and file data isn't editable until it has been restored; here we're // applying an overlay layer to prevent editing attempts and avoid confusion. $content.prepend('
'); @@ -152,7 +153,22 @@ $(function() { $(this).find('tr:eq(1)').addClass('ui-state-active'); if ($if.hasClass('InputfieldTinyMCE') || $if.hasClass('InputfieldCKEditor')) return; var $cacheobj = $if.find('.InputfieldContent:first') || $if.find('div.ui-widget-content:first'); - cache[$(this).data('field') + "." + $(this).data('revision')] = $cacheobj.clone(true, true); + var sortableOptions = []; + if ($if.hasClass('InputfieldImage')) { + // Destroy sortables before cloning to avoid an issue where sorting was no longer + // working for inputfield content after being restored from cache. + $if.find('.ui-sortable').each(function() { + sortableOptions.push($(this).sortable('option')); + $(this).sortable('destroy'); + $(this).addClass('version-control--ui-sortable'); + }); + } + var $cacheobjclone = $cacheobj.clone(true, true); + cache[$(this).data('field') + "." + $(this).data('revision')] = $cacheobjclone; + $if.find('.version-control--ui-sortable').each(function() { + // Restore sortables after cloning. + $(this).sortable(sortableOptions.pop()); + }); } $(this).addClass('version-control--prepared'); }); @@ -240,7 +256,11 @@ $(function() { $select.asmSelect(options); } if ($if.hasClass('InputfieldImage') || $if.hasClass('InputfieldFile')) { - // Trigger InputfieldImage() manually. + // Trigger InputfieldImage() and the "reloaded" event manually. Also remove + // init classes (otherwise upload, sorting, etc. are not enabled). + $if.find('.InputfieldImageInitUpload').removeClass('InputfieldImageInitUpload'); + $if.removeClass('InputfieldImageInit'); + $if.trigger('reloaded'); InputfieldImage($); } } else { diff --git a/res/js/VersionControl.min.js b/res/js/VersionControl.min.js index 12fcbd4..1fdd6a2 100644 --- a/res/js/VersionControl.min.js +++ b/res/js/VersionControl.min.js @@ -1 +1 @@ -$(function(){function d(t,i,e,n,s){var r;"Input"==e.render?(r=i.children("p.description:first"),e=i.children("p.notes:first"),i.html(s).prepend(r).append(e),t.hasClass("InputfieldImage")||t.hasClass("InputfieldFile")?(InputfieldImage($),i.prepend('
'),$(".version-control-overlay").attr("title",f.i18n.editDisabled).on("click",function(){alert($(this).attr("title"))}).hover(function(){$(this).parent(".version-control-overlay-parent").addClass("version-control-overlay-parent--hover")},function(){$(this).parent(".version-control-overlay-parent").removeClass("version-control-overlay-parent--hover")}).parent(".InputfieldContent").addClass("version-control-overlay-parent")):t.hasClass("Inputfield_permissions")&&($(".Inputfield_permissions .Inputfield_permissions > .InputfieldContent").insertAfter($(".Inputfield_permissions:first > .InputfieldContent:first")),$(".Inputfield_permissions:first > .InputfieldContent:first").remove()),t.find(".InputfieldAsmSelect").length&&(e=t.find("select[multiple=multiple]"),i="undefined"==typeof config?{sortable:!0}:config[e.attr("id")],e.asmSelect(i)),t.find(".langTabs").length&&"function"==typeof setupLanguageTabs&&setupLanguageTabs(t)):$.each(s,function(i,e){i=(i=i.replace("data",""))&&"__"+i;"undefined"!=typeof tinyMCE&&tinyMCE.get("Inputfield_"+n+i)?tinyMCE.get("Inputfield_"+n+i).setContent(e):t.find(".InputfieldCKEditorInline").length?t.find(".InputfieldCKEditorInline").html(e):"undefined"!=typeof CKEDITOR&&CKEDITOR.instances["Inputfield_"+n+i]&&CKEDITOR.instances["Inputfield_"+n+i].setData(e)})}function e(i,e){var t=$("#field-revisions--"+i);t.length&&(e=void 0===e?!t.attr("aria-hidden"):Boolean(e),$(".field-revisions-toggle[data-field="+i+"]").toggleClass("field-revisions-toggle--active",!e).attr("aria-expanded",!e),e?t.addClass("field-revisions--sliding").slideUp("fast",function(){t.removeClass("field-revisions--sliding").removeAttr("style").attr("aria-hidden",!0).find("a, button").attr("tabindex",-1),InputfieldColumnWidths()}):t.addClass("field-revisions--animatable field-revisions--sliding").slideDown("fast",function(){t.removeClass("field-revisions--sliding").removeAttr("aria-hidden").focus().find("a, button").removeAttr("tabindex"),InputfieldColumnWidths()}),$(window).trigger("resize.revisions-table"))}var f=config.VersionControl,c={},r=$('');window.versionControlPrepareInputfields=function(){var i;$("#version-control-data > div:not(.version-control--prepared)").each(function(){if($(this).data("revision")){var i=$(".Inputfield_"+$(this).data("field"));if(!i.length)return;if($(this).find("a, button").attr("tabindex",-1),$(this).find(".field-revision__current-label:first").attr("aria-hidden",!1),i.find("> label").addClass("version-control--with-history").data("version-control--field",$(this).data("field")).before($(this)),$(this).find("tr:eq(1)").addClass("ui-state-active"),i.hasClass("InputfieldTinyMCE")||i.hasClass("InputfieldCKEditor"))return;i=i.find(".InputfieldContent:first")||i.find("div.ui-widget-content:first");c[$(this).data("field")+"."+$(this).data("revision")]=i.clone(!0,!0)}$(this).addClass("version-control--prepared")}),$(".version-control--with-history:not(.version-control--prepared)").each(function(){var i=$("").hide().appendTo($(this)).css("color"),e=$('').addClass("field-revisions-toggle").attr("title",f.i18n.toggleRevisions).attr("aria-expanded",!1).attr("data-field",$(this).data("version-control--field")).prop("disabled",$("#field-revisions--"+$(this).data("version-control--field")).find("tr").length<2).css("color",i),i=($("").addClass("version-control--visually-hidden").text(f.i18n.toggleRevisions).appendTo(e),$(this).find(".toggle-icon"));i.length?i.after(e):$(this).append(e),$(this).addClass("version-control--prepared")}),$(".field-revisions").off("click.revisions-restore").on("click.revisions-restore",".field-revision__button--restore, .field-revision__current",function(){var i=$(this).parents(".field-revision:first");if(i.hasClass("ui-state-active"))return!1;var e={render:"Input"},t=$(this).parents(".field-revisions:first"),n=i.find(".field-revision__current:first"),s=$(this).parents(".Inputfield:first"),r=t.data("field");s.find(".field-revisions .ui-state-active").removeClass("ui-state-active"),s.find(".field-revision__current-label").attr("aria-hidden",!0),i.addClass("ui-state-active"),n.find(".field-revision__current-label").attr("aria-hidden",!1),$(".compare-revisions").remove(),$(".field-revision__button--diff").removeClass("active");var o=s.find(".InputfieldContent:first")||s.find("div.ui-widget-content:first"),a=$('').hide().css({height:o.innerHeight()+"px",backgroundColor:o.css("background-color")});(s.hasClass("InputfieldTinyMCE")||s.hasClass("InputfieldCKEditor"))&&(e={render:"JSON"});var l=i.data("revision");return c[r+"."+l]?"JSON"!=e.render&&l==t.data("revision")?(o.replaceWith(c[r+"."+l].clone(!0,!0)),s.find(".InputfieldFileList").length&&(s.find(".InputfieldFileInit").removeClass("InputfieldFileInit"),s.trigger("reloaded")),s.find(".InputfieldAsmSelect").length&&(i=s.find("select[multiple=multiple]"),t="undefined"==typeof config?{sortable:!0}:config[i.attr("id")],i.appendTo(s.find(".InputfieldAsmSelect")).show(),s.find(".asmContainer").remove(),i.asmSelect(t)),(s.hasClass("InputfieldImage")||s.hasClass("InputfieldFile"))&&InputfieldImage($)):d(s,o,e,r,c[r+"."+l]):(o.css("position","relative").prepend(a.fadeIn(250)),$.get(f.processPage+"field",{revision:l,field:r,settings:e},function(i){c[r+"."+l]=i,d(s,o,e,r,c[r+"."+l]),a.fadeOut(350,function(){$(this).remove()})})),!1}),$(".field-revisions-toggle").off("click.toggleRevisions").on("click.toggleRevisions",function(i){i.preventDefault(),e($(this).data("field"))}),$(window).off("keyup.field-revisions").on("keyup.field-revisions",function(i){"Esc"!=i.key&&"Escape"!=i.key||(i=$(document.activeElement)).hasClass("field-revisions")&&(i=i.data("field"),e(i),$(".field-revisions-toggle[data-field="+i+"]").focus())}),$(window).off("resize.revisions-table").on("resize.revisions-table",function(){clearTimeout(i),i=setTimeout(function(){$(".field-revisions:not(.field-revisions--animatable)").each(function(){$table=$(this).find("table"),$table.length&&$(this).width()<$table.outerWidth()?$(this).addClass("field-revisions--scrollable").find("> div").trigger("scroll.revisions-table"):$(this).removeClass("field-revisions--scrollable")})},250)}).trigger("resize.revisions-table"),$(".field-revisions > div").off("scroll.revisions-table").on("scroll.revisions-table",function(){$(this).parent().toggleClass("field-revisions--scrollable-start",!$(this).scrollLeft()).toggleClass("field-revisions--scrollable-end",$(this)[0].scrollWidth-$(this).scrollLeft()==$(this).outerWidth())}),$(".field-revisions").off("click.revisions-diff").on("click.revisions-diff",".field-revision__button--diff",function(){var i,e,t,n,s;return $(".compare-revisions").remove(),$(".field-revision__button--diff").not(this).removeClass("field-revisions__button--active"),$(this).toggleClass("field-revisions__button--active"),$(this).hasClass("field-revisions__button--active")&&($(this).attr("aria-expanded",!0),i=$(this).parents(".field-revisions:first"),e=$(this).parents(".field-revision:first"),t=i.data("field"),i=i.find(".ui-state-active:first").data("revision"),e=e.data("revision"),t=f.processPage+"diff/?revisions="+i+":"+e+"&field="+t,n=$("
").attr("tabindex",-1).addClass("compare-revisions").insertAfter($(this)).focus(),s=$(this).parents("tr:first"),n.prepend(r).load(t,function(){s.find("ul.page-diff").length?"function"!=typeof enableDiffSwitch?$.getScript(f.moduleDir+"diff_switch.min.js",function(){enableDiffSwitch(f)}):enableDiffSwitch(f):o();$("").addClass("field-revision__button compare-revisions__close fa fas fa-times").attr("tabindex",0).on("click",function(i){i.preventDefault(),$(this).parent().prev().focus().trigger("click")}).prependTo(n)})),!1})};var o=function(i,e){var t,i=i||document.getElementById("r1").value,e=e||document.getElementById("r2").value;if(i==e)t=""+f.i18n.noDiff+"";else{if("function"!=typeof diff_match_patch)return $.getScript(f.moduleDir+"res/js/diff_match_patch/diff_match_patch.js",function(){o(i,e)}),!1;var n=new diff_match_patch;n.Diff_Timeout=f.diff.timeout,n.Diff_EditCost=f.diff.editCost;var s=(new Date).getTime(),r=n.diff_main(i,e);(new Date).getTime();f.diff.cleanup&&n["diff_cleanup"+f.diff.cleanup](r),t=n.diff_prettyHtml(r)}document.getElementById("diff").innerHTML=t};$.get(f.processPage+"page",{pages_id:f.pageID,settings:{empty:!0,render:"HTML"}},function(i){$("body").prepend(i),versionControlPrepareInputfields(),$(document).on("reloaded",".Inputfield",function(){versionControlPrepareInputfields()})})}); \ No newline at end of file +$(function(){function d(t,e,i,n,s){var o;"Input"==i.render?(i=e.children("p.description:first"),o=e.children("p.notes:first"),e.html(s).prepend(i).append(o),t.hasClass("InputfieldImage")||t.hasClass("InputfieldFile")?(InputfieldImage($),t.trigger("reloaded"),e.prepend('
'),$(".version-control-overlay").attr("title",f.i18n.editDisabled).on("click",function(){alert($(this).attr("title"))}).hover(function(){$(this).parent(".version-control-overlay-parent").addClass("version-control-overlay-parent--hover")},function(){$(this).parent(".version-control-overlay-parent").removeClass("version-control-overlay-parent--hover")}).parent(".InputfieldContent").addClass("version-control-overlay-parent")):t.hasClass("Inputfield_permissions")&&($(".Inputfield_permissions .Inputfield_permissions > .InputfieldContent").insertAfter($(".Inputfield_permissions:first > .InputfieldContent:first")),$(".Inputfield_permissions:first > .InputfieldContent:first").remove()),t.find(".InputfieldAsmSelect").length&&(i=t.find("select[multiple=multiple]"),o="undefined"==typeof config?{sortable:!0}:config[i.attr("id")],i.asmSelect(o)),t.find(".langTabs").length&&"function"==typeof setupLanguageTabs&&setupLanguageTabs(t)):$.each(s,function(e,i){e=(e=e.replace("data",""))&&"__"+e;"undefined"!=typeof tinyMCE&&tinyMCE.get("Inputfield_"+n+e)?tinyMCE.get("Inputfield_"+n+e).setContent(i):t.find(".InputfieldCKEditorInline").length?t.find(".InputfieldCKEditorInline").html(i):"undefined"!=typeof CKEDITOR&&CKEDITOR.instances["Inputfield_"+n+e]&&CKEDITOR.instances["Inputfield_"+n+e].setData(i)})}function i(e,i){var t=$("#field-revisions--"+e);t.length&&(i=void 0===i?!t.attr("aria-hidden"):Boolean(i),$(".field-revisions-toggle[data-field="+e+"]").toggleClass("field-revisions-toggle--active",!i).attr("aria-expanded",!i),i?t.addClass("field-revisions--sliding").slideUp("fast",function(){t.removeClass("field-revisions--sliding").removeAttr("style").attr("aria-hidden",!0).find("a, button").attr("tabindex",-1),InputfieldColumnWidths()}):t.addClass("field-revisions--animatable field-revisions--sliding").slideDown("fast",function(){t.removeClass("field-revisions--sliding").removeAttr("aria-hidden").focus().find("a, button").removeAttr("tabindex"),InputfieldColumnWidths()}),$(window).trigger("resize.revisions-table"))}var f=config.VersionControl,c={},o=$(''),r=(window.versionControlPrepareInputfields=function(){var e;$("#version-control-data > div:not(.version-control--prepared)").each(function(){if($(this).data("revision")){var e=$(".Inputfield_"+$(this).data("field"));if(!e.length)return;if($(this).find("a, button").attr("tabindex",-1),$(this).find(".field-revision__current-label:first").attr("aria-hidden",!1),e.find("> label").addClass("version-control--with-history").data("version-control--field",$(this).data("field")).before($(this)),$(this).find("tr:eq(1)").addClass("ui-state-active"),e.hasClass("InputfieldTinyMCE")||e.hasClass("InputfieldCKEditor"))return;var i=e.find(".InputfieldContent:first")||e.find("div.ui-widget-content:first"),t=[],i=(e.hasClass("InputfieldImage")&&e.find(".ui-sortable").each(function(){t.push($(this).sortable("option")),$(this).sortable("destroy"),$(this).addClass("version-control--ui-sortable")}),i.clone(!0,!0));c[$(this).data("field")+"."+$(this).data("revision")]=i,e.find(".version-control--ui-sortable").each(function(){$(this).sortable(t.pop())})}$(this).addClass("version-control--prepared")}),$(".version-control--with-history:not(.version-control--prepared)").each(function(){var e=$("").hide().appendTo($(this)).css("color"),e=$('').addClass("field-revisions-toggle").attr("title",f.i18n.toggleRevisions).attr("aria-expanded",!1).attr("data-field",$(this).data("version-control--field")).prop("disabled",$("#field-revisions--"+$(this).data("version-control--field")).find("tr").length<2).css("color",e),i=($("").addClass("version-control--visually-hidden").text(f.i18n.toggleRevisions).appendTo(e),$(this).find(".toggle-icon"));i.length?i.after(e):$(this).append(e),$(this).addClass("version-control--prepared")}),$(".field-revisions").off("click.revisions-restore").on("click.revisions-restore",".field-revision__button--restore, .field-revision__current",function(){var e=$(this).parents(".field-revision:first");if(e.hasClass("ui-state-active"))return!1;var i={render:"Input"},t=$(this).parents(".field-revisions:first"),n=e.find(".field-revision__current:first"),s=$(this).parents(".Inputfield:first"),o=t.data("field"),r=(s.find(".field-revisions .ui-state-active").removeClass("ui-state-active"),s.find(".field-revision__current-label").attr("aria-hidden",!0),e.addClass("ui-state-active"),n.find(".field-revision__current-label").attr("aria-hidden",!1),$(".compare-revisions").remove(),$(".field-revision__button--diff").removeClass("active"),s.find(".InputfieldContent:first")||s.find("div.ui-widget-content:first")),a=$('').hide().css({height:r.innerHeight()+"px",backgroundColor:r.css("background-color")}),l=((s.hasClass("InputfieldTinyMCE")||s.hasClass("InputfieldCKEditor"))&&(i={render:"JSON"}),e.data("revision"));return c[o+"."+l]?"JSON"!=i.render&&l==t.data("revision")?(r.replaceWith(c[o+"."+l].clone(!0,!0)),s.find(".InputfieldFileList").length&&(s.find(".InputfieldFileInit").removeClass("InputfieldFileInit"),s.trigger("reloaded")),s.find(".InputfieldAsmSelect").length&&(n=s.find("select[multiple=multiple]"),e="undefined"==typeof config?{sortable:!0}:config[n.attr("id")],n.appendTo(s.find(".InputfieldAsmSelect")).show(),s.find(".asmContainer").remove(),n.asmSelect(e)),(s.hasClass("InputfieldImage")||s.hasClass("InputfieldFile"))&&(s.find(".InputfieldImageInitUpload").removeClass("InputfieldImageInitUpload"),s.removeClass("InputfieldImageInit"),s.trigger("reloaded"),InputfieldImage($))):d(s,r,i,o,c[o+"."+l]):(r.css("position","relative").prepend(a.fadeIn(250)),$.get(f.processPage+"field",{revision:l,field:o,settings:i},function(e){c[o+"."+l]=e,d(s,r,i,o,c[o+"."+l]),a.fadeOut(350,function(){$(this).remove()})})),!1}),$(".field-revisions-toggle").off("click.toggleRevisions").on("click.toggleRevisions",function(e){e.preventDefault(),i($(this).data("field"))}),$(window).off("keyup.field-revisions").on("keyup.field-revisions",function(e){"Esc"!=e.key&&"Escape"!=e.key||(e=$(document.activeElement)).hasClass("field-revisions")&&(e=e.data("field"),i(e),$(".field-revisions-toggle[data-field="+e+"]").focus())}),$(window).off("resize.revisions-table").on("resize.revisions-table",function(){clearTimeout(e),e=setTimeout(function(){$(".field-revisions:not(.field-revisions--animatable)").each(function(){$table=$(this).find("table"),$table.length&&$(this).width()<$table.outerWidth()?$(this).addClass("field-revisions--scrollable").find("> div").trigger("scroll.revisions-table"):$(this).removeClass("field-revisions--scrollable")})},250)}).trigger("resize.revisions-table"),$(".field-revisions > div").off("scroll.revisions-table").on("scroll.revisions-table",function(){$(this).parent().toggleClass("field-revisions--scrollable-start",!$(this).scrollLeft()).toggleClass("field-revisions--scrollable-end",$(this)[0].scrollWidth-$(this).scrollLeft()==$(this).outerWidth())}),$(".field-revisions").off("click.revisions-diff").on("click.revisions-diff",".field-revision__button--diff",function(){var e,i,t,n,s;return $(".compare-revisions").remove(),$(".field-revision__button--diff").not(this).removeClass("field-revisions__button--active"),$(this).toggleClass("field-revisions__button--active"),$(this).hasClass("field-revisions__button--active")&&($(this).attr("aria-expanded",!0),t=$(this).parents(".field-revisions:first"),i=$(this).parents(".field-revision:first"),e=t.data("field"),t=t.find(".ui-state-active:first").data("revision"),i=i.data("revision"),t=f.processPage+"diff/?revisions="+t+":"+i+"&field="+e,n=$("
").attr("tabindex",-1).addClass("compare-revisions").insertAfter($(this)).focus(),s=$(this).parents("tr:first"),n.prepend(o).load(t,function(){s.find("ul.page-diff").length?"function"!=typeof enableDiffSwitch?$.getScript(f.moduleDir+"diff_switch.min.js",function(){enableDiffSwitch(f)}):enableDiffSwitch(f):r();$("").addClass("field-revision__button compare-revisions__close fa fas fa-times").attr("tabindex",0).on("click",function(e){e.preventDefault(),$(this).parent().prev().focus().trigger("click")}).prependTo(n)})),!1})},function(e,i){var t,e=e||document.getElementById("r1").value,i=i||document.getElementById("r2").value;if(e==i)t=""+f.i18n.noDiff+"";else{if("function"!=typeof diff_match_patch)return $.getScript(f.moduleDir+"res/js/diff_match_patch/diff_match_patch.js",function(){r(e,i)}),!1;var n=new diff_match_patch,s=(n.Diff_Timeout=f.diff.timeout,n.Diff_EditCost=f.diff.editCost,(new Date).getTime()),o=n.diff_main(e,i);(new Date).getTime();f.diff.cleanup&&n["diff_cleanup"+f.diff.cleanup](o),t=n.diff_prettyHtml(o)}document.getElementById("diff").innerHTML=t});$.get(f.processPage+"page",{pages_id:f.pageID,settings:{empty:!0,render:"HTML"}},function(e){$("body").prepend(e),versionControlPrepareInputfields(),$(document).on("reloaded",".Inputfield",function(){versionControlPrepareInputfields()})})}); \ No newline at end of file diff --git a/res/js/VersionControl.min.js.map b/res/js/VersionControl.min.js.map index a90c4cc..6b20fb0 100644 --- a/res/js/VersionControl.min.js.map +++ b/res/js/VersionControl.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["res/js/VersionControl.js"],"names":["$","update","$if","$content","settings","field","data","before","render","children","after","html","prepend","append","hasClass","InputfieldImage","attr","moduleConfig","i18n","editDisabled","on","alert","this","hover","parent","addClass","removeClass","insertAfter","remove","find","length","$select","options","config","sortable","asmSelect","setupLanguageTabs","each","property","value","language","replace","tinyMCE","get","setContent","CKEDITOR","instances","setData","toggleRevisions","state","$revisions","undefined","Boolean","toggleClass","slideUp","removeAttr","InputfieldColumnWidths","slideDown","focus","window","trigger","VersionControl","cache","$spinner","versionControlPrepareInputfields","revisions_table_resize_timeout","$cacheobj","clone","revisions_toggle_color","hide","appendTo","css","$revisions_toggle","prop","$toggle_icon","text","off","$revision","parents","$current","$loading","height","innerHeight","backgroundColor","revision","replaceWith","show","fadeIn","processPage","fadeOut","event","preventDefault","key","$active_element","document","activeElement","clearTimeout","setTimeout","$table","width","outerWidth","scrollLeft","scrollWidth","r1","r2","href","$compare_revisions","$parent","not","load","enableDiffSwitch","getScript","moduleDir","enableDiffMatchPatch","prev","prependTo","ds","getElementById","noDiff","diff_match_patch","dmp","Diff_Timeout","diff","timeout","Diff_EditCost","editCost","ms_start","Date","getTime","d","diff_main","cleanup","diff_prettyHtml","innerHTML","pages_id","pageID","empty"],"mappings":"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"} \ No newline at end of file +{"version":3,"sources":["res/js//VersionControl.js"],"names":["$","update","$if","$content","settings","field","data","options","render","before","children","after","html","prepend","append","hasClass","InputfieldImage","trigger","attr","moduleConfig","i18n","editDisabled","on","alert","this","hover","parent","addClass","removeClass","insertAfter","remove","find","length","$select","config","sortable","asmSelect","setupLanguageTabs","each","property","value","language","replace","tinyMCE","get","setContent","CKEDITOR","instances","setData","toggleRevisions","state","$revisions","undefined","Boolean","toggleClass","slideUp","removeAttr","InputfieldColumnWidths","slideDown","focus","window","VersionControl","cache","$spinner","enableDiffMatchPatch","versionControlPrepareInputfields","revisions_table_resize_timeout","$cacheobj","sortableOptions","$cacheobjclone","push","clone","pop","revisions_toggle_color","hide","appendTo","css","$revisions_toggle","prop","$toggle_icon","text","off","$revision","parents","$current","$loading","height","innerHeight","backgroundColor","revision","replaceWith","show","fadeIn","processPage","fadeOut","event","preventDefault","key","$active_element","document","activeElement","clearTimeout","setTimeout","$table","width","outerWidth","scrollLeft","scrollWidth","r2","href","$compare_revisions","$parent","not","r1","load","enableDiffSwitch","getScript","moduleDir","prev","prependTo","ds","getElementById","noDiff","diff_match_patch","dmp","ms_start","Diff_Timeout","diff","timeout","Diff_EditCost","editCost","Date","getTime","d","diff_main","cleanup","diff_prettyHtml","innerHTML","pages_id","pageID","empty"],"mappings":"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"} \ No newline at end of file diff --git a/res/js/diff_switch.min.js.map b/res/js/diff_switch.min.js.map index 83c77c7..bd355ee 100644 --- a/res/js/diff_switch.min.js.map +++ b/res/js/diff_switch.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["res/js/diff_switch.js"],"names":["enableDiffSwitch","moduleConfig","diff_table","diff_div","document","getElementById","diff_switch","createElement","className","text","i18n","diffSideBySide","href","addEventListener","event","previous_tag_name","diff_row","preventDefault","style","length","diffList","id","Array","prototype","forEach","call","children","item","i","diff_col","tagName","insertBefore","appendChild","innerHTML","toLowerCase","cloneNode","parentNode"],"mappings":"AAAA,IAAIA,iBAAmB,SAASC,GAC5B,IAAIC,EACAC,EAAWC,SAASC,eAAe,QACnCC,EAAcF,SAASG,cAAc,KACzCD,EAAYE,UAAY,cACxBF,EAAYG,KAAOR,EAAaS,KAAKC,eACrCL,EAAYM,KAAO,IACnBN,EAAYO,iBAAiB,QAAS,SAASC,GAE3C,IAGQC,EAAmBC,EAJ3BF,EAAMG,iBACDb,SAASC,eAAe,cAmDlBH,EAAWgB,MAAMC,QACxBhB,EAASe,MAAQ,gBACjBhB,EAAWgB,MAAQ,GACnBZ,EAAYG,KAAOR,EAAaS,KAAKU,SACrCd,EAAYE,UAAY,iCAExBL,EAASe,MAAQ,GACjBhB,EAAWgB,MAAQ,gBACnBZ,EAAYG,KAAOR,EAAaS,KAAKC,eACrCL,EAAYE,UAAY,iBA3DxBN,EAAaE,SAASG,cAAc,UACzBc,GAAK,aAEhBC,MAAMC,UAAUC,QAAQC,KAAKtB,EAASuB,SAAS,GAAGA,SAAU,SAASC,EAAMC,GAavE,IAcQC,EA1BHb,GAAwC,GAA5BA,EAASU,SAASP,QAA2C,QAA5BQ,EAAKD,SAAS,GAAGI,UAC3Dd,GAAwC,GAA5BA,EAASU,SAASP,SACL,OAArBJ,EACAC,EAASe,aAAa3B,SAASG,cAAc,MAAOS,EAASU,SAAS,IAEtEV,EAASgB,YAAY5B,SAASG,cAAc,QAGpDS,EAAWZ,SAASG,cAAc,MAClCL,EAAW8B,YAAYhB,GACvBD,EAAoB,KAEnBA,GAAiD,QAA5BY,EAAKD,SAAS,GAAGI,SAAkD,OAA5BH,EAAKD,SAAS,GAAGI,SAAyC,OAArBf,GAA4D,OAA5BY,EAAKD,SAAS,GAAGI,SAAyC,OAArBf,IACnKc,EAAWzB,SAASG,cAAc,OAC7B0B,UAAYN,EAAKM,UAC1BJ,EAASrB,UAAY,YAAcmB,EAAKD,SAAS,GAAGI,QAAQI,cAC5DlB,EAASgB,YAAYH,GACW,QAA5BF,EAAKD,SAAS,GAAGI,SACjBd,EAASgB,YAAYH,EAASM,WAAU,KAErCpB,GAAqBY,EAAKD,SAAS,GAAGI,UACpB,OAArBf,EACAC,EAASe,aAAa3B,SAASG,cAAc,MAAOS,EAASU,SAAS,IAEtEV,EAASgB,YAAY5B,SAASG,cAAc,QAE5CsB,EAAWzB,SAASG,cAAc,OAC7B0B,UAAYN,EAAKM,UAC1BJ,EAASrB,UAAY,YAAcmB,EAAKD,SAAS,GAAGI,QAAQI,eAC5DlB,EAAWZ,SAASG,cAAc,OACzByB,YAAYH,GACrB3B,EAAW8B,YAAYhB,IAE3BD,EAAoBY,EAAKD,SAAS,GAAGI,UAErCd,GAAwC,GAA5BA,EAASU,SAASP,SACL,OAArBJ,EACAC,EAASe,aAAa3B,SAASG,cAAc,MAAOS,EAASU,SAAS,IAEtEV,EAASgB,YAAY5B,SAASG,cAAc,QAGpDJ,EAASiC,WAAWJ,YAAY9B,GAChCC,EAASe,MAAQ,gBACjBZ,EAAYG,KAAOR,EAAaS,KAAKU,SACrCd,EAAYE,UAAY,kCAahCL,EAASiC,WAAWL,aAAazB,EAAaH"} \ No newline at end of file +{"version":3,"sources":["res/js//diff_switch.js"],"names":["enableDiffSwitch","moduleConfig","diff_table","diff_div","document","getElementById","diff_switch","createElement","className","text","i18n","diffSideBySide","href","addEventListener","event","previous_tag_name","diff_row","preventDefault","style","length","diffList","id","Array","prototype","forEach","call","children","item","i","diff_col","tagName","insertBefore","appendChild","innerHTML","toLowerCase","cloneNode","parentNode"],"mappings":"AAAA,IAAIA,iBAAmB,SAASC,GAC5B,IAAIC,EACAC,EAAWC,SAASC,eAAe,QACnCC,EAAcF,SAASG,cAAc,KACzCD,EAAYE,UAAY,cACxBF,EAAYG,KAAOR,EAAaS,KAAKC,eACrCL,EAAYM,KAAO,IACnBN,EAAYO,iBAAiB,QAAS,SAASC,GAE3C,IAGQC,EAAmBC,EAJ3BF,EAAMG,iBACDb,SAASC,eAAe,cAmDlBH,EAAWgB,MAAMC,QACxBhB,EAASe,MAAQ,gBACjBhB,EAAWgB,MAAQ,GACnBZ,EAAYG,KAAOR,EAAaS,KAAKU,SACrCd,EAAYE,UAAY,iCAExBL,EAASe,MAAQ,GACjBhB,EAAWgB,MAAQ,gBACnBZ,EAAYG,KAAOR,EAAaS,KAAKC,eACrCL,EAAYE,UAAY,iBA3DxBN,EAAaE,SAASG,cAAc,UACzBc,GAAK,aAEhBC,MAAMC,UAAUC,QAAQC,KAAKtB,EAASuB,SAAS,GAAGA,SAAU,SAASC,EAAMC,GAavE,IAcQC,EA1BHb,GAAwC,GAA5BA,EAASU,SAASP,QAA2C,QAA5BQ,EAAKD,SAAS,GAAGI,UAC3Dd,GAAwC,GAA5BA,EAASU,SAASP,SACL,OAArBJ,EACAC,EAASe,aAAa3B,SAASG,cAAc,MAAOS,EAASU,SAAS,IAEtEV,EAASgB,YAAY5B,SAASG,cAAc,QAGpDS,EAAWZ,SAASG,cAAc,MAClCL,EAAW8B,YAAYhB,GACvBD,EAAoB,KAEnBA,GAAiD,QAA5BY,EAAKD,SAAS,GAAGI,SAAkD,OAA5BH,EAAKD,SAAS,GAAGI,SAAyC,OAArBf,GAA4D,OAA5BY,EAAKD,SAAS,GAAGI,SAAyC,OAArBf,IAEvKc,EADezB,SAASG,cAAc,OAC7B0B,UAAYN,EAAKM,UAC1BJ,EAASrB,UAAY,YAAcmB,EAAKD,SAAS,GAAGI,QAAQI,cAC5DlB,EAASgB,YAAYH,GACW,QAA5BF,EAAKD,SAAS,GAAGI,SACjBd,EAASgB,YAAYH,EAASM,WAAU,KAErCpB,GAAqBY,EAAKD,SAAS,GAAGI,UACpB,OAArBf,EACAC,EAASe,aAAa3B,SAASG,cAAc,MAAOS,EAASU,SAAS,IAEtEV,EAASgB,YAAY5B,SAASG,cAAc,QAGhDsB,EADezB,SAASG,cAAc,OAC7B0B,UAAYN,EAAKM,UAC1BJ,EAASrB,UAAY,YAAcmB,EAAKD,SAAS,GAAGI,QAAQI,eAC5DlB,EAAWZ,SAASG,cAAc,OACzByB,YAAYH,GACrB3B,EAAW8B,YAAYhB,IAE3BD,EAAoBY,EAAKD,SAAS,GAAGI,UAErCd,GAAwC,GAA5BA,EAASU,SAASP,SACL,OAArBJ,EACAC,EAASe,aAAa3B,SAASG,cAAc,MAAOS,EAASU,SAAS,IAEtEV,EAASgB,YAAY5B,SAASG,cAAc,QAGpDJ,EAASiC,WAAWJ,YAAY9B,GAChCC,EAASe,MAAQ,gBACjBZ,EAAYG,KAAOR,EAAaS,KAAKU,SACrCd,EAAYE,UAAY,kCAahCL,EAASiC,WAAWL,aAAazB,EAAaH"} \ No newline at end of file From 6f3faef9b25492182b9e4e77f0d0b490ee977084 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 28 Aug 2022 20:17:41 +0300 Subject: [PATCH 05/17] Fix likely typo in code --- VersionControl.module | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/VersionControl.module b/VersionControl.module index 4702afc..cff63e8 100644 --- a/VersionControl.module +++ b/VersionControl.module @@ -587,10 +587,8 @@ class VersionControl extends WireData implements Module, ConfigurableModule { $keys = array_keys($event->return); if (($pos = array_search('ProcessPageEditSettings', $keys)) !== false) { $pos += 1; - } else { - if (($pos = array_search('ProcessPageEditDelete', $keys)) === false) {; - $pos = array_search('ProcessPageEditView', $keys); - } + } else if (($pos = array_search('ProcessPageEditDelete', $keys)) === false) { + $pos = array_search('ProcessPageEditView', $keys); } if ($pos === false) return; From 8b3f56ac924f50c73669fc2e27babf7e67778249 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 28 Aug 2022 20:18:57 +0300 Subject: [PATCH 06/17] Fix issue with duplicate ID in markup --- lib/MarkupHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/MarkupHelper.php b/lib/MarkupHelper.php index 672e86d..3ac7139 100644 --- a/lib/MarkupHelper.php +++ b/lib/MarkupHelper.php @@ -122,6 +122,7 @@ public function getHistoryTab(array $data, Page $page): InputfieldWrapper { $tab->prepend($fieldset); $field = $this->modules->get('InputfieldHidden'); + $field->id = 'history_filters__id'; $field->name = 'id'; $field->value = $page->id; $fieldset->add($field); From 0ad32e4569d166717fa52e0342809cd238a88110 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 25 Sep 2022 20:06:18 +0300 Subject: [PATCH 07/17] Update copyright year --- PageSnapshot.module | 2 +- ProcessVersionControl.module | 2 +- VersionControlCleanup.module | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PageSnapshot.module b/PageSnapshot.module index e6d0b4f..3049617 100644 --- a/PageSnapshot.module +++ b/PageSnapshot.module @@ -10,7 +10,7 @@ use VersionControl\DataStore; * Original code for this module was posted by SteveB at the ProcessWire support forum: * https://processwire.com/talk/topic/2892-module-version-control-for-text-fields/?p=50438 * - * @copyright 2014-2021 Teppo Koivula & SteveB + * @copyright 2014-2022 Teppo Koivula & SteveB * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 */ class PageSnapshot extends WireData implements Module { diff --git a/ProcessVersionControl.module b/ProcessVersionControl.module index 222aad6..681fce1 100644 --- a/ProcessVersionControl.module +++ b/ProcessVersionControl.module @@ -11,7 +11,7 @@ use VersionControl\i18n; * This module acts as an interface for Version Control module by making itself available via an Admin page and * generating markup based on GET params. For more details see the README.md file distributed with this module. * - * @copyright 2013-2021 Teppo Koivula + * @copyright 2013-2022 Teppo Koivula * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 */ class ProcessVersionControl extends Process implements Module, ConfigurableModule { diff --git a/VersionControlCleanup.module b/VersionControlCleanup.module index be58da9..5ea7899 100644 --- a/VersionControlCleanup.module +++ b/VersionControlCleanup.module @@ -14,7 +14,7 @@ use VersionControl\Data\Files; * want to store unnecessary data, so with this module we're making sure that expired rows and * orphaned files etc. don't just stick around. * - * @copyright 2019-2021 Teppo Koivula + * @copyright 2019-2022 Teppo Koivula * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 */ class VersionControlCleanup extends WireData implements Module, ConfigurableModule { From 0a1ef427065c77d82d5b68685fa09acf7837ffa1 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 25 Sep 2022 20:06:40 +0300 Subject: [PATCH 08/17] Remove unnecessary use statements --- lib/data/Data.php | 1 - lib/data/Files.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/data/Data.php b/lib/data/Data.php index 219226d..03d64f3 100644 --- a/lib/data/Data.php +++ b/lib/data/Data.php @@ -5,7 +5,6 @@ use VersionControl\DataObject; use ProcessWire\Page; -use ProcessWire\User; /** * Version Control Data diff --git a/lib/data/Files.php b/lib/data/Files.php index 192e428..c158902 100644 --- a/lib/data/Files.php +++ b/lib/data/Files.php @@ -5,8 +5,6 @@ use VersionControl\DataObject; use VersionControl\i18n; -use ProcessWire\Page; -use ProcessWire\User; use ProcessWire\PagefilesManager; use function ProcessWire\wireMkdir; From 7f8d489aaf90fa3024d3e994f9e8520c4e449f22 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 25 Sep 2022 21:28:59 +0300 Subject: [PATCH 09/17] Avoid extra rows for temp files and add hook to handle secure page files --- VersionControl.module | 73 ++++++++++++++++++++++++++++++++++++++++++- lib/ModuleConfig.php | 9 ++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/VersionControl.module b/VersionControl.module index cff63e8..f6dfe54 100644 --- a/VersionControl.module +++ b/VersionControl.module @@ -12,7 +12,7 @@ use VersionControl\i18n; * automatically harvested for enabled fields and templates and stored in custom database tables. Stored data can be * viewed and restored via the Admin GUI (Page Edit) or the API. * - * @copyright 2013-2021 Teppo Koivula + * @copyright 2013-2022 Teppo Koivula * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 * @todo Regenerate data when fields are added to a tracked Template/Fieldgroup (see ProcessWire issue #1247). */ @@ -50,6 +50,7 @@ class VersionControl extends WireData implements Module, ConfigurableModule { 'enabled_templates' => [], 'enable_all_templates' => false, 'enable_locked_fields' => false, + 'secure_file_access' => true, ]; /** @@ -185,6 +186,11 @@ class VersionControl extends WireData implements Module, ConfigurableModule { // Add hook that removes hashes within text content of a rendered page. $this->addHookAfter('Page::render', $this, 'replaceTextHashes'); + if ($this->secure_file_access && $this->config->pagefileSecure) { + // Add hook that serves stored files for authorized users when secure pagefiles are enabled. + $this->addHookAfter('ProcessPageView::pageNotFound', $this, 'serveSecureFile'); + } + // Add new property versionControlFields to Template object. $this->addHookProperty('Template::versionControlFields', $this, 'versionControlFields'); @@ -282,6 +288,54 @@ class VersionControl extends WireData implements Module, ConfigurableModule { $event->return = preg_replace("#(?return); } + /** + * Attempt to serve pagefile when secure pagefiles are enabled and user makes request to stored file + * + * Note: this method validates permissions, file name format, existence of the file, etc. to avoid accidentally + * serving files that should remain inaccessible. Even then it's not possible to be 100% certain that the user + * should be allowed access to the file, which is why this hook is only enabled if the 'secure_file_access' + * configuration setting is enabled as well. + * + * @param HookEvent $event + */ + protected function serveSecureFile(HookEvent $event) { + + // check if this is a potential secure file request + $url = $event->arguments[1] ?? ''; + if ($url == '' || strpos($url, $this->config->urls->files) !== 0) return; + + // make sure that current user is logged in and has the page-edit permission + if (!$this->user->isLoggedin() || !$this->user->hasPermission('page-edit')) return; + + // get page ID for requested file + $file_page_id = explode('/', substr($url, strlen($this->config->urls->files)))[0] ?? 0; + if (!$file_page_id || (int) $file_page_id != $file_page_id) return; + + // get page and make sure it's editable by current user + $file_page = $this->pages->get((int) $file_page_id); + if (!$file_page->id || !$file_page->editable()) return; + + // make sure that requested file actually exists + $file_path = substr($url, strlen($this->config->urls->files) + strlen((string) $file_page->id) + 1); + $file = $this->config->paths->files . $this->config->pagefileSecurePathPrefix . $file_page->id . \DIRECTORY_SEPARATOR . $file_path; + if (strpos($file, '?')) $file = strtok($file, '?'); + if (!is_file($file)) return; + + // as a precaution validate file path; we only want to serve files managed by Version Control + $num_slashes = substr_count($file_path, '/'); + if ($num_slashes < 1 || $num_slashes > 2) return; + $filename = array_pop(explode('/', $file_path)); + if (!$filename || substr_count($filename, '.') < 2) return; + + // serve file and stop processing current request + http_response_code(200); + header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + wireSendFile($file); + exit; + } + /** * Update file fields if Page has _version_control_filedata or if POST param version_control_filedata is set * @@ -469,6 +523,23 @@ class VersionControl extends WireData implements Module, ConfigurableModule { $data = $field->type->___sleepValue($page, $field, $data); } + // Temporary files should be omitted, as they will be stored at a later time + if ($data instanceof Pagefiles) { + $num_files = $data->count(); + if ($num_files) { + $num_temps = 0; + foreach ($data as $file) { + if (!$file->isTemp()) continue; + ++$num_temps; + } + if ($num_temps == $num_files) { + continue 2; + } + } + } else if ($data instanceof Pagefile && $data->isTemp()) { + continue 2; + } + // Store data in database. $this->store->data->saveForField($fields_id, [$property => $data], $revisions_id); } diff --git a/lib/ModuleConfig.php b/lib/ModuleConfig.php index d467bdd..43a6d22 100644 --- a/lib/ModuleConfig.php +++ b/lib/ModuleConfig.php @@ -154,6 +154,15 @@ public function getFields() { $field->attr('checked', isset($data['enable_all_templates']) && $data['enable_all_templates']); $fieldset->add($field); + // enable access to Version Control managed files in secure pagefile directories + $field = $modules->get("InputfieldCheckbox"); + $field->name = "secure_file_access"; + $field->label = $this->_("Allow access to secure files"); + $field->description = $this->_("If secure page files are enabled and user attempts to view an earlier version of a file without restoring it first, ProcessWire will block access to said file. By default we attempt to circumvent this if requested file appears to be one managed by Version Control and current user has edit access to related page, but in case file directories are used to store other non-core content there's always the possiblity of something being unintentionally made viewable."); + $field->notes = $this->_("This option is enabled by default. If you disable it, users will not be able to preview files for non-public pages if core secure page file option is enabled."); + $fieldset->add($field); + $field->attr('checked', isset($data['secure_file_access']) && $data['secure_file_access']); + return $fields; } From 7a17dd3bb6c8e9aee714dc4049342ef39642c080 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 25 Sep 2022 23:09:46 +0300 Subject: [PATCH 10/17] Simplify access to secure pagefiles --- VersionControl.module | 20 +++++--------------- lib/ModuleConfig.php | 9 --------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/VersionControl.module b/VersionControl.module index f6dfe54..b575ac7 100644 --- a/VersionControl.module +++ b/VersionControl.module @@ -50,7 +50,6 @@ class VersionControl extends WireData implements Module, ConfigurableModule { 'enabled_templates' => [], 'enable_all_templates' => false, 'enable_locked_fields' => false, - 'secure_file_access' => true, ]; /** @@ -186,7 +185,7 @@ class VersionControl extends WireData implements Module, ConfigurableModule { // Add hook that removes hashes within text content of a rendered page. $this->addHookAfter('Page::render', $this, 'replaceTextHashes'); - if ($this->secure_file_access && $this->config->pagefileSecure) { + if ($this->config->pagefileSecure) { // Add hook that serves stored files for authorized users when secure pagefiles are enabled. $this->addHookAfter('ProcessPageView::pageNotFound', $this, 'serveSecureFile'); } @@ -304,16 +303,16 @@ class VersionControl extends WireData implements Module, ConfigurableModule { $url = $event->arguments[1] ?? ''; if ($url == '' || strpos($url, $this->config->urls->files) !== 0) return; - // make sure that current user is logged in and has the page-edit permission - if (!$this->user->isLoggedin() || !$this->user->hasPermission('page-edit')) return; + // make sure that current user is logged in and has the version-control permission + if (!$this->user->isLoggedin() || !$this->user->hasPermission('version-control')) return; // get page ID for requested file $file_page_id = explode('/', substr($url, strlen($this->config->urls->files)))[0] ?? 0; if (!$file_page_id || (int) $file_page_id != $file_page_id) return; - // get page and make sure it's editable by current user + // get page and make sure it's the Version Control process page $file_page = $this->pages->get((int) $file_page_id); - if (!$file_page->id || !$file_page->editable()) return; + if (!$file_page->id || $file_page->process != 'ProcessVersionControl') return; // make sure that requested file actually exists $file_path = substr($url, strlen($this->config->urls->files) + strlen((string) $file_page->id) + 1); @@ -321,17 +320,8 @@ class VersionControl extends WireData implements Module, ConfigurableModule { if (strpos($file, '?')) $file = strtok($file, '?'); if (!is_file($file)) return; - // as a precaution validate file path; we only want to serve files managed by Version Control - $num_slashes = substr_count($file_path, '/'); - if ($num_slashes < 1 || $num_slashes > 2) return; - $filename = array_pop(explode('/', $file_path)); - if (!$filename || substr_count($filename, '.') < 2) return; - // serve file and stop processing current request http_response_code(200); - header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); - header("Cache-Control: post-check=0, pre-check=0", false); - header("Pragma: no-cache"); wireSendFile($file); exit; } diff --git a/lib/ModuleConfig.php b/lib/ModuleConfig.php index 43a6d22..d467bdd 100644 --- a/lib/ModuleConfig.php +++ b/lib/ModuleConfig.php @@ -154,15 +154,6 @@ public function getFields() { $field->attr('checked', isset($data['enable_all_templates']) && $data['enable_all_templates']); $fieldset->add($field); - // enable access to Version Control managed files in secure pagefile directories - $field = $modules->get("InputfieldCheckbox"); - $field->name = "secure_file_access"; - $field->label = $this->_("Allow access to secure files"); - $field->description = $this->_("If secure page files are enabled and user attempts to view an earlier version of a file without restoring it first, ProcessWire will block access to said file. By default we attempt to circumvent this if requested file appears to be one managed by Version Control and current user has edit access to related page, but in case file directories are used to store other non-core content there's always the possiblity of something being unintentionally made viewable."); - $field->notes = $this->_("This option is enabled by default. If you disable it, users will not be able to preview files for non-public pages if core secure page file option is enabled."); - $fieldset->add($field); - $field->attr('checked', isset($data['secure_file_access']) && $data['secure_file_access']); - return $fields; } From 3577bcc68d7e4df8e461bce6a039e4dd4c2bd731 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Sun, 2 Oct 2022 13:04:14 +0300 Subject: [PATCH 11/17] Small updates to comments --- VersionControl.module | 9 +++++---- lib/data/Data.php | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/VersionControl.module b/VersionControl.module index b575ac7..ff08ed7 100644 --- a/VersionControl.module +++ b/VersionControl.module @@ -9,11 +9,12 @@ use VersionControl\i18n; * Version Control * * This module adds support for storing, viewing, and restoring earlier versions (values) of fields and pages. Data is - * automatically harvested for enabled fields and templates and stored in custom database tables. Stored data can be - * viewed and restored via the Admin GUI (Page Edit) or the API. + * automatically harvested for enabled fields and templates, and stored in custom database tables. Stored data can be + * viewed and restored via the Admin GUI (in Page Edit) or via the API. * * @copyright 2013-2022 Teppo Koivula * @license https://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License, version 2 + * * @todo Regenerate data when fields are added to a tracked Template/Fieldgroup (see ProcessWire issue #1247). */ class VersionControl extends WireData implements Module, ConfigurableModule { @@ -942,8 +943,8 @@ class VersionControl extends WireData implements Module, ConfigurableModule { $all_new_fields = []; $all_old_fields = []; if (!$new_fields && $old_fields || $new_fields && !$old_fields) { - // A minor optimization: if both are undefined we never come this far since there have - // obviously been no changes to enabled fields. + // If statement above is a minor optimization: if both old and new fields are undefined + // there have been no changes to enabled fields, so there's no need to come this far. foreach ($this->templates->find('id=' . implode('|', array_unique(array_merge($old_templates, $new_templates)))) as $template) { foreach ($template->fields as $field) { if (in_array($field->type, $this->compatible_fieldtypes)) { diff --git a/lib/data/Data.php b/lib/data/Data.php index 03d64f3..27b8ddb 100644 --- a/lib/data/Data.php +++ b/lib/data/Data.php @@ -320,7 +320,7 @@ public function purge(string $max_age): bool { } /** - * Get field ID based on mixed value input + * Get field ID based on mixed input * * @param int|string|Field $field * @return int|null From 3a76a3a42962b2bf52d2f08b5125182aa22b7385 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Tue, 1 Nov 2022 17:46:29 +0200 Subject: [PATCH 12/17] Add translations for Finnish --- languages/fi.csv | 104 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 languages/fi.csv diff --git a/languages/fi.csv b/languages/fi.csv new file mode 100644 index 0000000..7f072f0 --- /dev/null +++ b/languages/fi.csv @@ -0,0 +1,104 @@ +en,fi,description,file,hash +"Configuring the Version Control module bundle","Version Control -moduuliryhmän konfigurointi",,site/modules/VersionControl/lib/ProcessModuleConfig.php,2ca4ef1f394f8db64aa52f7bb177e79b +"All configuration settings for the Version Control module bundle can be found from the %s module.","Kaikki asetukset Version Control -moduuliin liittyen löytyvät %s -moduulista.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,40af9696444b1871975876b49f8f07c0 +"Output Settings",Tulostusasetukset,,site/modules/VersionControl/lib/ProcessModuleConfig.php,dd906a6f86cf14458b96f65560d0cb99 +"Date Format","Päivämäärän muotoilu",,site/modules/VersionControl/lib/ProcessModuleConfig.php,8b6f6d305407f0b686f1974206ad415c +"Used when displaying version history data in page edit.","Käytetään kun versiohistoriaa esitetään sivunmuokkausnäkymässä.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,a47feadcfd0a5717663fd7a673d06ed6 +"See the [PHP date](http://www.php.net/manual/en/function.date.php) function reference for more information on how to customize this format.","Lisää tietoa päivämäärän muotoiluasetuksiin liittyen löytyy [PHP:n date-funktion](http://www.php.net/manual/en/function.date.php) ohjeista.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,eda5cc2428c46f14966a92580ce8df1c +"User Name Format","Käyttäjätunnuksen muotoilu",,site/modules/VersionControl/lib/ProcessModuleConfig.php,a1ed5a914b47eaaf640034386816bd5d +"This defines the format and field(s) used to represent user names.","Tämä määrittää muodon ja kentän (tai kentät) joita käytetään kun esitetään käyttäjätunnuksia.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,51a60a5845a88e2ec62743fda4ea35fb +"This string is passed to wirePopulateStringTags() function. Example: {name} ({email}).","Valinnan arvo välitetään wirePopulateStringTags() -funktiolle. Esimerkki: {name} ({email}).",,site/modules/VersionControl/lib/ProcessModuleConfig.php,1864d121561bb6c06178c2baeffa7748 +"Diff Settings",Vertailuasetukset,,site/modules/VersionControl/lib/ProcessModuleConfig.php,a8a3da7c5eba7fac7bd358f5012b83e0 +"Disable diff","Poista vertailu käytöstä",,site/modules/VersionControl/lib/ProcessModuleConfig.php,b61508996bb43a31d40ee2ebd467da3e +"Diff Timeout","Vertailun aikaviive",,site/modules/VersionControl/lib/ProcessModuleConfig.php,0cb6c62734ea7546b27622e0eca80d53 +"If diff computation takes longer than this, best solution to date is returned. While correct, it may not be optimal.","Jos vertailun muodostaminen kestää tätä pidempään, käytetään sen hetken parasta tulosta. Tämä tulos ei välttämättä ole paras mahdollinen.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,044b1598df30d1825abcb1dee4026de9 +"A timeout of '0' allows for unlimited computation.","Aikaviiveen arvo '0' ei rajoita laskennan kestoa.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,5580337bdb2c199f407c1bff5c2fa6ac +"Post-diff Cleanup","Vertailun jälkeinen siistiminen",,site/modules/VersionControl/lib/ProcessModuleConfig.php,3f39347e882a3e1d132f409814ad1b7c +"Post-diff cleanup algorithms attempt to filter out irrelevant small commonalities, thus enhancing final output.","Vertailun jälkeisen siistimisen algoritmi pyrkii poistamaan vertailusta epäolennaiset yhteiset tekijät, siten parantaen lopputulosta.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,f555c9674334bcaf29d817a38ed2937a +"See [Diff Demo](https://neil.fraser.name/software/diff_match_patch/svn/trunk/demos/demo_diff.html) for examples and detailed descriptions.","[Vertailun demosta](https://neil.fraser.name/software/diff_match_patch/svn/trunk/demos/demo_diff.html) löydät esimerkkjä ja tarkemmat kuvaukset.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,a8f2b2da4be593c886047131b1524710 +"No Cleanup","Ei siistimistä",,site/modules/VersionControl/lib/ProcessModuleConfig.php,f8c32e5d5b9a0fe237b1aa27b32a55d0 +"Semantic Cleanup","Semanttinen siistiminen",,site/modules/VersionControl/lib/ProcessModuleConfig.php,19f23dd0e5bdf38ecfa3c681287cc16a +"Efficiency Cleanup","Tehokas siistiminen",,site/modules/VersionControl/lib/ProcessModuleConfig.php,02fed2850aea7fa7d41ea87bef40aa67 +"Efficiency Cleanup Edit Cost","Tehokkaan siistimisen kustannus",,site/modules/VersionControl/lib/ProcessModuleConfig.php,c8efeaf01ada1621547cf6541521a98b +"The larger the edit cost, the more agressive the cleanup.","Mitä suurempi kustannus, sen argressiivisemmin tulosta siistitään.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,f397623ad5cddce695447314173b6100 +"General Settings","Yleiset asetukset",,site/modules/VersionControl/lib/ModuleConfig.php,52f4393e1b52ba63e27310ca92ba098c +"Enable version control for these templates","Ota versionhallinta käyttöön näille kentille",,site/modules/VersionControl/lib/ModuleConfig.php,a97330dffd1d694a229fbf933a01c021 +"This setting has no effect because version control is currently enabled for all templates via Advanced Settings.","Tällä valinnalla ei ole tällä hetkellä merkitystä, koska versionhallinta on otettu käyttöön kaikille sivupohjille edistyneiden asetusten kautta.",,site/modules/VersionControl/lib/ModuleConfig.php,b696a9b16e7f378f992d49ba02555df7 +"Removing a template from here will also remove any data stored for it.","Sivupohjan poistaminen tältä listalta poistaa myös siihen liittyen tallennetut tiedot.",,site/modules/VersionControl/lib/ModuleConfig.php,499c7d7575db417553a10bcacce77255 +"Enable version control for these fields","Ota versionhallinta käyttöön näille kentille",,site/modules/VersionControl/lib/ModuleConfig.php,269f6a19a5b90e2852df2e70a0136f0c +"Only fields of compatible fieldtypes can be selected. If no fields are selected, all fields of compatible fieldtypes are considered enabled. Removing a field from here will also remove any data stored for it.","Voit valita vain yhteensopivia kenttiä. Jos ainuttakaan kenttää ei ole valittu, versionhallinta on käytössä kaikille yhteensopiville kentille. Kentän poistaminen tältä listalta poistaa myös siihen liittyen tallennetut tiedot.",,site/modules/VersionControl/lib/ModuleConfig.php,b43a2280c8b2605c8bf9219914ab5435 +"Enable version control for locked fields","Ota versionhallinta käyttöön lukituille kentille",,site/modules/VersionControl/lib/ModuleConfig.php,7e91c433a640bd1f2d9a344b1886d21b +"By default version control features are not displayed for locked (uneditable) fields. If this option is enabled, users will be able to view version history, but still won't be able to restore earlier revisions.","Oletuksena versionhallinnan toimintoja ei näytetä lukituille (ei-muokattaville) kentille. Jos tämä valinta on päällä, käyttäjät voivat tarkastella näihin liittyvää versiohistoriaa, mutta eivät voi palauttaa aiempia versioita.",,site/modules/VersionControl/lib/ModuleConfig.php,bcfd8e77dfd5509b3a925b0373068939 +"Advanced Settings","Edistyneet asetukset",,site/modules/VersionControl/lib/ModuleConfig.php,9ffc3ccc968a96d902af963c6d7b4e97 +"Compatible fieldtypes","Yhteensopivat kenttätyypit",,site/modules/VersionControl/lib/ModuleConfig.php,95986dc7ced60b0c34d297e5bc57362a +"Fieldtypes considered compatible with this module.","Kenttätyypit, jotka ovat yhteensopivia tämän moduulin kanssa.",,site/modules/VersionControl/lib/ModuleConfig.php,1ad66bae8e85e16d6cf2e09864ddc03a +"Please note that selecting any fieldtypes not selected by default may result in various problems.","Huomaathan, että sellaisten kenttien valitseminen jotka eivät ole oletuksena valittuja voi aiheuttaa ongelmia.",,site/modules/VersionControl/lib/ModuleConfig.php,dd940b9edae894d8e3a5eca78a7953d4 +"Enable version control for all templates","Ota versionhallinta käyttöön kaikille sivupohjille",,site/modules/VersionControl/lib/ModuleConfig.php,6ecc2774d58329572acaaad363839cf4 +"If this option is selected, Version Control will track changes to all templates.","Jos tämä valinta on päällä, versiohistoriaa tallennetaan kaikkien sivupohjien osalta.",,site/modules/VersionControl/lib/ModuleConfig.php,7e0d50273d74f2f0ebd6ba8307b596db +"This could result in very large amounts of collected data, and cause other unexpected side effects!","Tämä valinta voi johtaa siihen, että tietoa kerätään merkittäviä määrä, sekä aiheuttaa muita yllättäviä sivuseurauksia!",,site/modules/VersionControl/lib/ModuleConfig.php,381c6f7a283486bf587a570ce84106af +"Added fieldtypes: %s","Lisätyt kenttätyypit: %s",,site/modules/VersionControl/lib/ModuleConfig.php,6585b4d2bf569677ce08bcdc65514f95 +"Removed fieldtypes: %s","Poistetut kenttätyypit: %s",,site/modules/VersionControl/lib/ModuleConfig.php,18016d4552dc872860b05026e77cacfa +"Removed directory: %s","Poistettiin kansio: %s",,site/modules/VersionControl/lib/i18n.php,0ba71cc56d2aa413cd0bd3157b68a40d +History,Historia,"Tab Label: History",site/modules/VersionControl/lib/i18n.php,16d2b386b2034b9488996466aaae0b57 +Revision,Versio,,site/modules/VersionControl/lib/i18n.php,32c676ac5296556c0573a301ef5ab07b +Author,Käyttäjä,,site/modules/VersionControl/lib/i18n.php,a517747c3d12f99244ae598910d979c5 +Changes,Muutokset,,site/modules/VersionControl/lib/i18n.php,c112bb3542e98308d12d5ecb10a67abc +Timestamp,Aikaleima,,site/modules/VersionControl/lib/i18n.php,a3d5de3eac8bb00ae86fd1a1005f1500 +Comment,Kommentti,,site/modules/VersionControl/lib/i18n.php,0be8406951cdfda82f00f79328cf4efc +"Edit comment","Muokkaa kommenttia",,site/modules/VersionControl/lib/i18n.php,3216c36604b3d4125718d86bb9b89593 +"Restore revision","Palauta versio",,site/modules/VersionControl/lib/i18n.php,d54a35e2bdbe6ad9692efdc2b661fae3 +"Preview revision","Esikatsele versiota",,site/modules/VersionControl/lib/i18n.php,69fb565d295edfe9ef5a034edeaacd96 +"No history of changes found.","Historiatietoja ei löytynyt.",,site/modules/VersionControl/lib/i18n.php,6fb6435cd651c82119fb763bd9d9eb8b +Filters,Suodattimet,,site/modules/VersionControl/lib/i18n.php,f3f43e30c8c7d78c6ac0173515e57a00 +All,Kaikki,,site/modules/VersionControl/lib/i18n.php,b1c94ca2fbc3e78fc30069c8d0f01680 +"Filter by Author","Suodata käyttäjän mukaan",,site/modules/VersionControl/lib/i18n.php,4eb0785ece85fe48f83326d53a7cc93b +"When selected, only revisions authored by specific user will be shown.","Kun tämä on valittu, näytetään vain versiot jotka valittu käyttäjä on luonut.",,site/modules/VersionControl/lib/i18n.php,912cc439d3b3073997709b48283e2eb2 +Restore,Palauta,,site/modules/VersionControl/lib/i18n.php,2bd339d85ee3b33e513359ce781b60cc +Compare,Vertaa,,site/modules/VersionControl/lib/i18n.php,7eece51cf3938103677db7a5051ef8f5 +"Current revision","Nykyinen versio",,site/modules/VersionControl/lib/i18n.php,da2d486f24a8ab0acaae24cc7fc2e1a8 +"Stored revisions for field %s","Tallennetut versiot kentälle %s",,site/modules/VersionControl/lib/i18n.php,4ff542ace09012c289447260879002ea +"There are no earlier versions of this field available","Tälle kentälle ei ole saatavilla aiempia versioita",,site/modules/VersionControl/lib/i18n.php,3206076428d0fe1d85c3d2ba14d6f20d +"Table %s already exists","Taulu %s on jo olemassa",,site/modules/VersionControl/lib/i18n.php,fe97e509818446fa01c7b3c8573df78c +"Created table: %s","Luotiin taulu: %s",,site/modules/VersionControl/lib/i18n.php,39042b30c158883f432492e8325c7c95 +"Dropped table: %s","Tuhottiin taulu: %s",,site/modules/VersionControl/lib/i18n.php,e711f6ac2f755bec9b4f0e4cb418bc31 +"Imported existing Version Control For Text Fields data","Tiedot tuotiin Version Control For Text Fields -moduulilta",,site/modules/VersionControl/lib/i18n.php,081035c498fb1bec650696f1ead09323 +"Unrecognized path","Tunnistamaton polku",,site/modules/VersionControl/lib/i18n.php,3742382258205b5fde2cbb85b01cefe6 +"Page reverted to revision #%d","Sivu palautettu versioon #%d",,site/modules/VersionControl/lib/i18n.php,6ac8c871150bd705dfc14387b59866be +"Page doesn't exist: %d","Sivua ei ole olemassa: %d",,site/modules/VersionControl/lib/i18n.php,ad0d9fbb2d01181512c7eb2aa5fef5ff +"Permission denied (Page not viewable)","Ei oikeutta (sivu ei ole katseltavissa)",,site/modules/VersionControl/lib/i18n.php,dc767772f46e86658e64f23473ed2216 +"Permission denied (Page not editable)","Ei oikeutta (sivu ei ole muokattavissa)",,site/modules/VersionControl/lib/i18n.php,960d2b1f7df48f66a43017d642b9732e +"Revision doesn't exist: %d","Versiota ei ole olemassa: %d",,site/modules/VersionControl/lib/i18n.php,3a15d9aa53ab78a5841592dba48034cf +"Compare with current","Vertaa nykyiseen",,site/modules/VersionControl/lib/i18n.php,e4eff3e10828ac6fdd7052b40da7ff6b +"Editing this data is currently disabled. Restore it by saving the page or switch to current version first.","Tämän arvon muokkaaminen ei ole juuri nyt sallittu. Palauta arvo tallentamalla sivu tai vaihda ensin nykyiseen versioon.",,site/modules/VersionControl/lib/i18n.php,7990e195793caf681d24e5f4af990015 +"Type in comment text for this revision (max 255 characters)","Lisää kommentti tähän versioon (korkeintaan 255 merkkiä)",,site/modules/VersionControl/lib/i18n.php,545e1ee2896ebcef3b64540041ab3a96 +"This is the page as it appeared on %s. Click here to close the preview.","Sivu näytti tältä %s. Klikkaa tästä sulkeaksesi esikatselun.",,site/modules/VersionControl/lib/i18n.php,987acaaa32b1db07e88f9a19842ec181 +"Are you sure that you want to revert this page to an earlier revision?","Haluatko varmasti palauttaa sivun aiempaan versioon?",,site/modules/VersionControl/lib/i18n.php,9dbc97d60acf88417376db408a0030ed +"Show side by side","Näytä vierekkäin",,site/modules/VersionControl/lib/i18n.php,2cb17f0e67476d5f8e2a9af86713e52c +"Show as a list","Näytä listana",,site/modules/VersionControl/lib/i18n.php,f6fe40ccb62530bb659775a2b6bc0c14 +"There is no difference between these revisions.","Näiden versioiden välillä ei ole eroja.",,site/modules/VersionControl/lib/i18n.php,eba28d6caca6a4724111a5c6d27676de +"Toggle a list of revisions","Näytä tai piilota versioiden listaus",,site/modules/VersionControl/lib/i18n.php,858901d042014a3e0766b1729db69d28 +"Removed stored data for field %s","Poistettiin tallennetut tiedot kentälle %s",,site/modules/VersionControl/lib/i18n.php,95f76904d9652e36b767992ee566ee92 +"Populated data for %d page using template %s","Lisättiin tiedot %d sivupohjaa %s käyttävän sivun osalta","Singular Version",site/modules/VersionControl/lib/i18n.php,bb85c534be0ee55f5a92386b3a92fb31 +"Populated data for %d pages using template %s","Lisättiin tiedot %d sivupohjaa %s käyttävän sivun osalta","Plural Version",site/modules/VersionControl/lib/i18n.php,1d6f3cc256f9da6d04df92fde113efef +"Removed stored data for %d page using template %s","Poistettiin tiedot %d sivupohjaa %s käyttävän sivun osalta","Singular Version",site/modules/VersionControl/lib/i18n.php,d7301b4418f8458b6d4d93563df8ef5f +"Removed stored data for %d pages using template %s","Poistettiin tiedot %d sivupohjaa %s käyttävän sivun osalta","Plural Version",site/modules/VersionControl/lib/i18n.php,c3e885b12858224da7568f7f734924b1 +"Configuring the Version Control module bundle","Version Control -moduuliryhmän konfigurointi",,site/modules/VersionControl/lib/CleanupModuleConfig.php,2ca4ef1f394f8db64aa52f7bb177e79b +"All configuration settings for the Version Control module bundle can be found from the %s module.","Kaikki asetukset Version Control -moduuliin liittyen löytyvät %s -moduulista.",,site/modules/VersionControl/lib/CleanupModuleConfig.php,40af9696444b1871975876b49f8f07c0 +"Cleanup Settings",Siivousasetukset,,site/modules/VersionControl/lib/CleanupModuleConfig.php,caa3ac6397bd83436426cc64115abbe6 +"1 week","1 viikko",,site/modules/VersionControl/lib/CleanupModuleConfig.php,705fbbd6e1667845569c00fd7523d343 +"2 weeks","2 viikkoa",,site/modules/VersionControl/lib/CleanupModuleConfig.php,a9adf02fea7b0107b5a712a45e51076a +"1 month","1 kuukausi",,site/modules/VersionControl/lib/CleanupModuleConfig.php,1634e66b522edaa910370cc5581a47f1 +"2 months","2 kuukautta",,site/modules/VersionControl/lib/CleanupModuleConfig.php,e6d19733e42320b3240bb706c16c832b +"3 months","3 kuukautta",,site/modules/VersionControl/lib/CleanupModuleConfig.php,de0353cab16c9faa68162dffe7b3ec08 +"6 months","6 kuukautta",,site/modules/VersionControl/lib/CleanupModuleConfig.php,9f7aa8ce719604f269027cacec634cf1 +"1 year","1 vuosi",,site/modules/VersionControl/lib/CleanupModuleConfig.php,ca4c73c1f333c437a47c58afd0623530 +"Leave empty to disable automatic time-based cleanup.","Jätä tyhjäksi jos et halua siivota tietoja automaattisesti aikaan perustuen.",,site/modules/VersionControl/lib/CleanupModuleConfig.php,719f87659763b80e4c73d5854f36ef5b +"Automatic cleanup requires Lazy Cron module.","Automaattinen siivous vaatii Lazy Cron -moduulin.",,site/modules/VersionControl/lib/CleanupModuleConfig.php,fef36c3c742678daad1e4e4076fe206c +"For how long should we retain collected data?","Kuinka pitkään tietoja tulisi säilyttää?",,site/modules/VersionControl/lib/CleanupModuleConfig.php,336f9c65317f2f65b1e8fac040528252 +"Revisions retained for each field + page combination","Säilytettävien versioiden määrä kutakin kenttä ja sivu -yhdistelmää kohti",,site/modules/VersionControl/lib/CleanupModuleConfig.php,c5166cdbb9fc07aa73a1bab827e90c7e +"Leave empty to not impose limits for stored revisions.","Jätä tyhjäksi jos et halua rajoittaa säilytettävien versioiden määrää.",,site/modules/VersionControl/lib/CleanupModuleConfig.php,edf0566ec3045f830eb0550e5249a24f +"Additional cleanup methods","Lisää siivousvalintoja",,site/modules/VersionControl/lib/CleanupModuleConfig.php,ca072f08e58c7a9b702c6d4099d22226 +"Delete all stored data for deleted pages","Tuhoa poistettuihin sivuihin liittyvät tiedot",,site/modules/VersionControl/lib/CleanupModuleConfig.php,8a0d72383387ad4f274bcffeef251aff +"Delete all stored data for deleted fields","Tuhoa poistettuihin kenttiin liittyvät tiedot",,site/modules/VersionControl/lib/CleanupModuleConfig.php,703cff7892d93652c3b055dceb754fcc +"Delete data stored for non-existing fields after template is changed","Sivupohjan vaihdon jälkeen tuhoa tiedot, joihin liittyvää kenttää ei löydy uudesta sivupohjasta",,site/modules/VersionControl/lib/CleanupModuleConfig.php,d54cd57a1cdc9b12d5fc934aca054019 +"Delete data stored for fields that are removed from page's fieldgroup","Tuhoa sivun kenttäryhmästä poistettaviin kenttiin liittyvät tiedot",,site/modules/VersionControl/lib/CleanupModuleConfig.php,a98afe2de1bdfec8158b67b31d5fc00e From d5f85bbcfdcb54dbc4ba30b2385a52e4c05296f3 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Tue, 1 Nov 2022 22:55:39 +0200 Subject: [PATCH 13/17] Fix wrong translation in FI language file --- languages/fi.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages/fi.csv b/languages/fi.csv index 7f072f0..4ed47e7 100644 --- a/languages/fi.csv +++ b/languages/fi.csv @@ -22,7 +22,7 @@ en,fi,description,file,hash "Efficiency Cleanup Edit Cost","Tehokkaan siistimisen kustannus",,site/modules/VersionControl/lib/ProcessModuleConfig.php,c8efeaf01ada1621547cf6541521a98b "The larger the edit cost, the more agressive the cleanup.","Mitä suurempi kustannus, sen argressiivisemmin tulosta siistitään.",,site/modules/VersionControl/lib/ProcessModuleConfig.php,f397623ad5cddce695447314173b6100 "General Settings","Yleiset asetukset",,site/modules/VersionControl/lib/ModuleConfig.php,52f4393e1b52ba63e27310ca92ba098c -"Enable version control for these templates","Ota versionhallinta käyttöön näille kentille",,site/modules/VersionControl/lib/ModuleConfig.php,a97330dffd1d694a229fbf933a01c021 +"Enable version control for these templates","Ota versionhallinta käyttöön näille sivupohjille",,site/modules/VersionControl/lib/ModuleConfig.php,a97330dffd1d694a229fbf933a01c021 "This setting has no effect because version control is currently enabled for all templates via Advanced Settings.","Tällä valinnalla ei ole tällä hetkellä merkitystä, koska versionhallinta on otettu käyttöön kaikille sivupohjille edistyneiden asetusten kautta.",,site/modules/VersionControl/lib/ModuleConfig.php,b696a9b16e7f378f992d49ba02555df7 "Removing a template from here will also remove any data stored for it.","Sivupohjan poistaminen tältä listalta poistaa myös siihen liittyen tallennetut tiedot.",,site/modules/VersionControl/lib/ModuleConfig.php,499c7d7575db417553a10bcacce77255 "Enable version control for these fields","Ota versionhallinta käyttöön näille kentille",,site/modules/VersionControl/lib/ModuleConfig.php,269f6a19a5b90e2852df2e70a0136f0c From 403a89e6e18e3ff360a3a8331fa22692523f7c60 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Tue, 1 Nov 2022 22:56:02 +0200 Subject: [PATCH 14/17] Add hookable methods shouldReadPageData and shouldReadFieldData --- VersionControl.info.json | 2 +- VersionControl.module | 140 ++++++++++++++++++++++++--------------- 2 files changed, 86 insertions(+), 56 deletions(-) diff --git a/VersionControl.info.json b/VersionControl.info.json index 5ef497b..d56c4db 100644 --- a/VersionControl.info.json +++ b/VersionControl.info.json @@ -1,7 +1,7 @@ { "title": "Version Control", "summary": "Version control features for page content.", - "version": "2.5.0", + "version": "2.6.0", "href": "https://modules.processwire.com/modules/version-control/", "author": "Teppo Koivula", "installs": [ diff --git a/VersionControl.module b/VersionControl.module index ff08ed7..4df9d10 100644 --- a/VersionControl.module +++ b/VersionControl.module @@ -402,67 +402,97 @@ class VersionControl extends WireData implements Module, ConfigurableModule { // If Page has no ID, it's currently being added. $page_id = $page->id ?: 0; + // Check if we should read this page. + if (!$this->shouldReadPageData($page, $field, $force)) return; + + foreach ($page->template->fields as $page_field) { + + // Check if we should read this field. + if (!$this->shouldReadFieldData($page, $field, $page_field, $force)) continue; + + // Get data from the Page object. + $data = $page->get($page_field->name); + + // Continue only if either the page in question exists (i.e. old field was cleared) + // or page is new and field has value. + if ($page->id || !is_null($data) && $data != "") { + if (!isset($this->page_data[$page_id])) { + $this->page_data[$page_id] = []; + } + $data_array = [ + 'data' => $data, + ]; + if ($data instanceof Pagefiles) { + // Note: originally 'sort' value of each item was used instead of a custom + // counter, but that's not always available when working over the API. + $count = 0; + foreach ($data as $item) { + $data_item = [ + '_original_filename' => $item->filename, + 'filename' => hash_file('sha1', $item->filename) . "." . $item->basename, + 'description' => $item->description, + 'modified' => $item->modified, + 'created' => $item->created, + ]; + if ($page_field->useTags) { + $data_item['tags'] = $item->tags; + } + $data_array[$count.'.data'] = json_encode($data_item); + ++$count; + } + if (empty($data_array)) { + $data_array['0.data'] = null; + } + } else if ($data instanceof LanguagesPageFieldValue) { + foreach ($data->getChanges() as $key) { + if ($key == 'data') continue; + $data_array[$key] = $data->getLanguageValue(str_replace('data', '', $key)); + } + } + $this->page_data[$page_id][$page_field->id] = $data_array; + } + } + } + + /** + * Check if page data should be read + * + * @param Page $page + * @param Field|null $field + * @param bool $force + * @return bool + */ + protected function ___shouldReadPageData(Page $page, ?Field $field, bool $force) { + + if ($force) return true; + // Check if tracking values has been enabled for the template of current page, or (in the // case of Repeater pages) template of the containing page. $template = $page->template; if ($page instanceof RepeaterPage) { $template = $page->getForPage()->template; } - if (!$force && !$this->isEnabledTemplate($template)) return; - - if ($force || $page->isChanged()) { - foreach ($page->template->fields as $page_field) { - - // Check if we should skip this field. - if ($field && $page_field->id != $field->id) continue; - if (!$force && !$page->isChanged($page_field->name)) continue; - if (!$page->id && $page_field->type instanceof FieldtypeFile) continue; - if (!in_array($page_field->type, $this->compatible_fieldtypes)) continue; - if (!empty($this->enabled_fields) && !in_array($page_field->id, $this->enabled_fields)) continue; - - // Get data from the Page object. - $data = $page->get($page_field->name); - - // Continue only if either the page in question exists (i.e. old field was cleared) - // or page is new and field has value. - if ($page->id || !is_null($data) && $data != "") { - if (!isset($this->page_data[$page_id])) { - $this->page_data[$page_id] = []; - } - $data_array = [ - 'data' => $data, - ]; - if ($data instanceof Pagefiles) { - // Note: originally 'sort' value of each item was used instead of a custom - // counter, but that's not always available when working over the API. - $count = 0; - foreach ($data as $item) { - $data_item = [ - '_original_filename' => $item->filename, - 'filename' => hash_file('sha1', $item->filename) . "." . $item->basename, - 'description' => $item->description, - 'modified' => $item->modified, - 'created' => $item->created, - ]; - if ($page_field->useTags) { - $data_item['tags'] = $item->tags; - } - $data_array[$count.'.data'] = json_encode($data_item); - ++$count; - } - if (empty($data_array)) { - $data_array['0.data'] = null; - } - } else if ($data instanceof LanguagesPageFieldValue) { - foreach ($data->getChanges() as $key) { - if ($key == 'data') continue; - $data_array[$key] = $data->getLanguageValue(str_replace('data', '', $key)); - } - } - $this->page_data[$page_id][$page_field->id] = $data_array; - } - } - } + if (!$this->isEnabledTemplate($template)) return false; + + return $page->isChanged(); + } + + /** + * Check if field data should be read + * + * @param Page $page + * @param Field $page_field + * @param Field|null $field + * @param bool $force + * @return bool + */ + protected function ___shouldReadFieldData(Page $page, ?Field $field, Field $page_field, bool $force) { + if ($field && $page_field->id != $field->id) return false; + if (!$force && !$page->isChanged($page_field->name)) return false; + if (!$page->id && $page_field->type instanceof FieldtypeFile) return false; + if (!in_array($page_field->type, $this->compatible_fieldtypes)) return false; + if (!empty($this->enabled_fields) && !in_array($page_field->id, $this->enabled_fields)) return false; + return true; } /** From c7933d985a3e2b8ebad836d73d3edf37da2ef74b Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Thu, 3 Nov 2022 14:49:04 +0200 Subject: [PATCH 15/17] Add icon for history tab --- lib/MarkupHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/MarkupHelper.php b/lib/MarkupHelper.php index 3ac7139..6eb36cb 100644 --- a/lib/MarkupHelper.php +++ b/lib/MarkupHelper.php @@ -107,6 +107,7 @@ public function getHistoryTab(array $data, Page $page): InputfieldWrapper { $field->value = $table; } $field->label = i18n::getText('History'); + $field->icon = 'clock-o'; $tab->append($field); // Add filters. From 43b4ac5d3070d76e3be8ef0ede9771640f54634d Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Wed, 23 Nov 2022 22:23:08 +0200 Subject: [PATCH 16/17] Fix issue with extraneous outline in page editor --- README.md | 9 +++++---- res/css/VersionControl.css | 2 ++ res/css/VersionControl.min.css | 2 +- res/css/VersionControl.min.css.map | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 99e9d4e..329aac7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Version Control module for ProcessWire ====================================== Version Control module For ProcessWire CMS/CMF. -Copyright (c) 2013-2020 Teppo Koivula +Copyright (c) 2013-2022 Teppo Koivula This module uses hooks provided by ProcessWire to catch page edits and store revision data in a series of custom database tables, so that it can be later retrieved, reviewed, and restored. @@ -119,8 +119,9 @@ interested in submitting a pull request or otherwise participating in the develo ### Resources Resources (JS and CSS) required by this module are in the res directory. For each resource there's -also a ".min" version, which contains minified version of the file. There's no build process, but -minified files can be created via command-line using cleancss and uglifyjs: +a ".min" version, containing a minified version. There's no real build process, but minified files +can be created via command-line by using cleancss (https://github.com/clean-css/clean-css-cli) and +uglifyjs (https://github.com/mishoo/UglifyJS): ``` # CSS @@ -159,4 +160,4 @@ You should have received a copy of the GNU General Public License along with thi write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -(See included LICENSE file for full license text.) \ No newline at end of file +(See included LICENSE file for full license text.) diff --git a/res/css/VersionControl.css b/res/css/VersionControl.css index 3ea26bc..1e86757 100644 --- a/res/css/VersionControl.css +++ b/res/css/VersionControl.css @@ -5,9 +5,11 @@ textarea.version_control_filedata { display: none; } +/* note: disabled due to causing an extraneous outline when tabs are followed by less than full width inputfield(s) .InputfieldColumnWidth { position: relative; } +*/ .version-control--visually-hidden, .field-revisions--scrollable .version-control--visually-hidden-if-scrollable { diff --git a/res/css/VersionControl.min.css b/res/css/VersionControl.min.css index fba9bd3..ca78d34 100644 --- a/res/css/VersionControl.min.css +++ b/res/css/VersionControl.min.css @@ -1,2 +1,2 @@ -#version-control-data,textarea.version_control_filedata{display:none}.InputfieldColumnWidth{position:relative}.field-revisions--scrollable .version-control--visually-hidden-if-scrollable,.version-control--visually-hidden{border:0;clip:rect(1px,1px,1px,1px);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.version-control-overlay{top:0;left:0;z-index:90;width:100%;height:100%;position:absolute}.version-control-overlay-parent{position:relative}.InputfieldContent.version-control-overlay-parent>*{transition:.5s ease}.InputfieldContent.version-control-overlay-parent--hover>*{opacity:.5}.field-revisions{background:#1c2836;background:linear-gradient(to right,#1c2836 0,#293a4f 100%);outline:0;overflow:visible!important;padding:1em 0 .25em 0!important;position:relative}.field-revisions::after{content:'';width:0;height:0;bottom:0;left:1.25em;position:absolute;margin-bottom:-8px;border-top:8px solid #1c2836;border-left:8px solid transparent;border-right:8px solid transparent}.field-revisions--animatable{display:none}.field-revisions:not(.field-revisions--animatable){height:1px;opacity:0;overflow:hidden!important;padding-bottom:0!important;padding-top:0!important}.field-revisions--scrollable{box-shadow:inset -1em 0 1em rgba(0,0,0,.25);transition:box-shadow .25s ease}.field-revisions--scrollable-end{box-shadow:none}.field-revisions--sliding{overflow:hidden!important}.field-revisions>div{overflow:auto!important;padding:0 1em}.field-revisions table{float:left;margin:0 1em 1em 0;width:100%;line-height:1em;border-collapse:collapse;border-right:1em solid rgba(41,58,79,.01)}.field-revisions tbody tr{position:relative;border-top:1px solid rgba(255,255,255,.25)!important}.field-revisions td,.field-revisions th{padding:.75em 1em;white-space:nowrap}.field-revisions td:first-child,.field-revisions th:first-child{padding:.75em 0 .75em .5em}.field-revisions td:not(:first-child):not(:last-child){border-right:1px solid rgba(255,255,255,.25)}.field-revisions td:last-child{width:100%}.field-revisions,.field-revisions td,.field-revisions td>a,.field-revisions th{color:#fff!important}.InputfieldStateRequired>.field-revisions+.InputfieldHeader:after{content:' *';color:red}body.template-admin .field-revisions .ui-state-active{border:0;background:0 0}.field-revisions-toggle{background:0 0;border:0;cursor:pointer;float:right;font-size:1em;line-height:1.5;padding-right:1em;position:relative}.field-revisions-toggle--active,.field-revisions__button--active{opacity:.5}.field-revisions-toggle[disabled] *{cursor:help;opacity:.25}.field-revision__button{background:0 0;border:0;color:#fff;cursor:pointer;font-size:1em;line-height:2;padding:0}.field-revision__button:focus,.field-revision__button:hover{opacity:.75}.field-revision__button:not(:last-child){margin-right:1em}.field-revisions--scrollable .field-revision__button:not(:last-child){margin-right:.5rem}.field-revisions td:last-child{padding:0 0 0 1em}.field-revisions .ui-state-active .field-revision__button{display:none}.field-revisions .field-revision__current-icon::before{content:'\f0c8';font-family:FontAwesome;cursor:pointer}.field-revisions .ui-state-active .field-revision__current-icon::before{content:'\f14a';cursor:default}.field-revision__button--diff::before{content:'\f074';padding-right:.5em;font-family:FontAwesome}.field-revision__button--restore::before{content:'\f0e2';padding-right:.5em;font-family:FontAwesome}.compare-revisions>a.diff-switch::before{content:'\f0db';padding-right:.5em;font-family:FontAwesome}.compare-revisions>a.diff-switch.diff-switch-list::before{content:'\f039';padding-right:.5em;font-family:FontAwesome}.field-revisions a:hover{text-shadow:none}.field-revision-loading{top:0;left:0;width:100%;height:100%;z-index:98;display:block;position:absolute;background-repeat:no-repeat;background-position:center center;background-image:url('../img/loading.gif')}.compare-revisions{z-index:9;color:#333;padding:1em;background:#fff;border-radius:2px;position:absolute;left:1.25%;width:97.5%;margin-top:1em;white-space:normal;border:1px solid #ccc;box-shadow:1px 2px 2px rgba(0,0,0,.1)}.compare-revisions-close{color:#333;display:inline-block;float:right;margin:0 0 .25rem .5rem!important}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr a{opacity:.5}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr a,.InputfieldWrapper#VersionControlHistory .AdminDataTable tr span{display:inline-block;margin:0 .25em;width:14px}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr:hover a{opacity:1}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr td:last-child{text-align:center;width:6em}.InputfieldWrapper#VersionControlHistory ul.MarkupPagerNavCustom li a,.InputfieldWrapper#VersionControlHistory ul.MarkupPagerNavCustom li.MarkupPagerNavSeparator{margin-right:3px}.version-control--preview{overflow:hidden}#preview{top:0;width:90%;height:100%;right:-80%;display:none;position:fixed;z-index:999999;background-color:#fff;background-repeat:no-repeat;background-position:center center;background-image:url('../img/loading.gif');box-shadow:-4px 0 3px rgba(0,0,0,.25)}#preview.loaded{background-image:none}#preview iframe{width:100%;height:100%;border:0}#preview-overlay{top:0;left:0;width:100%;height:100%;display:none;opacity:.25;position:fixed;background:#000}#diff{display:inline}#diff .page-diff,#diff .page-diff li,#diff .page-diff li *,#diff-table,#diff-table td *{box-shadow:none;display:block;padding:0;margin:0!important;border:0}#diff-table{border-collapse:collapse;display:table;width:100%}.diff-switch{margin-bottom:.75em!important;display:inline-block}#diff .page-diff li *{margin-bottom:1px!important;padding:.25em}#diff-table td{color:#333!important;box-sizing:border-box;border:1px solid #ddd;vertical-align:top;padding:0;width:50%}#diff-table td *{padding:.275em .25em}.revision{display:none!important}#diff-table td.diff-col-ins,.field-revisions ins{background-color:#3db997!important;text-decoration:none;line-height:1.4rem;color:#fff}#diff-table td.diff-col-del,.field-revisions del{background-color:#e93661!important;line-height:1.4rem;color:#fff}#diff-table del{text-decoration:none}#diff-table ins::before{content:'+\00a0'}#diff-table del::before{content:'-\00a0'}#diff-table span::before{content:'\00a0\00a0'} +#version-control-data,textarea.version_control_filedata{display:none}.field-revisions--scrollable .version-control--visually-hidden-if-scrollable,.version-control--visually-hidden{border:0;clip:rect(1px,1px,1px,1px);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.version-control-overlay{top:0;left:0;z-index:90;width:100%;height:100%;position:absolute}.version-control-overlay-parent{position:relative}.InputfieldContent.version-control-overlay-parent>*{transition:.5s ease}.InputfieldContent.version-control-overlay-parent--hover>*{opacity:.5}.field-revisions{background:#1c2836;background:linear-gradient(to right,#1c2836 0,#293a4f 100%);outline:0;overflow:visible!important;padding:1em 0 .25em 0!important;position:relative}.field-revisions::after{content:'';width:0;height:0;bottom:0;left:1.25em;position:absolute;margin-bottom:-8px;border-top:8px solid #1c2836;border-left:8px solid transparent;border-right:8px solid transparent}.field-revisions--animatable{display:none}.field-revisions:not(.field-revisions--animatable){opacity:0;overflow:hidden!important;padding-bottom:0!important;padding-top:0!important}.field-revisions--scrollable{box-shadow:inset -1em 0 1em rgba(0,0,0,.25);transition:box-shadow .25s ease}.field-revisions--scrollable-end{box-shadow:none}.field-revisions--sliding{overflow:hidden!important}.field-revisions>div{overflow:auto!important;padding:0 1em}.field-revisions table{float:left;margin:0 1em 1em 0;width:100%;line-height:1em;border-collapse:collapse;border-right:1em solid rgba(41,58,79,.01)}.field-revisions tbody tr{position:relative;border-top:1px solid rgba(255,255,255,.25)!important}.field-revisions td,.field-revisions th{padding:.75em 1em;white-space:nowrap}.field-revisions td:first-child,.field-revisions th:first-child{padding:.75em 0 .75em .5em}.field-revisions td:not(:first-child):not(:last-child){border-right:1px solid rgba(255,255,255,.25)}.field-revisions td:last-child{width:100%}.field-revisions,.field-revisions td,.field-revisions td>a,.field-revisions th{color:#fff!important}.InputfieldStateRequired>.field-revisions+.InputfieldHeader:after{content:' *';color:red}body.template-admin .field-revisions .ui-state-active{border:0;background:0 0}.field-revisions-toggle{background:0 0;border:0;cursor:pointer;float:right;font-size:1em;line-height:1.5;padding-right:1em;position:relative}.field-revisions-toggle--active,.field-revisions__button--active{opacity:.5}.field-revisions-toggle[disabled] *{cursor:help;opacity:.25}.field-revision__button{background:0 0;border:0;color:#fff;cursor:pointer;font-size:1em;line-height:2;padding:0}.field-revision__button:focus,.field-revision__button:hover{opacity:.75}.field-revision__button:not(:last-child){margin-right:1em}.field-revisions--scrollable .field-revision__button:not(:last-child){margin-right:.5rem}.field-revisions td:last-child{padding:0 0 0 1em}.field-revisions .ui-state-active .field-revision__button{display:none}.field-revisions .field-revision__current-icon::before{content:'\f0c8';font-family:FontAwesome;cursor:pointer}.field-revisions .ui-state-active .field-revision__current-icon::before{content:'\f14a';cursor:default}.field-revision__button--diff::before{content:'\f074';padding-right:.5em;font-family:FontAwesome}.field-revision__button--restore::before{content:'\f0e2';padding-right:.5em;font-family:FontAwesome}.compare-revisions>a.diff-switch::before{content:'\f0db';padding-right:.5em;font-family:FontAwesome}.compare-revisions>a.diff-switch.diff-switch-list::before{content:'\f039';padding-right:.5em;font-family:FontAwesome}.field-revisions a:hover{text-shadow:none}.field-revision-loading{top:0;left:0;width:100%;height:100%;z-index:98;display:block;position:absolute;background-repeat:no-repeat;background-position:center center;background-image:url('../img/loading.gif')}.compare-revisions{z-index:9;color:#333;padding:1em;background:#fff;border-radius:2px;position:absolute;left:1.25%;width:97.5%;margin-top:1em;white-space:normal;border:1px solid #ccc;box-shadow:1px 2px 2px rgba(0,0,0,.1)}.compare-revisions-close{color:#333;display:inline-block;float:right;margin:0 0 .25rem .5rem!important}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr a{opacity:.5}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr a,.InputfieldWrapper#VersionControlHistory .AdminDataTable tr span{display:inline-block;margin:0 .25em;width:14px}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr:hover a{opacity:1}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr td:last-child{text-align:center;width:6em}.InputfieldWrapper#VersionControlHistory ul.MarkupPagerNavCustom li a,.InputfieldWrapper#VersionControlHistory ul.MarkupPagerNavCustom li.MarkupPagerNavSeparator{margin-right:3px}.version-control--preview{overflow:hidden}#preview{top:0;width:90%;height:100%;right:-80%;display:none;position:fixed;z-index:999999;background-color:#fff;background-repeat:no-repeat;background-position:center center;background-image:url('../img/loading.gif');box-shadow:-4px 0 3px rgba(0,0,0,.25)}#preview.loaded{background-image:none}#preview iframe{width:100%;height:100%;border:0}#preview-overlay{top:0;left:0;width:100%;height:100%;display:none;opacity:.25;position:fixed;background:#000}#diff{display:inline}#diff .page-diff,#diff .page-diff li,#diff .page-diff li *,#diff-table,#diff-table td *{box-shadow:none;display:block;padding:0;margin:0!important;border:0}#diff-table{border-collapse:collapse;display:table;width:100%}.diff-switch{margin-bottom:.75em!important;display:inline-block}#diff .page-diff li *{margin-bottom:1px!important;padding:.25em}#diff-table td{color:#333!important;box-sizing:border-box;border:1px solid #ddd;vertical-align:top;padding:0;width:50%}#diff-table td *{padding:.275em .25em}.revision{display:none!important}#diff-table td.diff-col-ins,.field-revisions ins{background-color:#3db997!important;text-decoration:none;line-height:1.4rem;color:#fff}#diff-table td.diff-col-del,.field-revisions del{background-color:#e93661!important;line-height:1.4rem;color:#fff}#diff-table del{text-decoration:none}#diff-table ins::before{content:'+\00a0'}#diff-table del::before{content:'-\00a0'}#diff-table span::before{content:'\00a0\00a0'} /*# sourceMappingURL=VersionControl.min.css.map */ \ No newline at end of file diff --git a/res/css/VersionControl.min.css.map b/res/css/VersionControl.min.css.map index 13f1cda..fd20857 100644 --- a/res/css/VersionControl.min.css.map +++ b/res/css/VersionControl.min.css.map @@ -1 +1 @@ -{"version":3,"sources":["res/css/VersionControl.css"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"sources":["res/css/VersionControl.css"],"names":[],"mappings":"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"} \ No newline at end of file From a68ee34ef90922785b95d46c0641322c0e55b676 Mon Sep 17 00:00:00 2001 From: Teppo Koivula Date: Wed, 23 Nov 2022 22:34:00 +0200 Subject: [PATCH 17/17] Another layout fix for page editor --- res/css/VersionControl.css | 2 +- res/css/VersionControl.min.css | 2 +- res/css/VersionControl.min.css.map | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/res/css/VersionControl.css b/res/css/VersionControl.css index 1e86757..062d557 100644 --- a/res/css/VersionControl.css +++ b/res/css/VersionControl.css @@ -76,8 +76,8 @@ textarea.version_control_filedata { display: none; } +/* Hide visually, but keep in the flow for the scrollable calculation */ .field-revisions:not(.field-revisions--animatable) { - /* Hide visually, but keep in the flow for the scrollable calculation */ height: 1px; opacity: 0; overflow: hidden !important; diff --git a/res/css/VersionControl.min.css b/res/css/VersionControl.min.css index ca78d34..c5a13c5 100644 --- a/res/css/VersionControl.min.css +++ b/res/css/VersionControl.min.css @@ -1,2 +1,2 @@ -#version-control-data,textarea.version_control_filedata{display:none}.field-revisions--scrollable .version-control--visually-hidden-if-scrollable,.version-control--visually-hidden{border:0;clip:rect(1px,1px,1px,1px);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.version-control-overlay{top:0;left:0;z-index:90;width:100%;height:100%;position:absolute}.version-control-overlay-parent{position:relative}.InputfieldContent.version-control-overlay-parent>*{transition:.5s ease}.InputfieldContent.version-control-overlay-parent--hover>*{opacity:.5}.field-revisions{background:#1c2836;background:linear-gradient(to right,#1c2836 0,#293a4f 100%);outline:0;overflow:visible!important;padding:1em 0 .25em 0!important;position:relative}.field-revisions::after{content:'';width:0;height:0;bottom:0;left:1.25em;position:absolute;margin-bottom:-8px;border-top:8px solid #1c2836;border-left:8px solid transparent;border-right:8px solid transparent}.field-revisions--animatable{display:none}.field-revisions:not(.field-revisions--animatable){opacity:0;overflow:hidden!important;padding-bottom:0!important;padding-top:0!important}.field-revisions--scrollable{box-shadow:inset -1em 0 1em rgba(0,0,0,.25);transition:box-shadow .25s ease}.field-revisions--scrollable-end{box-shadow:none}.field-revisions--sliding{overflow:hidden!important}.field-revisions>div{overflow:auto!important;padding:0 1em}.field-revisions table{float:left;margin:0 1em 1em 0;width:100%;line-height:1em;border-collapse:collapse;border-right:1em solid rgba(41,58,79,.01)}.field-revisions tbody tr{position:relative;border-top:1px solid rgba(255,255,255,.25)!important}.field-revisions td,.field-revisions th{padding:.75em 1em;white-space:nowrap}.field-revisions td:first-child,.field-revisions th:first-child{padding:.75em 0 .75em .5em}.field-revisions td:not(:first-child):not(:last-child){border-right:1px solid rgba(255,255,255,.25)}.field-revisions td:last-child{width:100%}.field-revisions,.field-revisions td,.field-revisions td>a,.field-revisions th{color:#fff!important}.InputfieldStateRequired>.field-revisions+.InputfieldHeader:after{content:' *';color:red}body.template-admin .field-revisions .ui-state-active{border:0;background:0 0}.field-revisions-toggle{background:0 0;border:0;cursor:pointer;float:right;font-size:1em;line-height:1.5;padding-right:1em;position:relative}.field-revisions-toggle--active,.field-revisions__button--active{opacity:.5}.field-revisions-toggle[disabled] *{cursor:help;opacity:.25}.field-revision__button{background:0 0;border:0;color:#fff;cursor:pointer;font-size:1em;line-height:2;padding:0}.field-revision__button:focus,.field-revision__button:hover{opacity:.75}.field-revision__button:not(:last-child){margin-right:1em}.field-revisions--scrollable .field-revision__button:not(:last-child){margin-right:.5rem}.field-revisions td:last-child{padding:0 0 0 1em}.field-revisions .ui-state-active .field-revision__button{display:none}.field-revisions .field-revision__current-icon::before{content:'\f0c8';font-family:FontAwesome;cursor:pointer}.field-revisions .ui-state-active .field-revision__current-icon::before{content:'\f14a';cursor:default}.field-revision__button--diff::before{content:'\f074';padding-right:.5em;font-family:FontAwesome}.field-revision__button--restore::before{content:'\f0e2';padding-right:.5em;font-family:FontAwesome}.compare-revisions>a.diff-switch::before{content:'\f0db';padding-right:.5em;font-family:FontAwesome}.compare-revisions>a.diff-switch.diff-switch-list::before{content:'\f039';padding-right:.5em;font-family:FontAwesome}.field-revisions a:hover{text-shadow:none}.field-revision-loading{top:0;left:0;width:100%;height:100%;z-index:98;display:block;position:absolute;background-repeat:no-repeat;background-position:center center;background-image:url('../img/loading.gif')}.compare-revisions{z-index:9;color:#333;padding:1em;background:#fff;border-radius:2px;position:absolute;left:1.25%;width:97.5%;margin-top:1em;white-space:normal;border:1px solid #ccc;box-shadow:1px 2px 2px rgba(0,0,0,.1)}.compare-revisions-close{color:#333;display:inline-block;float:right;margin:0 0 .25rem .5rem!important}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr a{opacity:.5}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr a,.InputfieldWrapper#VersionControlHistory .AdminDataTable tr span{display:inline-block;margin:0 .25em;width:14px}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr:hover a{opacity:1}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr td:last-child{text-align:center;width:6em}.InputfieldWrapper#VersionControlHistory ul.MarkupPagerNavCustom li a,.InputfieldWrapper#VersionControlHistory ul.MarkupPagerNavCustom li.MarkupPagerNavSeparator{margin-right:3px}.version-control--preview{overflow:hidden}#preview{top:0;width:90%;height:100%;right:-80%;display:none;position:fixed;z-index:999999;background-color:#fff;background-repeat:no-repeat;background-position:center center;background-image:url('../img/loading.gif');box-shadow:-4px 0 3px rgba(0,0,0,.25)}#preview.loaded{background-image:none}#preview iframe{width:100%;height:100%;border:0}#preview-overlay{top:0;left:0;width:100%;height:100%;display:none;opacity:.25;position:fixed;background:#000}#diff{display:inline}#diff .page-diff,#diff .page-diff li,#diff .page-diff li *,#diff-table,#diff-table td *{box-shadow:none;display:block;padding:0;margin:0!important;border:0}#diff-table{border-collapse:collapse;display:table;width:100%}.diff-switch{margin-bottom:.75em!important;display:inline-block}#diff .page-diff li *{margin-bottom:1px!important;padding:.25em}#diff-table td{color:#333!important;box-sizing:border-box;border:1px solid #ddd;vertical-align:top;padding:0;width:50%}#diff-table td *{padding:.275em .25em}.revision{display:none!important}#diff-table td.diff-col-ins,.field-revisions ins{background-color:#3db997!important;text-decoration:none;line-height:1.4rem;color:#fff}#diff-table td.diff-col-del,.field-revisions del{background-color:#e93661!important;line-height:1.4rem;color:#fff}#diff-table del{text-decoration:none}#diff-table ins::before{content:'+\00a0'}#diff-table del::before{content:'-\00a0'}#diff-table span::before{content:'\00a0\00a0'} +#version-control-data,textarea.version_control_filedata{display:none}.field-revisions--scrollable .version-control--visually-hidden-if-scrollable,.version-control--visually-hidden{border:0;clip:rect(1px,1px,1px,1px);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.version-control-overlay{top:0;left:0;z-index:90;width:100%;height:100%;position:absolute}.version-control-overlay-parent{position:relative}.InputfieldContent.version-control-overlay-parent>*{transition:.5s ease}.InputfieldContent.version-control-overlay-parent--hover>*{opacity:.5}.field-revisions{background:#1c2836;background:linear-gradient(to right,#1c2836 0,#293a4f 100%);outline:0;overflow:visible!important;padding:1em 0 .25em 0!important;position:relative}.field-revisions::after{content:'';width:0;height:0;bottom:0;left:1.25em;position:absolute;margin-bottom:-8px;border-top:8px solid #1c2836;border-left:8px solid transparent;border-right:8px solid transparent}.field-revisions--animatable{display:none}.field-revisions:not(.field-revisions--animatable){height:1px;opacity:0;overflow:hidden!important;padding-bottom:0!important;padding-top:0!important}.field-revisions--scrollable{box-shadow:inset -1em 0 1em rgba(0,0,0,.25);transition:box-shadow .25s ease}.field-revisions--scrollable-end{box-shadow:none}.field-revisions--sliding{overflow:hidden!important}.field-revisions>div{overflow:auto!important;padding:0 1em}.field-revisions table{float:left;margin:0 1em 1em 0;width:100%;line-height:1em;border-collapse:collapse;border-right:1em solid rgba(41,58,79,.01)}.field-revisions tbody tr{position:relative;border-top:1px solid rgba(255,255,255,.25)!important}.field-revisions td,.field-revisions th{padding:.75em 1em;white-space:nowrap}.field-revisions td:first-child,.field-revisions th:first-child{padding:.75em 0 .75em .5em}.field-revisions td:not(:first-child):not(:last-child){border-right:1px solid rgba(255,255,255,.25)}.field-revisions td:last-child{width:100%}.field-revisions,.field-revisions td,.field-revisions td>a,.field-revisions th{color:#fff!important}.InputfieldStateRequired>.field-revisions+.InputfieldHeader:after{content:' *';color:red}body.template-admin .field-revisions .ui-state-active{border:0;background:0 0}.field-revisions-toggle{background:0 0;border:0;cursor:pointer;float:right;font-size:1em;line-height:1.5;padding-right:1em;position:relative}.field-revisions-toggle--active,.field-revisions__button--active{opacity:.5}.field-revisions-toggle[disabled] *{cursor:help;opacity:.25}.field-revision__button{background:0 0;border:0;color:#fff;cursor:pointer;font-size:1em;line-height:2;padding:0}.field-revision__button:focus,.field-revision__button:hover{opacity:.75}.field-revision__button:not(:last-child){margin-right:1em}.field-revisions--scrollable .field-revision__button:not(:last-child){margin-right:.5rem}.field-revisions td:last-child{padding:0 0 0 1em}.field-revisions .ui-state-active .field-revision__button{display:none}.field-revisions .field-revision__current-icon::before{content:'\f0c8';font-family:FontAwesome;cursor:pointer}.field-revisions .ui-state-active .field-revision__current-icon::before{content:'\f14a';cursor:default}.field-revision__button--diff::before{content:'\f074';padding-right:.5em;font-family:FontAwesome}.field-revision__button--restore::before{content:'\f0e2';padding-right:.5em;font-family:FontAwesome}.compare-revisions>a.diff-switch::before{content:'\f0db';padding-right:.5em;font-family:FontAwesome}.compare-revisions>a.diff-switch.diff-switch-list::before{content:'\f039';padding-right:.5em;font-family:FontAwesome}.field-revisions a:hover{text-shadow:none}.field-revision-loading{top:0;left:0;width:100%;height:100%;z-index:98;display:block;position:absolute;background-repeat:no-repeat;background-position:center center;background-image:url('../img/loading.gif')}.compare-revisions{z-index:9;color:#333;padding:1em;background:#fff;border-radius:2px;position:absolute;left:1.25%;width:97.5%;margin-top:1em;white-space:normal;border:1px solid #ccc;box-shadow:1px 2px 2px rgba(0,0,0,.1)}.compare-revisions-close{color:#333;display:inline-block;float:right;margin:0 0 .25rem .5rem!important}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr a{opacity:.5}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr a,.InputfieldWrapper#VersionControlHistory .AdminDataTable tr span{display:inline-block;margin:0 .25em;width:14px}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr:hover a{opacity:1}.InputfieldWrapper#VersionControlHistory .AdminDataTable tr td:last-child{text-align:center;width:6em}.InputfieldWrapper#VersionControlHistory ul.MarkupPagerNavCustom li a,.InputfieldWrapper#VersionControlHistory ul.MarkupPagerNavCustom li.MarkupPagerNavSeparator{margin-right:3px}.version-control--preview{overflow:hidden}#preview{top:0;width:90%;height:100%;right:-80%;display:none;position:fixed;z-index:999999;background-color:#fff;background-repeat:no-repeat;background-position:center center;background-image:url('../img/loading.gif');box-shadow:-4px 0 3px rgba(0,0,0,.25)}#preview.loaded{background-image:none}#preview iframe{width:100%;height:100%;border:0}#preview-overlay{top:0;left:0;width:100%;height:100%;display:none;opacity:.25;position:fixed;background:#000}#diff{display:inline}#diff .page-diff,#diff .page-diff li,#diff .page-diff li *,#diff-table,#diff-table td *{box-shadow:none;display:block;padding:0;margin:0!important;border:0}#diff-table{border-collapse:collapse;display:table;width:100%}.diff-switch{margin-bottom:.75em!important;display:inline-block}#diff .page-diff li *{margin-bottom:1px!important;padding:.25em}#diff-table td{color:#333!important;box-sizing:border-box;border:1px solid #ddd;vertical-align:top;padding:0;width:50%}#diff-table td *{padding:.275em .25em}.revision{display:none!important}#diff-table td.diff-col-ins,.field-revisions ins{background-color:#3db997!important;text-decoration:none;line-height:1.4rem;color:#fff}#diff-table td.diff-col-del,.field-revisions del{background-color:#e93661!important;line-height:1.4rem;color:#fff}#diff-table del{text-decoration:none}#diff-table ins::before{content:'+\00a0'}#diff-table del::before{content:'-\00a0'}#diff-table span::before{content:'\00a0\00a0'} /*# sourceMappingURL=VersionControl.min.css.map */ \ No newline at end of file diff --git a/res/css/VersionControl.min.css.map b/res/css/VersionControl.min.css.map index fd20857..ff4b9b5 100644 --- a/res/css/VersionControl.min.css.map +++ b/res/css/VersionControl.min.css.map @@ -1 +1 @@ -{"version":3,"sources":["res/css/VersionControl.css"],"names":[],"mappings":"AAEA,sBACA,kCACI,QAAS,KAUb,6EADA,kCAEI,OAAQ,EACR,KAAM,sBACN,UAAW,WACX,OAAQ,IACR,OAAQ,KACR,SAAU,OACV,QAAS,EACT,SAAU,SACV,MAAO,IACP,UAAW,iBAKf,yBACI,IAAK,EACL,KAAM,EACN,QAAS,GACT,MAAO,KACP,OAAQ,KACR,SAAU,SAGd,gCACI,SAAU,SAGd,oDACI,WAAY,IAAI,KAGpB,2DACI,QAAS,GAKb,iBACI,WAAY,QACZ,WAAY,iDACZ,QAAS,EACT,SAAU,kBACV,QAAS,IAAI,EAAE,MAAM,YACrB,SAAU,SAGd,wBACI,QAAS,GACT,MAAO,EACP,OAAQ,EACR,OAAQ,EACR,KAAM,OACN,SAAU,SACV,cAAe,KACf,WAAY,IAAI,MAAM,QACtB,YAAa,IAAI,MAAM,YACvB,aAAc,IAAI,MAAM,YAG5B,6BACI,QAAS,KAGb,mDAGI,QAAS,EACT,SAAU,iBACV,eAAgB,YAChB,YAAa,YAGjB,6BACI,WAAY,MAAM,KAAK,EAAE,IAAI,gBAC7B,WAAY,WAAW,KAAK,KAGhC,iCACI,WAAY,KAGhB,0BACI,SAAU,iBAGd,qBACI,SAAU,eACV,QAAS,EAAE,IAGf,uBACI,MAAO,KACP,OAAQ,EAAE,IAAI,IAAI,EAClB,MAAO,KACP,YAAa,IACb,gBAAiB,SACjB,aAAc,IAAI,MAAM,mBAG5B,0BACI,SAAU,SACV,WAAY,IAAI,MAAM,gCAI1B,oBADA,oBAEI,QAAS,MAAM,IACf,YAAa,OAIjB,gCADA,gCAEI,QAAS,MAAM,EAAE,MAAM,KAG3B,uDACI,aAAc,IAAI,MAAM,sBAG5B,+BACI,MAAO,KAGX,iBACA,oBAEA,sBADA,oBAEI,MAAO,eAGX,kEACI,QAAS,KACT,MAAO,IAGX,sDACI,OAAQ,EACR,WAAY,IAKhB,wBACI,WAAY,IACZ,OAAQ,EACR,OAAQ,QACR,MAAO,MACP,UAAW,IACX,YAAa,IACb,cAAe,IACf,SAAU,SAId,gCADA,iCAEI,QAAS,GAGb,oCACI,OAAQ,KACR,QAAS,IAGb,wBACI,WAAY,IACZ,OAAQ,EACR,MAAO,KACP,OAAQ,QACR,UAAW,IACX,YAAa,EACb,QAAS,EAIb,8BADA,8BAEI,QAAS,IAGb,yCACI,aAAc,IAGlB,sEACI,aAAc,MAGlB,+BACI,QAAS,EAAE,EAAE,EAAE,IAGnB,0DACI,QAAS,KAGb,uDACI,QAAS,QACT,YAAa,YACb,OAAQ,QAGZ,wEACI,QAAS,QACT,OAAQ,QAGZ,sCACI,QAAS,QACT,cAAe,KACf,YAAa,YAGjB,yCACI,QAAS,QACT,cAAe,KACf,YAAa,YAGjB,yCACI,QAAS,QACT,cAAe,KACf,YAAa,YAGjB,0DACI,QAAS,QACT,cAAe,KACf,YAAa,YAGjB,yBACI,YAAa,KAGjB,wBACI,IAAK,EACL,KAAM,EACN,MAAO,KACP,OAAQ,KACR,QAAS,GACT,QAAS,MACT,SAAU,SACV,kBAAmB,UACnB,oBAAqB,OAAO,OAC5B,iBAAkB,0BAGtB,mBACI,QAAS,EACT,MAAO,KACP,QAAS,IACT,WAAY,KACZ,cAAe,IACf,SAAU,SACV,KAAM,MACN,MAAO,MACP,WAAY,IACZ,YAAa,OACb,OAAQ,IAAI,MAAM,KAClB,WAAY,IAAI,IAAI,IAAI,eAG5B,yBACI,MAAO,KACP,QAAS,aACT,MAAO,MACP,OAAQ,EAAE,EAAE,OAAO,gBAKvB,8DACI,QAAS,GAGb,8DACA,iEACI,QAAS,aACT,OAAQ,EAAE,MACV,MAAO,KAGX,oEACI,QAAS,EAGb,0EACI,WAAY,OACZ,MAAO,IAIX,sEADA,4FAEI,aAAc,IAGlB,0BACI,SAAU,OAGd,SACI,IAAK,EACL,MAAO,IACP,OAAQ,KACR,MAAO,KACP,QAAS,KACT,SAAU,MACV,QAAS,OACT,iBAAkB,KAClB,kBAAmB,UACnB,oBAAqB,OAAO,OAC5B,iBAAkB,0BAClB,WAAY,KAAK,EAAE,IAAI,gBAG3B,gBACI,iBAAkB,KAGtB,gBACI,MAAO,KACP,OAAQ,KACR,OAAQ,EAGZ,iBACI,IAAK,EACL,KAAM,EACN,MAAO,KACP,OAAQ,KACR,QAAS,KACT,QAAS,IACT,SAAU,MACV,WAAY,KAGhB,MACI,QAAS,OAGb,iBACA,oBACA,sBACA,YACA,iBACI,WAAY,KACZ,QAAS,MACT,QAAS,EACT,OAAQ,YACR,OAAQ,EAGZ,YACI,gBAAiB,SACjB,QAAS,MACT,MAAO,KAGX,aACI,cAAe,gBACf,QAAS,aAGb,sBACI,cAAe,cACf,QAAS,MAGb,eACI,MAAO,eACP,WAAY,WACZ,OAAQ,IAAI,MAAM,KAClB,eAAgB,IAChB,QAAS,EACT,MAAO,IAGX,iBACI,QAAS,OAAO,MAGpB,UACI,QAAS,eAGb,4BACA,qBACI,iBAAkB,kBAClB,gBAAiB,KACjB,YAAa,OACb,MAAO,KAGX,4BACA,qBACI,iBAAkB,kBAClB,YAAa,OACb,MAAO,KAGX,gBACI,gBAAiB,KAGrB,wBACI,QAAS,SAGb,wBACI,QAAS,SAGb,yBACI,QAAS"} \ No newline at end of file +{"version":3,"sources":["res/css/VersionControl.css"],"names":[],"mappings":"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"} \ No newline at end of file