Project

General

Profile

Download (19.4 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * pkg.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2022 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * Licensed under the Apache License, Version 2.0 (the "License");
12
 * you may not use this file except in compliance with the License.
13
 * You may obtain a copy of the License at
14
 *
15
 * http://www.apache.org/licenses/LICENSE-2.0
16
 *
17
 * Unless required by applicable law or agreed to in writing, software
18
 * distributed under the License is distributed on an "AS IS" BASIS,
19
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
 * See the License for the specific language governing permissions and
21
 * limitations under the License.
22
 */
23

    
24
##|+PRIV
25
##|*IDENT=page-package-settings
26
##|*NAME=Package: Settings
27
##|*DESCR=Allow access to the 'Package: Settings' page.
28
##|*MATCH=pkg.php*
29
##|-PRIV
30

    
31
require_once("guiconfig.inc");
32
require_once("pkg-utils.inc");
33

    
34
function domTT_title($title_msg) {
35
	print "onmouseout=\"this.style.color = ''; domTT_mouseout(this, event);\" onmouseover=\"domTT_activate(this, event, 'content', '".gettext($title_msg)."', 'trail', true, 'delay', 0, 'fade', 'both', 'fadeMax', 93, 'styleClass', 'niceTitle');\"";
36
}
37

    
38
$xml = $_REQUEST['xml'];
39

    
40
if ($xml == "") {
41
	$pgtitle = array(gettext("Package"), gettext("Editor"));
42
	$pglinks = array("", "@self");
43
	include("head.inc");
44
	print_info_box(gettext("No valid package defined."), 'danger', false);
45
	include("foot.inc");
46
	exit;
47
} else {
48
	$pkg_xml_prefix = "/usr/local/pkg/";
49
	$pkg_full_path = "{$pkg_xml_prefix}/{$xml}";
50
	$pkg_realpath = realpath($pkg_full_path);
51
	if (empty($pkg_realpath)) {
52
		$path_error = sprintf(gettext("Package path %s not found."), htmlspecialchars($pkg_full_path));
53
	} else if (substr_compare($pkg_realpath, $pkg_xml_prefix, 0, strlen($pkg_xml_prefix))) {
54
		$path_error = sprintf(gettext("Invalid path %s specified."), htmlspecialchars($pkg_full_path));
55
	}
56

    
57
	if (!empty($path_error)) {
58
		include("head.inc");
59
		print_info_box($path_error . "<br />" . gettext("Try reinstalling the package."), 'danger', false);
60
		include("foot.inc");
61
		die;
62
	}
63

    
64
	if (file_exists($pkg_full_path)) {
65
		$pkg = parse_xml_config_pkg($pkg_full_path, "packagegui");
66
	} else {
67
		include("head.inc");
68
		print_info_box(sprintf(gettext("File not found %s."), htmlspecialchars($xml)), 'danger', false);
69
		include("foot.inc");
70
		exit;
71
	}
72
}
73

    
74
if ($pkg['donotsave'] != "") {
75
	header("Location: pkg_edit.php?xml=" . $xml);
76
	exit;
77
}
78

    
79
if ($pkg['include_file'] != "") {
80
	require_once($pkg['include_file']);
81
}
82

    
83
if ($_REQUEST['startdisplayingat']) {
84
	$startdisplayingat = $_REQUEST['startdisplayingat'];
85
}
86

    
87
if ($_REQUEST['display_maximum_rows']) {
88
	if ($_REQUEST['display_maximum_rows']) {
89
		$display_maximum_rows = $_REQUEST['display_maximum_rows'];
90
	}
91
}
92

    
93
$config_path = sprintf('installedpackages/%s/config', xml_safe_fieldname($pkg['name']));
94

    
95
$evaledvar = config_get_path($config_path, []);
96

    
97
if ($_REQUEST['act'] == "update") {
98

    
99
	if (is_array($config['installedpackages'][$pkg['name']]) && $pkg['name'] != "" && $_REQUEST['ids'] !="") {
100
		// get current values
101
		$current_values=$config['installedpackages'][$pkg['name']]['config'];
102
		// get updated ids
103
		parse_str($_REQUEST['ids'], $update_list);
104
		// sort ids to know what to change
105
		// useful to do not lose data when using sorting and paging
106
		$sort_list=$update_list['ids'];
107
		sort($sort_list);
108
		// apply updates
109
		foreach ($update_list['ids'] as $key=> $value) {
110
			$config['installedpackages'][$pkg['name']]['config'][$sort_list[$key]]=$current_values[$update_list['ids'][$key]];
111
		}
112
		// save current config
113
		write_config(gettext("Package configuration changes saved from package settings page."));
114
		// sync package
115
		eval ("{$pkg['custom_php_resync_config_command']}");
116
	}
117
	// function called via jquery, no need to continue after save changes.
118
	exit;
119
}
120
if ($_REQUEST['act'] == "del") {
121
	// loop through our fieldnames and automatically setup the fieldnames
122
	// in the environment.	ie: a fieldname of username with a value of
123
	// testuser would automatically eval $username = "testuser";
124
	foreach ($evaledvar as $ip) {
125
		if ($pkg['adddeleteeditpagefields']['columnitem']) {
126
			foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
127
				${xml_safe_fieldname($column['fielddescr'])} = $ip[xml_safe_fieldname($column['fieldname'])];
128
			}
129
		}
130
	}
131

    
132
	init_config_arr(array('installedpackages', xml_safe_fieldname($pkg['name']), 'config'));
133
	$a_pkg = &$config['installedpackages'][xml_safe_fieldname($pkg['name'])]['config'];
134

    
135
	if ($a_pkg[$_REQUEST['id']]) {
136
		unset($a_pkg[$_REQUEST['id']]);
137
		write_config(gettext("Package configuration item deleted from package settings page."));
138
		if ($pkg['custom_delete_php_command'] != "") {
139
			if ($pkg['custom_php_command_before_form'] != "") {
140
				eval($pkg['custom_php_command_before_form']);
141
			}
142
			eval($pkg['custom_delete_php_command']);
143
		}
144
		header("Location:  pkg.php?xml=" . $xml);
145
		exit;
146
	}
147
}
148

    
149
ob_start();
150

    
151
$iflist = get_configured_interface_with_descr(true);
152
$evaledvar = config_get_path($config_path, []);
153

    
154
if ($pkg['custom_php_global_functions'] != "") {
155
	eval($pkg['custom_php_global_functions']);
156
}
157

    
158
if ($pkg['custom_php_command_before_form'] != "") {
159
	eval($pkg['custom_php_command_before_form']);
160
}
161

    
162
// Breadcrumb
163
if ($pkg['title'] != "") {
164
	/*if (!$only_edit) {						// Is any package still making use of this?? Is this something that is still wanted, considering the breadcrumb policy https://redmine.pfsense.org/issues/5527
165
 		$pkg['title'] = $pkg['title'] . '/Edit';		// If this needs to live on, then it has to be moved to run AFTER "foreach ($pkg['tabs']['tab'] as $tab)"-loop. This due to $pgtitle[] = $tab['text'];
166
	}*/
167
	if (strpos($pkg['title'], '/')) {
168
		$title = explode('/', $pkg['title']);
169

    
170
		foreach ($title as $subtitle) {
171
			$pgtitle[] = gettext($subtitle);
172
			$pglinks[] = "@self";
173
		}
174
	} else {
175
		$pgtitle = array(gettext("Package"), gettext($pkg['title']));
176
		$pglinks = array("", "@self");
177
	}
178
} else {
179
	$pgtitle = array(gettext("Package"), gettext("Editor"));
180
	$pglinks = array("", "@self");
181
}
182

    
183
if ($pkg['tabs'] != "") {
184
	$tab_array = array();
185
	foreach ($pkg['tabs']['tab'] as $tab) {
186
		if ($tab['tab_level']) {
187
			$tab_level = $tab['tab_level'];
188
		} else {
189
			$tab_level = 1;
190
		}
191
		if (isset($tab['active'])) {
192
			$active = true;
193
			$pgtitle[] = $tab['text'];
194
			$pglinks[] = "@self";
195
		} else {
196
			$active = false;
197
		}
198
		$urltmp = "";
199
		if ($tab['url'] != "") {
200
			$urltmp = $tab['url'];
201
		}
202
		if ($tab['xml'] != "") {
203
			$urltmp = "pkg_edit.php?xml=" . $tab['xml'];
204
		}
205

    
206
		$addresswithport = getenv("HTTP_HOST");
207
		$colonpos = strpos($addresswithport, ":");
208
		if ($colonpos !== False) {
209
			//my url is actually just the IP address of the pfsense box
210
			$myurl = substr($addresswithport, 0, $colonpos);
211
		} else {
212
			$myurl = $addresswithport;
213
		}
214
		// eval url so that above $myurl item can be processed if need be.
215
		$url = str_replace('$myurl', $myurl, $urltmp);
216

    
217
		$tab_array[$tab_level][] = array(
218
			$tab['text'],
219
			$active,
220
			$url
221
		);
222
	}
223

    
224
	ksort($tab_array);
225
}
226

    
227
if (!empty($pkg['tabs'])) {
228
	$shortcut_section = $pkg['shortcut_section'];
229
}
230

    
231
include("head.inc");
232
if (isset($tab_array)) {
233
	foreach ($tab_array as $tabid => $tab) {
234
		display_top_tabs($tab);
235
	}
236
}
237

    
238
?>
239

    
240
<script type="text/javascript">
241
//<![CDATA[
242
events.push(function() {
243

    
244
	function setFilter(filtertext) {
245
		$('#pkg_filter').val(filtertext);
246
		document.pkgform.submit();
247
	}
248

    
249
<?php
250
	if ($pkg['adddeleteeditpagefields']['movable']) {
251
?>
252
		$('#mainarea table tbody').sortable({
253
		items: 'tr.sortable',
254
			cursor: 'move',
255
			distance: 10,
256
			opacity: 0.8,
257
			helper: function(e, ui) {
258
				ui.children().each(function() {
259
					$(this).width($(this).width());
260
				});
261
			return ui;
262
			},
263
		});
264
<?php
265
	}
266
?>
267
});
268

    
269
function save_changes_to_xml(xml) {
270
	var ids = $('#mainarea table tbody').sortable('serialize', {key:"ids[]"});
271
	var strloading="<?=gettext('Saving changes...')?>";
272
	if (confirm("<?=gettext("Confirmation Required to save changes.")?>")) {
273
		$.ajax({
274
			type: 'get',
275
			cache: false,
276
			url: "<?=$_SERVER['SCRIPT_NAME']?>",
277
			data: {xml:'<?=$xml?>', act:'update', ids: ids},
278
			beforeSend: function() {
279
				$('#savemsg').empty().html(strloading);
280
			},
281
			error: function(data) {
282
				$('#savemsg').empty().html('Error:' + data);
283
			},
284
			success: function(data) {
285
				$('#savemsg').empty().html(data);
286
			}
287
		});
288
	}
289
}
290

    
291
//]]>
292
</script>
293

    
294
<?php
295
if ($_REQUEST['savemsg'] != "") {
296
	$savemsg = htmlspecialchars($_REQUEST['savemsg']);
297
}
298

    
299
if ($savemsg) {
300
	print_info_box($savemsg, 'success');
301
}
302
?>
303

    
304
<form action="pkg.php" name="pkgform" method="get">
305
	<input type='hidden' name='xml' value='<?=$_REQUEST['xml']?>' />
