/
PageRenameOptions.module
364 lines (300 loc) · 17.8 KB
/
PageRenameOptions.module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
<?php
/**
* ProcessWire module for determining how pages are renamed when the title is changed
* by Adrian Jones
*
* Determine how pages are renamed when the title is changed
*
* Copyright (C) 2023 by Adrian Jones
* Licensed under GNU/GPL v2, see LICENSE.TXT
*
*/
class PageRenameOptions extends WireData implements Module, ConfigurableModule {
public static function getModuleInfo() {
return array(
'title' => 'Page Rename Options',
'author' => 'Adrian Jones',
'summary' => 'Determine how pages are renamed when the title is changed',
'href' => 'http://modules.processwire.com/modules/page-rename-options/',
'version' => '2.0.2',
'autoload' => "template=admin",
'icon' => 'pencil-square-o'
);
}
/**
* Data as used by the get/set functions
*
*/
protected $data = array();
/**
* Default configuration for module
*
*/
static public function getDefaultData() {
return array(
"renameMatchTitle" => 1,
"renameWithoutTitleChange" => null,
"renameUnpublishedOnly" => null,
"includeExclude" => 'exclude',
"specifiedTemplates" => array(),
"specifiedPages" => array(),
"initialDifferencesProtected" => null,
"exemptRolesInitialDifferencesProtected" => array(),
"preventManualChanges" => null,
"exemptRolesPreventManualChanges" => array(),
"liveChangesProtected" => null,
"exemptRolesLiveChangesProtected" => array()
);
}
/**
* Populate the default config data
*
*/
public function __construct() {
foreach(self::getDefaultData() as $key => $value) {
$this->$key = $value;
}
}
public function init() {
}
public function ready() {
// exit if not PageEdit, PageAdd, or PageClone
if($this->wire('page')->process != 'ProcessPageEdit' && $this->wire('page')->process != 'ProcessPageAdd' && $this->wire('page')->process != 'ProcessPageClone') {
return;
}
$this->wire()->addHookBefore("ProcessPageEdit::buildForm", $this, "addScripts");
$this->wire()->addHookBefore("ProcessPageAdd::buildForm", $this, "addScripts");
$this->wire()->addHookBefore("ProcessPageClone::buildForm", $this, "addScripts");
}
protected function addScripts($event) {
$p = $event->object->getPage();
if(!$this->checkPageTemplateRestrictions($p)) return;
$conf = $this->getModuleInfo();
$version = (int) $conf['version'];
// if settings tab is disabled and name is not in content tab,
// then we need to add name to content tab, but set it to be hidden
if($p->id && $p->template->noSettings && !$p->template->nameContentTab) {
$p->template->nameContentTab = 1;
$this->wire('config')->styles->add($this->wire('config')->urls->PageRenameOptions . "HideNameField.css?v={$version}");
}
if($this->data['preventManualChanges'] && !$this->wire('user')->roles->has("name=".implode("|", $this->data['exemptRolesPreventManualChanges']))) {
// ProcessPageEdit/Add/Clone check is to prevent disabling of name field when editing a user etc
if(($this->wire('process') == "ProcessPageEdit" || $this->wire('process') == "ProcessPageAdd" || $this->wire('process') == "ProcessPageClone")) {
$this->wire('config')->scripts->add($this->wire('config')->urls->PageRenameOptions . "PreventManualChanges.js?v={$version}");
}
}
if($this->data['initialDifferencesProtected'] && !$this->wire('user')->roles->has("name=".implode("|", $this->data['exemptRolesInitialDifferencesProtected']))) {
$this->wire('config')->scripts->add($this->wire('config')->urls->PageRenameOptions . "InitialDifferenceProtected.js?v={$version}");
}
if($this->data['renameWithoutTitleChange']) {
if(!$p->is(Page::statusUnpublished) && $this->data['renameUnpublishedOnly'] == 1) return;
// if editing existing page (already has template) and title field is locked
// then we need to define a JS var with the title of the page so the renaming doesn't set to blank
if($p->template) {
$fieldCollapsed = $p->template->fieldgroup->getField('title', true)->collapsed;
if($fieldCollapsed == Inputfield::collapsedNoLocked || $fieldCollapsed == Inputfield::collapsedYesLocked) {
$this->wire('config')->js("pageRenameOptionsPageTitle", $p->title);
}
}
$this->wire('config')->scripts->add($this->wire('config')->urls->PageRenameOptions . "RenameWithoutTitleChange.js?v={$version}");
}
if($this->data['renameMatchTitle']) {
if(!$p->is(Page::statusUnpublished) && $this->data['renameUnpublishedOnly'] == 1) return;
$this->wire('config')->scripts->add($this->wire('config')->urls->PageRenameOptions . "PageRenameOptions.js?v={$version}");
}
if($this->data['liveChangesProtected'] && !$this->wire('user')->roles->has("name=".implode("|", $this->data['exemptRolesLiveChangesProtected']))) {
$this->wire('config')->scripts->add($this->wire('config')->urls->PageRenameOptions . "LiveChangesProtected.js?v={$version}");
}
}
private function checkPageTemplateRestrictions($p) {
// escape if homepage
if($p->id == 1) return false;
// only run these checks if page has a template already. This also covers it having a valid ID (ie not 0)
// this prevents this working when adding a new page, but works fine for editing/renaming an existing page
if($p->template) {
if($this->data['includeExclude'] == 'include') {
// escape if page is not specified
if(count($this->data['specifiedPages']) !== 0 && !in_array($p->id, $this->data['specifiedPages'])) return false;
// escape if template is specified
if(count($this->data['specifiedTemplates']) !== 0 && !in_array($p->template->id, $this->data['specifiedTemplates'])) return false;
}
else {
// escape if page is specified
if(count($this->data['specifiedPages']) !== 0 && in_array($p->id, $this->data['specifiedPages'])) return false;
// escape if template is not specified
if(count($this->data['specifiedTemplates']) !== 0 && in_array($p->template->id, $this->data['specifiedTemplates'])) return false;
}
}
// $p->template check is because it is not available for ProcessPageAdd
if($p->template && $p->template->flags & Template::flagSystem) return false; // exclude system templates eg. users etc
return true;
}
/**
* Return an InputfieldsWrapper of Inputfields used to configure the class
*
* @param array $data Array of config values indexed by field name
* @return InputfieldsWrapper
*
*/
public function getModuleConfigInputfields(array $data) {
$data = array_merge(self::getDefaultData(), $data);
$wrapper = new InputfieldWrapper();
$f = $this->wire('modules')->get("InputfieldMarkup");
$f->attr('name', 'warning');
$f->label = __('WARNING');
$f->value = "<p>This module may break links to your site because changes to the page's name also change its URL!</p>";
if($this->wire('modules')->isInstalled("PagePathHistory")) {
$f->value .= "<p style='color:#009900'>Page Path History is installed, so you are protected from URL changes.</p>";
}
else {
$f->value .= "<p style='color:#FF6600'>You should install <a href='".$this->wire('config')->urls->admin."module/installConfirm?name=PagePathHistory'>Page Path History</a> (core module) to ensure any changed URLs are automatically redirected.</p>";
}
$f->value .= "<br /><p><strong>Introduction</strong></p><p>With the 'Automatically rename the name field to match the title' option checked, the default behavior is to have the page name always match changes to the page title, but allow the user to manually edit if they want. The other options allow you to modify this behavior in several different ways.</p>";
$wrapper->add($f);
$f = $this->wire('modules')->get("InputfieldCheckbox");
$f->attr('name', 'renameMatchTitle');
$f->label = __('Automatically rename the name field to match the title');
$f->description = __('If checked, any changes to the title will result in the name being changed to match.');
$f->columnWidth = 33;
$f->attr('checked', $data['renameMatchTitle'] ? 'checked' : '' );
$wrapper->add($f);
$f = $this->wire('modules')->get("InputfieldCheckbox");
$f->attr('name', 'renameWithoutTitleChange');
$f->label = __('Rename without title change');
$f->columnWidth = 34;
$f->description = __('If checked, names that don\'t match titles will be updated even if the title is not manually changed.');
$f->notes = __('If checked, this will disable the "Initial Differences Protected" setting.');
$f->attr('checked', $data['renameWithoutTitleChange'] ? 'checked' : '' );
$wrapper->add($f);
$f = $this->wire('modules')->get("InputfieldCheckbox");
$f->attr('name', 'renameUnpublishedOnly');
$f->label = __('Only rename unpublished pages');
$f->description = __('If checked, the name field will only be renamed to match the title when the page is unpublished.');
$f->showIf = "renameMatchTitle=1";
$f->columnWidth = 33;
$f->attr('checked', $data['renameUnpublishedOnly'] ? 'checked' : '' );
$wrapper->add($f);
$f = $this->wire('modules')->get('InputfieldRadios');
$f->attr('name+id', 'includeExclude');
$f->label = __('Exclude or include specified templates or pages');
$f->columnWidth = 33;
$f->description = __("This setting determines how the Specified Templates or Specified Pages are handled. If nothing is specified in either of those settings, rules will be applied to all templates / pages.");
$f->addOption('exclude', 'Exclude');
$f->addOption('include', 'Include');
if(isset($data['includeExclude'])) $f->value = $data['includeExclude'];
$wrapper->add($f);
$f = $this->wire('modules')->get('InputfieldAsmSelect');
$f->attr('name+id', 'specifiedTemplates');
$f->label = __('Specified templates');
$f->columnWidth = 34;
$f->description = __("Selected templates are excluded or included from the actions of this module.");
$f->notes = __("This restriction only affects editing/renaming existing pages, not when adding new ones.");
$f->setAsmSelectOption('sortable', false);
foreach($this->wire('templates') as $t) {
$f->addOption($t->id, $t->name);
}
if(isset($data['specifiedTemplates'])) $f->value = $data['specifiedTemplates'];
$wrapper->add($f);
$f = $this->wire('modules')->get('InputfieldPageListSelectMultiple');
$f->attr('name+id', 'specifiedPages');
$f->label = __('Specified pages');
$f->columnWidth = 33;
$f->description = __("Pages that are excluded or included from the actions of this module.");
$f->notes = __("This restriction only affects editing/renaming existing pages, not when adding new ones.");
if(isset($data['specifiedPages'])) $f->value = $data['specifiedPages'];
$wrapper->add($f);
$f = $this->wire('modules')->get("InputfieldCheckbox");
$f->attr('name', 'initialDifferencesProtected');
$f->label = __('Initial differences protected');
$f->showIf = "renameWithoutTitleChange!=1";
$f->columnWidth = 50;
$f->description = __('If checked, further changes to the name (to match a changing title) will not happen if the name was already different from the title (evaluated on page edit initial load).');
$f->attr('checked', $data['initialDifferencesProtected'] ? 'checked' : '' );
$wrapper->add($f);
$f = $this->wire('modules')->get('InputfieldAsmSelect');
$f->attr('name+id', 'exemptRolesInitialDifferencesProtected');
$f->label = __('Roles exempt from initial differences protected');
$f->description = __("The selected roles will not be subject to the Initial Differences Protected check.\nThe name will change automatically with the title (unless the 'Automatically rename the name field to match the title' is unchecked.), but they will be able to manually edit the page name as desired.");
$f->setAsmSelectOption('sortable', false);
$f->showIf = "initialDifferencesProtected=1, renameWithoutTitleChange!=1";
$f->columnWidth = 50;
foreach($this->wire('roles') as $r) {
$f->addOption($r->name);
}
if(isset($data['exemptRolesInitialDifferencesProtected'])) $f->value = $data['exemptRolesInitialDifferencesProtected'];
$wrapper->add($f);
$f = $this->wire('modules')->get("InputfieldCheckbox");
$f->attr('name', 'preventManualChanges');
$f->label = __('Prevent manual changes');
$f->columnWidth = 50;
$f->description = __('If checked, it won\'t be possible to manually edit the name.');
$f->notes = __('If checked, this will disable the "Live Changes Protected" setting since it won\'t be possible to make any manual changes.');
$f->attr('checked', $data['preventManualChanges'] ? 'checked' : '' );
$wrapper->add($f);
$f = $this->wire('modules')->get('InputfieldAsmSelect');
$f->attr('name+id', 'exemptRolesPreventManualChanges');
$f->label = __('Roles exempt from prevent manual changes');
$f->description = __("The selected roles will not be subject to the Prevent Manual Changes check.\nThe name will change automatically with the title (unless the 'Automatically rename the name field to match the title' is unchecked.), but they will be able to manually edit the page name as desired.");
$f->setAsmSelectOption('sortable', false);
$f->showIf = "preventManualChanges=1";
$f->columnWidth = 50;
foreach($this->wire('roles') as $r) {
$f->addOption($r->name);
}
if(isset($data['exemptRolesPreventManualChanges'])) $f->value = $data['exemptRolesPreventManualChanges'];
$wrapper->add($f);
$f = $this->wire('modules')->get("InputfieldCheckbox");
$f->attr('name', 'liveChangesProtected');
$f->label = __('Live changes protected');
$f->showIf = "preventManualChanges!=1";
$f->columnWidth = 50;
$f->description = __('If checked, further changes to the name (to match a changing title) will not happen if the name field was manually changed at any time during the current page edit.');
$f->attr('checked', $data['liveChangesProtected'] ? 'checked' : '' );
$wrapper->add($f);
$f = $this->wire('modules')->get('InputfieldAsmSelect');
$f->attr('name+id', 'exemptRolesLiveChangesProtected');
$f->label = __('Roles exempt from live changes protected');
$f->description = __("The selected roles will not be subject to the Live Changes Protected check.\nThe name will change automatically with the title (unless the 'Automatically rename the name field to match the title' is unchecked.), but they will be able to manually edit the page name as desired.");
$f->setAsmSelectOption('sortable', false);
$f->showIf = "liveChangesProtected=1, preventManualChanges!=1";
$f->columnWidth = 50;
foreach($this->wire('roles') as $r) {
$f->addOption($r->name);
}
if(isset($data['exemptRolesLiveChangesProtected'])) $f->value = $data['exemptRolesLiveChangesProtected'];
$wrapper->add($f);
return $wrapper;
}
public function ___upgrade($fromVersion, $toVersion) {
if(version_compare($fromVersion, '2.0.0', '<=')) {
$moduleSettings = $this->wire('modules')->getConfig($this);
// migrate from old single exemptRoles setting for several rules to separate settings for each rule
if(isset($moduleSettings['exemptRoles'])) {
$moduleSettings['exemptRolesInitialDifferencesProtected'] = $moduleSettings['exemptRoles'];
$moduleSettings['exemptRolesPreventManualChanges'] = $moduleSettings['exemptRoles'];
$moduleSettings['exemptRolesLiveChangesProtected'] = $moduleSettings['exemptRoles'];
unset($moduleSettings['exemptRoles']);
}
// migrate from old exemptPages and exemptTemplates to specifiedPages and specifiedTemplates
if(isset($moduleSettings['exemptPages'])) {
$moduleSettings['specifiedPages'] = $moduleSettings['exemptPages'];
unset($moduleSettings['exemptPages']);
}
if(isset($moduleSettings['exemptTemplates'])) {
$moduleSettings['specifiedTemplates'] = $moduleSettings['exemptTemplates'];
unset($moduleSettings['exemptTemplates']);
}
// tweak to change liveChanges to nicer liveChangesProtected
if(isset($moduleSettings['liveChanges'])) {
$moduleSettings['liveChangesProtected'] = $moduleSettings['liveChanges'];
unset($moduleSettings['liveChanges']);
}
// tweak to change initialDifferences to nicer initialDifferencesProtected
if(isset($moduleSettings['initialDifferences'])) {
$moduleSettings['initialDifferencesProtected'] = $moduleSettings['initialDifferences'];
unset($moduleSettings['initialDifferences']);
}
$this->wire('modules')->saveConfig($this, $moduleSettings);
}
}
}