├── README.md
├── TrashmanWatchdog.module
└── ProcessTrashman.module
/README.md:
--------------------------------------------------------------------------------
1 | # Trashman module
2 |
3 | ProcessWire module which adds new admin page where users without superuser
4 | role can view and restore pages in trash.
5 |
6 | ## How to use this
7 |
8 | Install ProcessTrashman module, which automatically installs the required
9 | watchdog module. Installation creates new permission called "trashman".
10 | Give that new permission for all the roles that you want to have access to
11 | trash. After that users with those roles can view trash from Setup => Trashman.
12 |
13 | You can move or rename the trashman page if you want. Also visit the module
14 | settings to set date formats for your liking.
15 |
16 | ## Please note
17 |
18 | Trashman doesn't allow viewing or restoring pages that were trashed before
19 | Trashman was installed. This is by design, since I want to have trashed date.
20 |
21 | ## Licence
22 |
23 | GPL, just like [ProcessWire](https://github.com/ryancramerdesign/ProcessWire/blob/master/LICENSE.txt)
24 |
25 | ------
26 | Trashman Copyright 2013 by Antti Peisa
27 | [ProcessWire](http://processwire.com) Copyright 2013 by Ryan Cramer
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/TrashmanWatchdog.module:
--------------------------------------------------------------------------------
1 | 'Trashman Watchdog',
28 | 'version' => 100,
29 | 'summary' => 'Autoload module, that saves the parent_before_trash value before page is trashed',
30 | 'href' => 'http://modules.processwire.com',
31 | 'singular' => true,
32 | 'autoload' => true,
33 | 'requires' => 'ProcessTrashman'
34 | );
35 | }
36 |
37 | public function init() {
38 | $this->pages->addHookBefore('trash', $this, 'pageTrashed');
39 | $this->pages->addHookAfter('restore', $this, 'removePageFromTrash');
40 | $this->pages->addHookAfter('delete', $this, 'removePageFromTrash');
41 | }
42 |
43 | /**
44 | * When page is trashed, we save a row for it on ProcessTrashman table
45 | *
46 | */
47 | public function pageTrashed($event) {
48 | $page = $event->arguments[0];
49 | if ($page->parent->id != $this->config->trashPageID) {
50 | $sql = "INSERT INTO ProcessTrashman SET page_id = {$page->id}, parent_id = {$page->parent->id} ON DUPLICATE KEY UPDATE parent_id = {$page->parent->id};";
51 | $this->db->query($sql);
52 | }
53 | }
54 |
55 | public function removePageFromTrash($event) {
56 | $page = $event->arguments[0];
57 | $sql = "DELETE FROM ProcessTrashman WHERE page_id = {$page->id};";
58 | $this->db->query($sql);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/ProcessTrashman.module:
--------------------------------------------------------------------------------
1 | 'Trashman',
29 | 'summary' => 'Custom trash view for clients also.',
30 | 'version' => 100,
31 | 'author' => 'Antti Peisa',
32 | 'href' => 'http://modules.processwire.com/',
33 | 'permission' => 'trashman',
34 | 'installs' => 'TrashmanWatchdog'
35 | );
36 | }
37 |
38 | private $dateFormat;
39 |
40 | const pageName = 'trashman';
41 |
42 | static public function getDefaultData() {
43 | return array(
44 | 'dateInputFormat' => 'Y-m-d',
45 | 'timeInputFormat' => 'H:i:s',
46 | );
47 | }
48 |
49 | public function init() {
50 | // we use date and time format combined when
51 | $this->dateFormat = $this->dateInputFormat . " " . $this->timeInputFormat;
52 | }
53 |
54 | public function __construct() {
55 | foreach (self::getDefaultData() as $key => $value) {
56 | $this->$key = $value;
57 | }
58 | }
59 |
60 | public function ___execute() {
61 |
62 | $out = '';
63 |
64 | // Grab end value
65 | $end = ($this->input->get->end) ? strtotime($this->input->get->end) : strtotime("tomorrow -1second");
66 |
67 | // If timeInputFormat is empty, then we need to add 23:59 to end value
68 | if ($this->timeInputFormat == "" && $this->input->get->end)
69 | $end = $end + 60 * 60 * 24 - 1;
70 |
71 | $start = ($this->input->get->start) ? strtotime($this->input->get->start) : $end - 60 * 60 * 24 * 32 + 1;
72 |
73 | // Mysql date formats for those
74 | $startDB = date('Y-m-d H:i:s', $start);
75 | $endDB = date('Y-m-d H:i:s', $end);
76 |
77 | // And nice readable formats
78 | $startNice = date($this->dateFormat, $start);
79 | $endNice = date($this->dateFormat, $end);
80 |
81 | if ($this->input->get->start) {
82 | $out .= "
" . sprintf($this->_('Showing pages that were removed between %1$s and %2$s'), $startNice, $endNice) . "
";
83 | } else {
84 | $out .= "" . $this->_('Showing pages that were removed in last 30 days') . "
";
85 | }
86 |
87 | $form = $this->modules->get("InputfieldForm");
88 | $form->method = "get";
89 |
90 | $wrapper = $this->modules->get("InputfieldFieldset");
91 | $wrapper->label = $this->_("Filters");
92 | $wrapper->collapsed = Inputfield::collapsedYes;
93 |
94 | $startDate = $this->modules->get("InputfieldDatetime");
95 | $startDate->name = "start";
96 | $startDate->label = $this->_("Startdate");
97 | $startDate->value = $start;
98 | $startDate->columnWidth = 50;
99 | $startDate->dateInputFormat = $this->dateInputFormat;
100 | $startDate->timeInputFormat = $this->timeInputFormat;
101 |
102 | $endDate = $this->modules->get("InputfieldDatetime");
103 | $endDate->name = "end";
104 | $endDate->label = $this->_("Enddate");
105 | $endDate->value = $end;
106 | $endDate->columnWidth = 50;
107 | $endDate->dateInputFormat = $this->dateInputFormat;
108 | $endDate->timeInputFormat = $this->timeInputFormat;
109 |
110 | $submit = $this->modules->get("InputfieldSubmit");
111 |
112 | // Build the form
113 | $wrapper->add($startDate);
114 | $wrapper->add($endDate);
115 | $wrapper->add($submit);
116 | $form->add($wrapper);
117 |
118 | // Find pages in trash that were removed between wanted dates
119 | $sql = "SELECT page_id, parent_id, trashed FROM {$this->className} WHERE trashed >= '$startDB' AND trashed <= '$endDB';";
120 | $result = $this->db->query($sql);
121 |
122 | $pagesInTrash = new PageArray();
123 | while ($row = $result->fetch_assoc()) {
124 | $p = $this->pages->get($row['page_id']);
125 |
126 | // If there is no page anymore, let's continue TODO: remove this from db also
127 | if (!$p->id)
128 | continue;
129 |
130 | // Add parent page and trashed date
131 | $p->oldParent = $this->pages->get($row['parent_id']);
132 | $p->trashed = $row['trashed'];
133 | $pagesInTrash->add($p);
134 | }
135 |
136 | // Render the form
137 | $out .= $form->render();
138 |
139 | // Render results
140 | if ($pagesInTrash->count() == 0) {
141 | $out .= $this->_("Pages not found");
142 | } else {
143 |
144 | $table = $this->modules->get("MarkupAdminDataTable");
145 | $table->setEncodeEntities(false);
146 | $table->headerRow(array(
147 | $this->_("Page title"),
148 | $this->_("Number of children"),
149 | $this->_("Old parent"),
150 | $this->_("Date removed"),
151 | $this->_("Restore")
152 | ));
153 |
154 | foreach ($pagesInTrash as $p) {
155 |
156 | if (!$p->oldParent->id || $p->oldParent->isTrash()) {
157 | $parentPath = $this->_("unknown");
158 | #$p->oldParent->id = 0; // causes page_id = 0 bug
159 | $parent = 0;
160 | }
161 | else {
162 | $parentPath = $p->oldParent->path;
163 | $parent = $p->oldParent->id;
164 | }
165 |
166 |
167 | $table->row(array(
168 | $p->get("title|name"),
169 | $p->children("include=all")->count(),
170 | $parentPath,
171 | $p->trashed,
172 | "" . $this->_("restore") . ""
173 | ));
174 | }
175 |
176 | $out .= $table->render();
177 | }
178 |
179 | return $out;
180 | }
181 |
182 | public function ___executeRestore() {
183 |
184 | if ($this->input->post->restore_page)
185 | return $this->processInput();
186 |
187 | $p = $this->pages->get($this->input->get->page_id);
188 | $out = '';
189 |
190 | if (!$p->id) {
191 | $this->error($this->_("Page not found"));
192 | $this->session->redirect("../");
193 | }
194 |
195 | if (!$p->isTrash()) {
196 | $this->error($this->_("Page is not in trash"));
197 | $this->session->redirect("../");
198 | }
199 |
200 | // set a new headline, replacing the one used by our page (optional)
201 | Wire::setFuel('processHeadline', $this->_("Restore page"));
202 |
203 | $out .= "$p->title
";
204 |
205 | $form = $this->modules->get("InputfieldForm");
206 | $form->action = "./";
207 | $form->method = "post";
208 |
209 | $field = $this->modules->get("InputfieldPageListSelect");
210 | $field->parent_id = 0;
211 | $field->value = (int) $this->input->get->parent_id;
212 | $field->required = true;
213 | $field->label = $this->_("Parent page for the restored page");
214 | $field->description = sprintf($this->_("'%s' will be restored under selected page. Please note that the restored page will be unpublished, so you can edit it before publishing."), $p->get("title|name"));
215 | $field->name = "parent_id";
216 | $form->add($field);
217 |
218 | $field = $this->modules->get("InputfieldHidden");
219 | $field->name = "page_id";
220 | $field->value = $p->id;
221 | $form->add($field);
222 |
223 | $submit = $this->modules->get("InputfieldSubmit");
224 | $submit->name = "restore_page";
225 | $form->add($submit);
226 |
227 | $out .= $form->render();
228 | $out .= "" . $this->_("Go Back") . "
";
229 |
230 | // add a breadcrumb that returns to our main page
231 | $this->breadcrumbs->add(new Breadcrumb('../', $this->page->title));
232 |
233 | return $out;
234 | }
235 |
236 | public function processInput() {
237 | if (!$this->input->post->parent_id || !$this->input->post->page_id) {
238 | $this->error($this->_("Error while trying to restore the page"));
239 | return "" . $this->_("Go back") . "";
240 | }
241 |
242 | $page = $this->pages->get($this->input->post->page_id);
243 | $page->parent = $this->pages->get($this->input->post->parent_id);
244 | $page->addStatus(Page::statusUnpublished);
245 |
246 | $this->pages->restore($page);
247 | $this->message($this->_("Page restored, but kept unpublished"));
248 |
249 | $admin = $this->pages->get($this->config->adminRootPageID);
250 | $this->session->redirect($admin->url . "page/edit/?id=$page->id");
251 | }
252 |
253 | /**
254 | * Module configuration
255 | *
256 | * @param array $data
257 | * @return InputfieldWrapper
258 | */
259 | static public function getModuleConfigInputfields(array $data) {
260 |
261 | // this is a container for fields, basically like a fieldset
262 | $fields = new InputfieldWrapper();
263 |
264 | // since this is a static function, we can't use $this->modules, so get them from the global wire() function
265 | $modules = wire('modules');
266 |
267 | // merge default config settings (custom values overwrite defaults)
268 | $defaults = self::getDefaultData();
269 | $data = array_merge($defaults, $data);
270 |
271 | // date format used
272 | $field = $modules->get("InputfieldText");
273 | $field->name = "dateInputFormat";
274 | $field->label = "Date Format";
275 | $field->notes = "See the [PHP date](http://www.php.net/manual/en/function.date.php) function reference for more information on how to customize this format.";
276 | $field->value = $data['dateInputFormat'];
277 | $field->size = 70;
278 | $fields->add($field);
279 |
280 | // time format used
281 | $field = $modules->get("InputfieldText");
282 | $field->name = "timeInputFormat";
283 | $field->label = "Time Format";
284 | $field->description = "Leave this empty, if you don't need to filter by time";
285 | $field->notes = "See the [PHP date](http://www.php.net/manual/en/function.date.php) function reference for more information on how to customize this format.";
286 | $field->value = $data['timeInputFormat'];
287 | $field->size = 70;
288 | $fields->add($field);
289 |
290 | return $fields;
291 | }
292 |
293 | /**
294 | * Called only when your module is installed
295 | *
296 | * This version creates a new page with this Process module assigned.
297 | *
298 | */
299 | public function ___install() {
300 |
301 | $permission = $this->permissions->add("trashman");
302 | $permission->title = "Can view and restore pages in trash";
303 | $permission->save();
304 | $this->session->message("Created new permission: trashman");
305 |
306 | $page = new Page();
307 | $page->template = 'admin';
308 | $page->name = self::pageName;
309 | $page->parent = $this->pages->get($this->config->adminRootPageID)->child('name=setup');
310 | $page->process = $this;
311 | $page->title = "Trashman";
312 | $page->save();
313 |
314 | $this->message("Created Page: {$page->path}");
315 |
316 | $sql = <<< _END
317 |
318 | CREATE TABLE {$this->className} (
319 | id int unsigned NOT NULL auto_increment,
320 | page_id int unsigned DEFAULT 0,
321 | parent_id int unsigned DEFAULT 0,
322 | trashed TIMESTAMP,
323 | PRIMARY KEY(id),
324 | UNIQUE KEY(page_id)
325 | ) ENGINE = MYISAM;
326 |
327 | _END;
328 |
329 | $this->db->query($sql);
330 |
331 | $this->message("Created database table: {$this->className}");
332 | }
333 |
334 | /**
335 | * Called only when your module is uninstalled
336 | *
337 | * This should return the site to the same state it was in before the module was installed.
338 | *
339 | */
340 | public function ___uninstall() {
341 |
342 | $permission = $this->permissions->get("trashman");
343 | $this->permissions->delete($permission);
344 |
345 | // find the page we installed, locating it by the process field (which has the module ID)
346 | // it would probably be sufficient just to locate by name, but this is just to be extra sure.
347 | $moduleID = $this->modules->getModuleID($this);
348 | $page = $this->pages->get("template=admin, process=$moduleID, name=" . self::pageName);
349 |
350 | if ($page->id) {
351 | // if we found the page, let the user know and delete it
352 | $this->message("Deleting Page: {$page->path}");
353 | $page->delete();
354 | }
355 |
356 | $this->db->query("DROP TABLE {$this->className}");
357 | }
358 |
359 | }
360 |
361 |
--------------------------------------------------------------------------------