306
		<div id="mainarea" class="panel panel-default">
307
			<table id="mainarea" class="table table-striped table-hover table-condensed">
308
				<thead>
309
<?php
310
	/* Handle filtering bar A-Z */
311
	$include_filtering_inputbox = false;
312
	$colspan = 0;
313
	if ($pkg['adddeleteeditpagefields']['columnitem'] != "") {
314
		foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
315
			$colspan++;
316
		}
317
	}
318
	if ($pkg['fields']['field']) {
319
		// First find the sorting type field if it exists
320
		foreach ($pkg['fields']['field'] as $field) {
321
			if ($field['type'] == "sorting") {
322
				if (isset($field['include_filtering_inputbox'])) {
323
					$include_filtering_inputbox = true;
324
				}
325
				if ($display_maximum_rows < 1) {
326
					if ($field['display_maximum_rows']) {
327
						$display_maximum_rows = $field['display_maximum_rows'];
328
					}
329
				}
330
				echo "<tr><td colspan='$colspan' class='text-center'>";
331
				echo gettext("Filter by: ");
332
				$isfirst = true;
333
				for ($char = 65; $char < 91; $char++) {
334
					if (!$isfirst) {
335
						echo " | ";
336
					}
337
					echo "<a href=\"#\" onclick=\"setFilter('" . chr($char) . "');\">" . chr($char) . "</a>";
338
					$isfirst = false;
339
				}
340
				echo "</td></tr>";
341
				echo "<tr><td colspan='$colspan' class='text-center'>";
342
				if ($field['sortablefields']) {
343
					echo gettext("Filter field: ") . "<select name='pkg_filter_type'>";
344
					foreach ($field['sortablefields']['item'] as $si) {
345
						if ($si['name'] == $_REQUEST['pkg_filter_type']) {
346
							$SELECTED = "selected";
347
						} else {
348
							$SELECTED = "";
349
						}
350
						echo "<option value='{$si['name']}' {$SELECTED}>{$si['name']}</option>";
351
					}
352
					echo "</select>";
353
				}
354
				if ($include_filtering_inputbox) {
355
					echo '&nbsp;&nbsp;' . gettext("Filter text: ") . '<input id="pkg_filter" name="pkg_filter" value="' . htmlspecialchars($_REQUEST['pkg_filter']) . '" />';
356
					echo '&nbsp;<button type="submit" value="Filter" class="btn btn-primary btn-xs">';
357
					echo '<i class="fa fa-filter icon-embed-btn"></i>';
358
					echo gettext("Filter");
359
					echo "</button>";
360
				}
361
				echo "</td></tr><tr><td><font size='-3'>&nbsp;</font></td></tr>";
362
			}
363
		}
364
	}
