Automad
 All Classes Functions Variables Pages
page_data.php
1 <?php
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  * AUTOMAD
29  *
30  * Copyright (c) 2014 by Marc Anton Dahmen
31  * http://marcdahmen.de
32  *
33  * Licensed under the MIT license.
34  * http://automad.org/license
35  */
36 
37 
38 namespace Automad\Core;
39 
40 
41 defined('AUTOMAD') or die('Direct access not permitted!');
42 
43 
44 /*
45  * All ajax requests regarding a page's data file get processed here.
46  * Basically that means "Saving, Renaming & Redirecting" as the first option and "Loading" as the second option.
47  *
48  * When "$_POST['data']" exists, that means, that a form with "edited" page information got submitted and the data gets processed to be written into the data file.
49  * In that case, this handler either returns a redirect URL (for reloading the page, in case it got renamed) or an error message. NO (!) form data is submitted back, since
50  * The form already exists on the "client side".
51  *
52  * When only "$_POST['url']" got submitted, that means, the the form on the "client side" is still empty and therefore it must be an initial page loading request, which then will return
53  * the page's data as the form HTML.
54  *
55  * NOTE: Only the inner elements of the form are returned. To keep the outer form information lose form the processing here, there must be an outer form existing on the page to wrap that HTML output.
56  */
57 
58 
59 // Array for returned JSON data.
60 $output = array();
61 
62 
63 // Verify page's URL - The URL must exist in the site's collection.
64 if (isset($_POST['url']) && array_key_exists($_POST['url'], $this->collection)) {
65 
66 
67  $url = $_POST['url'];
68 
69 
70  // The currently edited page.
71  $Page = $this->collection[$url];
72 
73 
74  // If the posted form contains any "data", save the form's data to the page file.
75  if (isset($_POST['data'])) {
76 
77 
78  $data = $_POST['data'];
79 
80 
81  // A title is required for building the page's path.
82  // If there is no title provided, an error will be returned instead of saving and moving the page.
83  if ($data['title']) {
84 
85 
86  // Check if the parent directory is writable.
87  if (is_writable(dirname(dirname($this->pageFile($Page))))) {
88 
89 
90  // Check if the page's file and the page's directory is writable.
91  if (is_writable($this->pageFile($Page)) && is_writable(dirname($this->pageFile($Page)))) {
92 
93 
94  // Remove empty data.
95  // Needs to be done here, to be able to simply test for empty title field.
96  $data = array_filter($data);
97 
98 
99  // Set hidden parameter within the $data array.
100  // Since it is a checkbox, it must get parsed separately.
101  if (isset($_POST['hidden'])) {
102  $data['hidden'] = 1;
103  }
104 
105 
106  // The theme and the template get passed as theme/template.php combination separate form $_POST['data'].
107  // That information has to be parsed first and "subdivided".
108 
109  // Get correct theme name.
110  // If the theme is not set and there is no slash passed within 'theme_template', the resulting dirname is just a dot.
111  // In that case, $data['theme'] gets removed (no theme - use site theme).
112  if (dirname($_POST['theme_template']) != '.') {
113  $data['theme'] = dirname($_POST['theme_template']);
114  } else {
115  unset($data['theme']);
116  }
117 
118 
119  // Build file content to be written to the txt file.
120  $pairs = array();
121 
122  foreach ($data as $key => $value) {
123  $pairs[] = $key . AM_PARSE_PAIR_SEPARATOR . ' ' . $value;
124  }
125 
126  $content = implode("\r\n\r\n" . AM_PARSE_BLOCK_SEPARATOR . "\r\n\r\n", $pairs);
127 
128 
129  // Delete old (current) file, in case, the template has changed.
130  unlink($this->pageFile($Page));
131 
132 
133  // Build the path of the data file by appending the basename of 'theme_template' to $page->path.
134  $newPageFile = AM_BASE_DIR . AM_DIR_PAGES . $Page->path . str_replace('.php', '', basename($_POST['theme_template'])) . '.' . AM_FILE_EXT_DATA;
135 
136 
137  // Save new file within current directory, even when the prefix/title changed.
138  // Renaming/moving is done in a later step, to keep files and subpages bundled to the current text file.
139  $old = umask(0);
140  file_put_contents($newPageFile, $content);
141  umask($old);
142 
143 
144  // If the page is not the homepage,
145  // rename the page's directory including all children and all files, after saving according to the (new) title and prefix.
146  // $this->movePage() will check if renaming is needed, and will skip moving, when old and new path are equal.
147  if ($url != '/') {
148 
149  if (!isset($_POST['prefix'])) {
150  $prefix = '';
151  } else {
152  $prefix = $_POST['prefix'];
153  }
154 
155  $newPagePath = $this->movePage($Page->path, dirname($Page->path), $prefix, $data['title']);
156 
157  } else {
158 
159  // In case the page is the home page, the path is just '/'.
160  $newPagePath = '/';
161 
162  }
163 
164 
165  // Clear the cache to make sure, the changes get reflected on the website directly.
166  $Cache = new Cache();
167  $Cache->clear();
168 
169 
170  // Rebuild Automad object, since the file structure might be different now.
171  $Automad = new Automad(false);
172  $collection = $Automad->getCollection();
173 
174 
175  // Find new URL.
176  foreach ($collection as $key => $page) {
177 
178  if ($page->path == $newPagePath) {
179 
180  // Just return a redirect URL (might be the old URL), to also reflect the possible renaming in all the GUI's navigation.
181  $output['redirect'] = '?context=edit_page&url=' . urlencode($key);
182  break;
183 
184  }
185 
186  }
187 
188 
189  } else {
190 
191  $output['error'] = $this->tb['error_permission'] . '<p>' . dirname($this->pageFile($Page)) . '</p>';
192 
193  }
194 
195 
196  } else {
197 
198  $output['error'] = $this->tb['error_permission'] . '<p>' . dirname(dirname($this->pageFile($Page))) . '</p>';
199 
200  }
201 
202 
203  } else {
204 
205  // If the title is missing, just return an error.
206  $output['error'] = $this->tb['error_page_title'];
207 
208  }
209 
210 
211  } else {
212 
213 
214  // If only the URL got submitted,
215  // get the page's data from its .txt file and return a form's inner HTML containing these information.
216 
217  // Get page's data.
218  $data = Parse::textFile($this->pageFile($Page));
219 
220 
221  // Set up all standard variables.
222 
223  // These keys are always part of the form and have to be normalized/created.
224  $standardKeys = array(AM_KEY_TITLE, AM_KEY_TAGS, AM_KEY_THEME, AM_KEY_URL, AM_KEY_HIDDEN);
225 
226  // Create empty array items for all missing standard variables in $data.
227  foreach ($standardKeys as $key) {
228  if (!isset($data[$key])) {
229  $data[$key] = false;
230  }
231  }
232 
233  // Set title, in case the variable is not set (when editing the text file in an editor and the title wasn't set correctly)
234  if (!$data[AM_KEY_TITLE]) {
235  $data[AM_KEY_TITLE] = basename($Page->url);
236  }
237 
238  // Check if page is hidden.
239  if (isset($data[AM_KEY_HIDDEN]) && $data[AM_KEY_HIDDEN] && $data[AM_KEY_HIDDEN] != 'false') {
240  $hidden = true;
241  } else {
242  $hidden = false;
243  }
244 
245 
246  // Get variable keys from selected template file, which are not part of the $standardKeys.
247  // If one of these keys is not a key in $data, its textarea gets automatically created in the form, to make it easier for the user to understand,
248  // what variables are available without having to add them manually. (below)
249  $templateKeys = array_diff($this->getPageVarsInTemplate($data[AM_KEY_THEME], $Page->template), $standardKeys);
250 
251 
252  // Start buffering the HTML.
253  ob_start();
254 
255  ?>
256 
257  <div class="form-group">
258  <label for="input-data-title"><?php echo ucwords(AM_KEY_TITLE); ?></label>
259  <input id="input-data-title" class="form-control input-lg" type="text" name="data[<?php echo AM_KEY_TITLE; ?>]" value="<?php echo str_replace('"', '&quot;', $data[AM_KEY_TITLE]); ?>" onkeypress="return event.keyCode != 13;" placeholder="Required" required />
260  </div>
261  <div class="form-group">
262  <label for="input-data-tags"><?php echo $this->tb['page_tags']; ?></label>
263  <input id="input-data-tags" class="form-control" type="text" name="data[<?php echo AM_KEY_TAGS; ?>]" value="<?php echo str_replace('"', '&quot;', $data[AM_KEY_TAGS]); ?>" onkeypress="return event.keyCode != 13;" />
264  </div>
265 
266  <hr>
267 
268  <h3><?php echo $this->tb['page_settings']; ?></h3>
269  <div class="form-group">
270  <button type="button" data-toggle="modal" data-target="#select-template-modal" class="btn btn-default">
271  <?php
272 
273  if ($data[AM_KEY_THEME]) {
274  $theme = $data[AM_KEY_THEME];
275  } else {
276  $theme = $this->siteData[AM_KEY_THEME];
277  }
278 
279  echo $this->tb['page_theme_template'];
280 
281  // Give feedback in template button whether the template exists or not.
282  if (file_exists(AM_BASE_DIR . AM_DIR_THEMES . '/' . $theme . '/' . $Page->template . '.php')) {
283  echo ' <span class="badge">' . ucwords(str_replace('_', ' ', ltrim($data[AM_KEY_THEME] . ' > ', '> ') . $Page->template)) . '</span>';
284  } else {
285  echo ' <span class="badge off">' . ucwords(str_replace('_', ' ', ltrim($data[AM_KEY_THEME] . ' > ', '> ') . $Page->template)) . ' - ' . $this->tb['error_template_missing'] . '</span>';
286  }
287 
288  ?>
289  </button>
290  </div>
291  <!-- Select Template Modal -->
292  <div id="select-template-modal" class="modal fade">
293  <div class="modal-dialog">
294  <div class="modal-content">
295  <div class="modal-body">
296  <?php echo $this->templateSelectBox('theme_template', 'theme_template', $data[AM_KEY_THEME], $Page->template); ?>
297  </div>
298  <div class="modal-footer">
299  <div class="btn-group btn-group-justified">
300  <div class="btn-group">
301  <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <?php echo $this->tb['btn_close']; ?></button>
302  </div>
303  <div class="btn-group">
304  <button type="submit" class="btn btn-primary" data-loading-text="<?php echo $this->tb['btn_loading']; ?>"><span class="glyphicon glyphicon-ok"></span> <?php echo $this->tb['btn_apply_reload']; ?></button>
305  </div>
306  </div>
307  </div>
308  </div>
309  </div>
310  </div>
311  <?php if ($Page->path != '/') { ?>
312  <div class="row">
313  <div class="form-group col-xs-6">
314  <label for="input-prefix"><?php echo $this->tb['page_prefix']; ?></label>
315  <input id="input-prefix" class="form-control" type="text" name="prefix" value="<?php echo $this->extractPrefixFromPath($Page->path); ?>" onkeypress="return event.keyCode != 13;" />
316  </div>
317  <div class="form-group col-xs-6">
318  <label><?php echo $this->tb['page_visibility']; ?></label>
319  <div class="btn-group btn-group-justified" data-toggle="buttons">
320  <label class="btn btn-default<?php if ($hidden) { echo ' active'; } ?>"><?php echo $this->tb['btn_hide_page']; ?>
321  <input type="checkbox" name="<?php echo AM_KEY_HIDDEN; ?>"<?php if ($hidden) { echo ' checked'; } ?> />
322  </label>
323  </div>
324  </div>
325  </div>
326  <div class="form-group">
327  <label for="input-redirect"><?php echo $this->tb['page_redirect']; ?></label>
328  <input id="input-redirect" class="form-control" type="text" name="data[<?php echo AM_KEY_URL; ?>]" value="<?php echo $data[AM_KEY_URL]; ?>" onkeypress="return event.keyCode != 13;" />
329  </div>
330  <?php } ?>
331 
332  <hr>
333 
334  <h3><?php echo $this->tb['page_vars_used']; ?></h3>
335  <?php
336 
337  // Add textareas for all variables in $data, which are used in the currently selected template and are not part of the $standardKeys array
338  // and create empty textareas for those keys found in the template, but are not defined in $data.
339  foreach ($templateKeys as $key) {
340  if (isset($data[$key])) {
341  echo $this->varTextArea($key, $data[$key]);
342  } else {
343  echo $this->varTextArea($key, '');
344  }
345  }
346 
347  ?>
348 
349  <hr>
350 
351  <h3><?php echo $this->tb['page_vars_unused']; ?></h3>
352  <div id="automad-custom-variables">
353  <?php
354 
355  // Add textareas for all left over variables $data, which don't show up in the template.
356  // The left over vars get also a remove button, since they are optional.
357  // Even when these vars are not used in the current template, they might be used in another template and should therefore still be present.
358  foreach (array_diff(array_keys($data), $templateKeys, $standardKeys) as $key) {
359  echo $this->varTextArea($key, $data[$key], true);
360  }
361 
362  ?>
363  </div>
364  <br />
365  <a class="btn btn-default" href="#" data-toggle="modal" data-target="#automad-add-variable-modal"><span class="glyphicon glyphicon-plus"></span> <?php echo $this->tb['btn_add_var']; ?></a>
366 
367  <hr>
368 
369  <div class="btn-group btn-group-justified">
370  <div class="btn-group"><a class="btn btn-danger" href=""><span class="glyphicon glyphicon-remove"></span> <?php echo $this->tb['btn_discard']; ?></a></div>
371  <div class="btn-group"><button type="submit" class="btn btn-success" data-loading-text="<?php echo $this->tb['btn_loading']?>"><span class="glyphicon glyphicon-ok"></span> <?php echo $this->tb['btn_save']; ?></button></div>
372  </div>
373 
374  <!-- Add Variable Modal -->
375  <div id="automad-add-variable-modal" class="modal fade">
376  <div class="modal-dialog">
377  <div class="modal-content">
378  <div class="modal-header">
379  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
380  <h3 class="modal-title"><?php echo $this->tb['btn_add_var']; ?></h3>
381  </div>
382  <div class="modal-body">
383  <div class="form-group">
384  <label for="automad-add-variable-name"><?php echo $this->tb['page_var_name']; ?></label>
385  <input type="text" class="form-control" id="automad-add-variable-name" onkeypress="return event.keyCode != 13;" />
386  </div>
387  </div>
388  <div class="modal-footer">
389  <div class="btn-group btn-group-justified">
390  <div class="btn-group">
391  <button type="button" class="btn btn-default" data-dismiss="modal">
392  <span class="glyphicon glyphicon-remove"></span> <?php echo $this->tb['btn_close']; ?>
393  </button>
394  </div>
395  <div class="btn-group">
396  <button type="button" class="btn btn-primary" id="automad-add-variable-button" data-automad-error-exists="<?php echo $this->tb['error_var_exists']; ?>" data-automad-error-name="<?php echo $this->tb['error_var_name']; ?>">
397  <span class="glyphicon glyphicon-plus"></span> <?php echo $this->tb['btn_add_var']; ?>
398  </button>
399  </div>
400  </div>
401  </div>
402  </div>
403  </div>
404  </div>
405 
406  <?php
407 
408 
409  // Save buffer to JSON array.
410  $output['html'] = ob_get_contents();
411  ob_end_clean();
412 
413 
414  }
415 
416 
417 } else {
418 
419  $output['error'] = $this->tb['error_page_not_found'];
420 
421 }
422 
423 
424 echo json_encode($output);
425 
426 
427 ?>
static textFile($file)
Definition: parse.php:473