365
?>
366
				<tr>
367

    
368
<?php
369
	if ($display_maximum_rows) {
370
		$totalpages = ceil(round((count($evaledvar) / $display_maximum_rows), 9));
371
		$page = 1;
372
		$tmpcount = 0;
373
		$tmppp = 0;
374
		if (is_array($evaledvar)) {
375
			foreach ($evaledvar as $ipa) {
376
				if ($tmpcount == $display_maximum_rows) {
377
					$page++;
378
					$tmpcount = 0;
379
				}
380
				if ($tmppp == $startdisplayingat) {
381
					break;
382
				}
383
				$tmpcount++;
384
				$tmppp++;
385
			}
386
		}
387
		echo "<tr><th colspan='" . count($pkg['adddeleteeditpagefields']['columnitem']) . "'>";
388
		echo "<table width='100%' summary=''>";
389
		echo "<tr>";
390
		echo "<td class='text-left'>" . sprintf(gettext('Displaying page %1$s of %2$s'), $page, $totalpages) . "</b></td>";
391
		echo "<td class='text-right'>" . gettext("Rows per page: ") . "<select onchange='document.pkgform.submit();' name='display_maximum_rows'>";
392
		for ($x = 0; $x < 250; $x++) {
393
			if ($x == $display_maximum_rows) {
394
				$SELECTED = "selected";
395
			} else {
396
				$SELECTED = "";
397
			}
398
			echo "<option value='$x' $SELECTED>$x</option>\n";
399
			$x = $x + 4;
400
		}
401
		echo "</select></td></tr>";
402
		echo "</table>";
403
		echo "</th></tr>";
404
	}
405

    
406
	$cols = 0;
407
	if ($pkg['adddeleteeditpagefields']['columnitem'] != "") {
408
		foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
409
			echo "<th class=\"listhdrr\">" . $column['fielddescr'] . "</th>";
410
			$cols++;
411
		}
412
	}
413
?>
414
				</tr>
415
				</thead>
416
				<tbody>
417
<?php
418
	$i = 0;
419
	$pagination_counter = 0;
420
	if ($evaledvar && is_array($evaledvar)) {
421
		foreach ($evaledvar as $ip) {
422
			if ($startdisplayingat) {
423
				if ($i < $startdisplayingat) {
424
					$i++;
425
					continue;
426
				}
427
			}
428
			if ($_REQUEST['pkg_filter']) {
429
				// Handle filtered items
430
				if ($pkg['fields']['field'] && !$filter_regex) {
431
					// First find the sorting type field if it exists
432
					foreach ($pkg['fields']['field'] as $field) {
433
						if ($field['type'] == "sorting") {
434
							if ($field['sortablefields']['item']) {
435
								foreach ($field['sortablefields']['item'] as $sf) {
436
									if ($sf['name'] == $_REQUEST['pkg_filter_type']) {
437
										$filter_fieldname = $sf['fieldname'];
438
										#Use a default regex on sortable fields when none is declared
439
										$pkg_filter = cleanup_regex_pattern(htmlspecialchars(strip_tags($_REQUEST['pkg_filter'])));
440
										if ($sf['regex']) {
441
											$filter_regex = str_replace("%FILTERTEXT%", $pkg_filter, trim($sf['regex']));
442
										} else {
443
											$filter_regex = "/{$pkg_filter}/i";
444
										}
445
									}
446
								}
447
							}
448
						}
449
					}
450
				}
451
				// Do we have something to filter on?
452
				unset($filter_matches);
453
				if ($pkg['adddeleteeditpagefields']['columnitem'] != "") {
454
					foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
455
						$fieldname = $ip[xml_safe_fieldname($column['fieldname'])];
456
						if ($column['fieldname'] == $filter_fieldname) {
457
							if ($filter_regex) {
458
								preg_match($filter_regex, $fieldname, $filter_matches);
459
								break;
460
							}
461
						}
462
					}
463
				}
464
				if (!$filter_matches) {
465
					$i++;
466
					continue;
467
				}
468
			}
469
			if ($pkg['adddeleteeditpagefields']['movable']) {
470
				echo "<tr style=\"vertical-align: top\" class=\"sortable\" id=\"id_{$i}\">\n";
471
			} else {
472
				echo "<tr style=\"vertical-align: top\">\n";
473
			}
474
			if ($pkg['adddeleteeditpagefields']['columnitem'] != "") {
475
				foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
476
					if ($column['fieldname'] == "description") {
477
						$class = "listbg";
478
					} else {
479
						$class = "listlr";
480
					}
481
?>
482
					<td class="<?=$class?>" ondblclick="document.location='pkg_edit.php?xml=<?=$xml?>&amp;act=edit&amp;id=<?=$i?>';">
483
<?php
484
					$fieldname = $ip[xml_safe_fieldname($column['fieldname'])];
485
					#Check if columnitem has a type field declared
486
					if ($column['type'] == "checkbox") {
487
						if ($fieldname == "") {
488
							echo gettext("No");
489
						} else {
490
							echo gettext("Yes");
491
						}
492
					} else if ($column['type'] == "interface") {
493
						echo $column['prefix'] . $iflist[$fieldname] . $column['suffix'];
494
					} else {
495
						$display_text = "";
496
						#Check if columnitem has an encoding field declared
497
						if ($column['encoding'] == "base64") {
498
							$display_text = $column['prefix'] . base64_decode($fieldname) . $column['suffix'];
499
						#Check if there is a custom info to show when $fieldname is not empty
500
						} else if ($column['listmodeon'] && $fieldname != "") {
501
							$display_text = $column['prefix'] . gettext($column['listmodeon']). $column['suffix'];
502
						#Check if there is a custom info to show when $fieldname is empty
503
						} else if ($column['listmodeoff'] && $fieldname == "") {
504
							$display_text = $column['prefix'] .gettext($column['listmodeoff']). $column['suffix'];
505
						} else {
506
							$display_text = $column['prefix'] . $fieldname ." ". $column['suffix'];
507
						}
508
						if (!isset($column['allow_html'])) {
509
							$display_text = htmlspecialchars($display_text);
510
						}
511
						echo $display_text;
512
					}
513
?>
514
					</td>
515
<?php
516
				} // foreach columnitem
517
			} // if columnitem
518
?>
519
					<td style="vertical-align: middle" class="list text-nowrap">
520
						<table border="0" cellspacing="0" cellpadding="1" summary="icons">
521
							<tr>
522
<?php
523
			#Show custom description to edit button if defined
524
			$edit_msg=($pkg['adddeleteeditpagefields']['edittext']?$pkg['adddeleteeditpagefields']['edittext']:gettext("Edit this item"));
525
?>
526
								<td><a class="fa fa-pencil" href="pkg_edit.php?xml=<?=$xml?>&amp;act=edit&amp;id=<?=$i?>" title="<?=$edit_msg?>"></a></td>
527
<?php
528
			#Show custom description to delete button if defined
529
			$delete_msg=($pkg['adddeleteeditpagefields']['deletetext']?$pkg['adddeleteeditpagefields']['deletetext']:gettext("Delete this item"));
530
?>
531
								<td>&nbsp;<a class="fa fa-trash" href="pkg.php?xml=<?=$xml?>&amp;act=del&amp;id=<?=$i?>" title="<?=$delete_msg?>"></a></td>
532
							</tr>
533
						</tbody>
534
					</table>
535
				</td>
536
<?php
537
			echo "</tr>\n"; // Pairs with an echo tr some way above
538
			// Handle pagination and display_maximum_rows
539
			if ($display_maximum_rows) {
540
				if ($pagination_counter == ($display_maximum_rows-1) or
541
					$i == (count($evaledvar)-1)) {
542
					$colcount = count($pkg['adddeleteeditpagefields']['columnitem']);
543
					$final_footer = "";
544
					$final_footer .= "<tr><td colspan='$colcount'>";
545
					$final_footer .= "<table width='100%' summary=''><tr>";
546
					$final_footer .= "<td class='text-left'>";
547
					$startingat = $startdisplayingat - $display_maximum_rows;
548
					if ($startingat > -1) {
549
						$final_footer .= "<a href='pkg.php?xml=" . $_REQUEST['xml'] . "&amp;startdisplayingat={$startingat}&amp;display_maximum_rows={$display_maximum_rows}'>";
550
					} else if ($startdisplayingat > 1) {
551
						$final_footer .= "<a href='pkg.php?xml=" . $_REQUEST['xml'] . "&amp;startdisplayingat=0&amp;display_maximum_rows={$display_maximum_rows}'>";
552
					}
553
					$final_footer .= "<font size='2'><< " . gettext("Previous page") . "</font></a>";
554
					if ($tmppp + $display_maximum_rows > count($evaledvar)) {
555
						$endingrecord = count($evaledvar);
556
					} else {
557
						$endingrecord = $tmppp + $display_maximum_rows;
558
					}
559
					$final_footer .= "</td><td class='text-center'>";
560
					$tmppp++;
561
					$final_footer .= "<font size='2'>Displaying {$tmppp} - {$endingrecord} / " . count($evaledvar) . " records";
562
					$final_footer .= "</font></td><td class='text-right'>&nbsp;";
563
					if (($i+1) < count($evaledvar)) {
564
						$final_footer .= "<a href='pkg.php?xml=" . $_REQUEST['xml'] . "&amp;startdisplayingat=" . ($startdisplayingat + $display_maximum_rows) . "&amp;display_maximum_rows={$display_maximum_rows}'>";
565
					}
566
					$final_footer .= "<font size='2'>" . gettext("Next page") . " >></font></a>";
567
					$final_footer .= "</td></tr></table></td></tr>";
568
					$i = count($evaledvar);
569
					break;
570
				}
571
			}
572
			$i++;
573
			$pagination_counter++;
574
		} // foreach evaledvar
575
	} // if evaledvar
576
?>
577
				<tr>
578
					<td colspan="<?=$cols?>"></td>
579
					<td>
580
						<table border="0" cellspacing="0" cellpadding="1" summary="icons">
581
							<tr>
582
<?php
583
	#Show custom description to add button if defined
584
	$add_msg=($pkg['adddeleteeditpagefields']['addtext']?$pkg['adddeleteeditpagefields']['addtext']:gettext("Add a new item"));
585
?>
586
								<td><a href="pkg_edit.php?xml=<?=$xml?>&amp;id=<?=$i?>" class="btn btn-sm btn-success" title="<?=$add_msg?>"><i class="fa fa-plus icon-embed-btn"></i><?=gettext('Add')?></a></td>
587
<?php
588
	#Show description button and info if defined
589
	if ($pkg['adddeleteeditpagefields']['description']) {
590
?>
591
								<td>
592
									<i class="fa fa-info-circle"><?=$pkg['adddeleteeditpagefields']['description']?></i>
593
								</td>
594
<?php
595
	}
596
?>
597
							</tr>
598
						</table>
599
					</td>
600
				</tr>
601
				<?=$final_footer?>
602
			</table>
603
			</div>
604
		<button class="btn btn-primary" type="button" value="Save" name="Submit" onclick="save_changes_to_xml('<?=$xml?>')"><i class="fa fa-save icon-embed-btn"></i><?=gettext("Save")?></button>
605

    
606
</form>
607
<?php
608
include("foot.inc"); ?>
(94-94/228)