PageRenderTime 70ms CodeModel.GetById 10ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 1ms

/links/edit.php

https://github.com/altatof/altashop
PHP | 574 lines | 292 code | 92 blank | 190 comment | 93 complexity | dc5365707dff056fcebae4704799068b MD5 | raw file
  1<?php
  2/**
  3 * post a new link or update an existing one
  4 *
  5 * This is the main script used to post a new link, or to modify an existing one.
  6 *
  7 * Each link may have a label, a hovering title, a target window, and a full description.
  8 *
  9 * Shortcuts for internal links are accepted. This script will automatically translate:
 10 * - a link to an article &#91;article=123]
 11 * - a link to a section &#91;section=123]
 12 * - a link to a file &#91;file=123]
 13 * - a link to an image &#91;image=123]
 14 * - a link to a category &#91;category=123]
 15 * - a link to a user &#91;user=jean]
 16 *
 17 * This script ensures that links stored in the database may be easily classified as being
 18 * external or internal.
 19 * This is achieved by applying following transformations to submitted links:
 20 * - 'www.foo.bar' becomes 'http://www.foo.bar/'
 21 * - 'http://this_server/path/target' becomes '/path/target'
 22 * - 'anything@foo.bar' becomes 'mailto:anything@foo.bar'
 23 *
 24 * Also, an external link may be displayed in a separate window, or in the current window.
 25 *
 26 * On anonymous usage YACS attempts to stop robots by generating a random string and by asking user to type it.
 27 *
 28 * When a new link is posted:
 29 * - the edition date of the link is updated
 30 * - the user profile is incremented
 31 * - the edition field of the anchor page is modified with the value 'link:create' (cf. shared/anchor.php)
 32 *
 33 * When a link is updated:
 34 * - the edition date of the link is updated
 35 * - the edition field of the anchor page is modified with the value 'link:update' (cf. shared/anchor.php)
 36 *
 37 * Associates can also decide to not stamp the creation or the modification of the link.
 38 * If the silent option is checked:
 39 * - the previous edition date is retained
 40 * - the anchor page is not modified either
 41 *
 42 * A button-based editor is used for the description field.
 43 * It's aiming to introduce most common [link=codes]codes/index.php[/link] supported by YACS.
 44 *
 45 * This script attempts to validate the new or updated article description against a standard PHP XML parser.
 46 * The objective is to spot malformed or unordered HTML and XHTML tags. No more, no less.
 47 *
 48 * The permission assessment is based upon following rules applied in the provided orders:
 49 * - associates and editors are allowed to move forward
 50 * - permission is denied if the anchor is not viewable
 51 * - surfer owns the image (= he is the last editor)
 52 * - this is a new post and the surfer is an authenticated member and submissions are allowed
 53 * - permission denied is the default
 54 *
 55 * Anonymous (not-logged) surfer are invited to register to be able to post new images.
 56 *
 57 * Accepted calls:
 58 * - edit.php create a new link,	start by selecting an anchor
 59 * - edit.php/&lt;type&gt;/&lt;id&gt;	create a new link for this anchor
 60 * - edit.php?anchor=&lt;type&gt;:&lt;id&gt;	upload a new link for the anchor
 61 * - edit.php/&lt;id&gt;	modify an existing link
 62 * - edit.php?id=&lt;id&gt; modify an existing link
 63 *
 64 * If no anchor data is provided, a list of anchors is proposed to let the surfer select one of them.
 65 *
 66 * There is also a special invocation format to be used for direct bookmarking from bookmarklets,
 67 * such as the one provided by YACS.
 68 *
 69 * @see categories/view.php
 70 *
 71 * This format is aiming to provide to YACS every necessary parameters, but through a single GET or POST call.
 72 * Following parameters have to be provided:
 73 * - [code]account[/code] - the nickname to log in
 74 * - [code]password[/code] - the related password
 75 * - [code]link[/code] - the target href
 76 * - [code]category[/code] - the optional id of the category where the new link will be placed
 77 * - [code]title[/code] - an optional title for the new link
 78 * - [code]text[/code] - some optional text to document the link
 79 *
 80 * If no authentication data is provided ([code]account[/code] and [code]password[/code]),
 81 * the surfer is redirected to the login page at [script]users/login.php[/script].
 82 *
 83 * Data submitted from a bookmarklet is saved as session data.
 84 * Therefore information is preserved through any additional steps,
 85 * including user authentication and category selection.
 86 *
 87 * Also, session data is purged on successful article post.
 88 *
 89 * This design allows for a generic bookmarklet bound only to the web site.
 90 *
 91 * The generic bookmarking bookmarklet is proposed as a direct link to any authenticated member:
 92 * - at the control panel ([script]control/index.php[/script])
 93 * - at any user page ([script]users/view.php[/script])
 94 * - at the main help page ([script]help/index.php[/script])
 95 *
 96 * If the anchor for this item specifies a specific skin (option keyword '[code]skin_xyz[/code]'),
 97 * or a specific variant (option keyword '[code]variant_xyz[/code]'), they are used instead default values.
 98 *
 99 * @see control/index.php
100 * @see users/view.php
101 * @see help/index.php
102 *
103 * @author Bernard Paques
104 * @author Vincent No&euml;l
105 * @author GnapZ
106 * @author Christophe Battarel [email]christophe.battarel@altairis.fr[/email]
107 * @tester Ghjmora
108 * @tester GnapZ
109 * @tester Cyandrea
110 * @tester Lucrecius
111 * @reference
112 * @license http://www.gnu.org/copyleft/lesser.txt GNU Lesser General Public License
113 */
114
115// common definitions and initial processing
116include_once '../shared/global.php';
117include_once '../shared/xml.php';	// input validation
118include_once 'links.php';
119
120// allow for direct login
121if(isset($_REQUEST['account']) && isset($_REQUEST['password'])) {
122
123	// authenticate the surfer and start a session
124	if($user = Users::login($_REQUEST['account'], $_REQUEST['password']))
125		Surfer::set($user);
126
127}
128
129// look for the id
130$id = NULL;
131if(isset($_REQUEST['id']))
132	$id = $_REQUEST['id'];
133elseif(isset($context['arguments'][0]) && !isset($context['arguments'][1]))
134	$id = $context['arguments'][0];
135$id = strip_tags($id);
136
137// get the item from the database
138$item =& Links::get($id);
139
140// get the related anchor, if any
141$anchor = NULL;
142if(isset($_REQUEST['anchor']))
143	$anchor =& Anchors::get($_REQUEST['anchor']);
144elseif(isset($_REQUEST['category']) && $_REQUEST['category'])
145	$anchor =& Anchors::get('category:'.$_REQUEST['category']);
146elseif(isset($_REQUEST['section']) && $_REQUEST['section'])
147	$anchor =& Anchors::get('section:'.$_REQUEST['section']);
148elseif(isset($context['arguments'][1]))
149	$anchor =& Anchors::get($context['arguments'][0].':'.$context['arguments'][1]);
150elseif(isset($item['anchor']))
151	$anchor =& Anchors::get($item['anchor']);
152
153// anchor owners can do what they want
154if(is_object($anchor) && $anchor->is_owned()) {
155	Surfer::empower();
156	$permitted = TRUE;
157
158// editors can move forward
159} elseif(!isset($item['id']) && Links::allow_creation($anchor, $item))
160	$permitted = TRUE;
161
162// the anchor has to be viewable by this surfer
163elseif(is_object($anchor) && !$anchor->is_viewable())
164	$permitted = FALSE;
165
166// surfer owns the item
167elseif(isset($item['edit_id']) && Surfer::is($item['edit_id']))
168	$permitted = TRUE;
169
170// the default is to disallow access
171else
172	$permitted = FALSE;
173
174// do not always show the edition form
175$with_form = FALSE;
176
177// load the skin, maybe with a variant
178load_skin('links', $anchor);
179
180// clear the tab we are in, if any
181if(is_object($anchor))
182	$context['current_focus'] = $anchor->get_focus();
183
184// the path to this page
185if(is_object($anchor) && $anchor->is_viewable())
186	$context['path_bar'] = $anchor->get_path_bar();
187else
188	$context['path_bar'] = array( 'links/' => i18n::s('Links') );
189
190// the title of the page
191if($item['id'])
192	$context['page_title'] = i18n::s('Edit a link');
193else
194	$context['page_title'] = i18n::s('Add a link');
195
196// save data in session, if any, to pass through login step or through anchor selection step
197if(!Surfer::is_logged() || !is_object($anchor)) {
198	if(isset($_REQUEST['anchor']) && $_REQUEST['anchor'])
199		$_SESSION['anchor_reference'] = $_REQUEST['anchor'];
200	elseif(isset($_REQUEST['category']) && $_REQUEST['category'])
201		$_SESSION['anchor_reference'] = 'category:'.$_REQUEST['category'];
202	elseif(isset($_REQUEST['section']) && $_REQUEST['section'])
203		$_SESSION['anchor_reference'] = 'section:'.$_REQUEST['section'];
204
205	if(isset($_REQUEST['link']) && $_REQUEST['link'])
206		$_SESSION['pasted_link'] = utf8::encode($_REQUEST['link']);
207
208	if(isset($_REQUEST['title']) && $_REQUEST['title'])
209		$_SESSION['pasted_title'] = utf8::encode($_REQUEST['title']);
210
211	if(isset($_REQUEST['text']) && $_REQUEST['text'])
212		$_SESSION['pasted_text'] = utf8::encode($_REQUEST['text']);
213}
214
215// validate input syntax only if required
216if(isset($_REQUEST['option_validate']) && ($_REQUEST['option_validate'] == 'Y')) {
217	if(isset($_REQUEST['description']))
218		xml::validate($_REQUEST['description']);
219}
220
221// stop crawlers
222if(Surfer::is_crawler()) {
223	Safe::header('Status: 401 Unauthorized', TRUE, 401);
224	Logger::error(i18n::s('You are not allowed to perform this operation.'));
225
226// permission denied
227} elseif(!$permitted) {
228
229	// anonymous users are invited to log in or to register
230	if(!Surfer::is_logged()) {
231
232		if(isset($item['id']))
233			$link = Links::get_url($item['id'], 'edit');
234		elseif(isset($_REQUEST['anchor']))
235			$link = 'links/edit.php?anchor='.urlencode($_REQUEST['anchor']);
236		else
237			$link = 'links/edit.php';
238
239		Safe::redirect($context['url_to_home'].$context['url_to_root'].'users/login.php?url='.urlencode($link));
240	}
241
242	// permission denied to authenticated user
243	Safe::header('Status: 401 Unauthorized', TRUE, 401);
244	Logger::error(i18n::s('You are not allowed to perform this operation.'));
245
246// an error occured
247} elseif(count($context['error'])) {
248	$item = $_REQUEST;
249	$with_form = TRUE;
250
251// process uploaded data
252} elseif(isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'POST')) {
253
254	// track anonymous surfers
255	Surfer::track($_REQUEST);
256
257	// we have to translate this link to an internal link
258	if(($attributes = Links::transform_reference($_REQUEST['link_url'])) && $attributes[0]) {
259
260		$_REQUEST['link_url'] = $attributes[0];
261		if(!$_REQUEST['title'] && $attributes[1])
262			$_REQUEST['title'] = $attributes[1];
263		if(!$_REQUEST['description'] && $attributes[2])
264			$_REQUEST['description'] = $attributes[2];
265
266	// rewrite links if necessary
267	} else {
268		$from = array(
269			'/^www\.([^\W\.]+?)\.([^\W]+?)/i',
270			'/^(http:|https:)\/\/('.preg_quote($context['host_name'], '/').'|'.$_SERVER['SERVER_ADDR'].')(.+)/i',
271			'/^(http:|https:)\/\/('.preg_quote($context['host_name'], '/').'|'.$_SERVER['SERVER_ADDR'].')$/i',
272			'/^([^:]+?)@([^\W\.]+?)\.([^\W]+?)/i'
273			);
274
275		$to = array(
276			'http://\\0',
277			'\\3',
278			'/',
279			'mailto:\\0'
280			);
281
282		$_REQUEST['link_url'] = preg_replace($from, $to, $_REQUEST['link_url']);
283	}
284
285	// an anchor is mandatory
286	if(!is_object($anchor)) {
287		Logger::error(i18n::s('No anchor has been found.'));
288		$item = $_REQUEST;
289		$with_form = TRUE;
290
291	// stop robots
292	} elseif(Surfer::may_be_a_robot()) {
293		Logger::error(i18n::s('Please prove you are not a robot.'));
294		$item = $_REQUEST;
295		$with_form = TRUE;
296
297	// reward the poster for new posts
298	} elseif(!isset($item['id'])) {
299
300		// display the form on error
301		if(!$_REQUEST['id'] = Links::post($_REQUEST)) {
302			$item = $_REQUEST;
303			$with_form = TRUE;
304
305		// follow-up
306		} else {
307
308			// touch the related anchor
309			$anchor->touch('link:create', $_REQUEST['id'], isset($_REQUEST['silent']) && ($_REQUEST['silent'] == 'Y'), TRUE, TRUE);
310
311			// clear cache
312			Links::clear($_REQUEST);
313
314			// increment the post counter of the surfer
315			Users::increment_posts(Surfer::get_id());
316
317			// thanks
318			$context['page_title'] = i18n::s('Thank you for your contribution');
319
320			// the action
321			$context['text'] .= '<p>'.i18n::s('The link has been successfully recorded.').'</p>';
322
323			// list persons that have been notified
324			$context['text'] .= Mailer::build_recipients(i18n::s('Persons that have been notified'));
325
326			// follow-up commands
327			$follow_up = i18n::s('What do you want to do now?');
328			$menu = array();
329			if(is_object($anchor)) {
330				$menu = array_merge($menu, array($anchor->get_url('links') => i18n::s('View the page')));
331				$menu = array_merge($menu, array('links/edit.php?anchor='.$anchor->get_reference() => i18n::s('Submit another link')));
332			}
333			$follow_up .= Skin::build_list($menu, 'menu_bar');
334			$context['text'] .= Skin::build_block($follow_up, 'bottom');
335
336			// log the submission of a new link by a non-associate
337			if(!Surfer::is_associate() && is_object($anchor)) {
338				$label = sprintf(i18n::c('New link at %s'), strip_tags($anchor->get_title()));
339                                $link = $context['url_to_home'].$context['url_to_root'].$anchor->get_url().'#links';
340				$description = $_REQUEST['link_url']."\n"
341					.sprintf(i18n::c('at %s'),'<a href="'.$link.'">'.$link.'</a>');
342				Logger::notify('links/edit.php', $label, $description);
343			}
344		}
345
346	// update an existing link
347	} else {
348
349		// display the form on error
350		if(!Links::put($_REQUEST)) {
351			$item = $_REQUEST;
352			$with_form = TRUE;
353
354		// follow-up
355		} else {
356
357			// touch the related anchor
358			$anchor->touch('link:update', $_REQUEST['id'], isset($_REQUEST['silent']) && ($_REQUEST['silent'] == 'Y'));
359
360			// clear cache
361			Links::clear($_REQUEST);
362
363			// forward to the updated anchor page
364			Safe::redirect($context['url_to_home'].$context['url_to_root'].$anchor->get_url().'#links');
365		}
366	}
367
368// display the form on GET
369} else
370	$with_form = TRUE;
371
372// display the form
373if($with_form) {
374
375	// the form to edit a link
376	$context['text'] .= '<form method="post" action="'.$context['script_url'].'" onsubmit="return validateDocumentPost(this)" id="main_form"><div>';
377
378	// the category, for direct uploads
379	if(!$anchor) {
380
381		// a splash message for new users
382		$context['text'] .= Skin::build_block(i18n::s('This script will add this page to one of the sections listed below. If you would like to add a link to an existing page, browse the target page instead and use the adequate command from the menu.'), 'caution')."\n";
383
384		$label = i18n::s('Section');
385		$input = '<select name="anchor">'.Sections::get_options(NULL, 'bookmarks').'</select>';
386		$hint = i18n::s('Please carefully select a section for your link');
387		$fields[] = array($label, $input, $hint);
388
389	// allow for section change
390	} elseif($item['id'] && preg_match('/section:/', $current = $anchor->get_reference())) {
391
392		$label = i18n::s('Section');
393		$input = '<select name="anchor">'.Sections::get_options($current, NULL).'</select>';
394		$hint = i18n::s('Please carefully select a section for your link');
395		$fields[] = array($label, $input, $hint);
396
397	// else preserve the previous anchor
398	} elseif(is_object($anchor))
399		$context['text'] .= '<input type="hidden" name="anchor" value="'.$anchor->get_reference().'" />';
400
401
402	// additional fields for anonymous surfers
403	if(!isset($item['id']) && !Surfer::is_logged()) {
404
405		// splash
406		if(isset($item['id'])) {
407			if(is_object($anchor))
408				$login_url = $context['url_to_root'].'users/login.php?url='.urlencode('links/edit.php?id='.$item['id'].'&anchor='.$anchor->get_reference());
409			else
410				$login_url = $context['url_to_root'].'users/login.php?url='.urlencode('links/edit.php?id='.$item['id']);
411		} else {
412			if(is_object($anchor))
413				$login_url = $context['url_to_root'].'users/login.php?url='.urlencode('links/edit.php?anchor='.$anchor->get_reference());
414			else
415				$login_url = $context['url_to_root'].'users/login.php?url='.urlencode('links/edit.php');
416		}
417		$context['text'] .= '<p>'.sprintf(i18n::s('If you have previously registered to this site, please %s. Then the server will automatically put your name and address in following fields.'), Skin::build_link($login_url, i18n::s('authenticate')))."</p>\n";
418
419		// the name, if any
420		$label = i18n::s('Your name');
421		$input = '<input type="text" name="edit_name" size="45" maxlength="128" accesskey="n" value="'.encode_field(isset($_REQUEST['edit_name']) ? $_REQUEST['edit_name'] : Surfer::get_name(' ')).'" />';
422		$hint = i18n::s('This optional field can be left blank if you wish.');
423		$fields[] = array($label, $input, $hint);
424
425		// the address, if any
426		$label = i18n::s('Your address');
427		$input = '<input type="text" name="edit_address" size="45" maxlength="128" accesskey="a" value="'.encode_field(isset($_REQUEST['edit_address']) ? $_REQUEST['edit_address'] : Surfer::get_email_address()).'" />';
428		$hint = i18n::s('e-mail or web address; this field is optional');
429		$fields[] = array($label, $input, $hint);
430
431		// stop robots
432		if($field = Surfer::get_robot_stopper())
433			$fields[] = $field;
434
435	}
436
437	// the link url
438	$label = i18n::s('Web address');
439	$value = '';
440	if(isset($item['link_url']) && $item['link_url'])
441		$value = $item['link_url'];
442	elseif(isset($_REQUEST['link']))
443		$value = $_REQUEST['link'];
444	elseif(isset($_SESSION['pasted_link']))
445		$value = $_SESSION['pasted_link'];
446	$input = '<input type="text" name="link_url" id="link_url" size="55" value="'.encode_field($value).'" maxlength="255" accesskey="a" />';
447	$hint = i18n::s('You can either type a plain url (http://) or use [article=&lt;id&gt;] notation');
448	$fields[] = array($label, $input, $hint);
449
450	// the title
451	$label = i18n::s('Title');
452	$value = '';
453	if(isset($item['title']) && $item['title'])
454		$value = $item['title'];
455	elseif(isset($_REQUEST['title']))
456		$value = $_REQUEST['title'];
457	elseif(isset($_SESSION['pasted_title']))
458		$value = $_SESSION['pasted_title'];
459	$input = '<input name="title" value="'.encode_field($value).'" size="55" maxlength="255" />';
460	$hint = i18n::s('Please provide a meaningful title.');
461	$fields[] = array($label, $input, $hint);
462
463	// the hovering title
464	$label = i18n::s('Hovering popup');
465	$value = '';
466	if(isset($item['link_title']) && $item['link_title'])
467		$value = $item['link_title'];
468	$input = '<input name="link_title" value="'.encode_field($value).'" size="55" maxlength="255" />';
469	$hint = i18n::s('This will appear near the link when the mouse is placed on top of it');
470	$fields[] = array($label, $input, $hint);
471
472	// the target flag: Inside the existing window or Blank window
473	$label = i18n::s('Target window');
474	$input = '<input type="radio" name="link_target" value="B"';
475	if(!isset($item['link_target']) || ($item['link_target'] != 'I'))
476		$input .= ' checked="checked"';
477	$input .= '/> '.i18n::s('Open a separate window for external links')
478		.BR.'<input type="radio" name="link_target" value="I"';
479	if(isset($item['link_target']) && ($item['link_target'] == 'I'))
480		$input .= ' checked="checked"';
481	$input .= '/> '.i18n::s('Stay in same window on click')."\n";
482	$fields[] = array($label, $input);
483
484	// the description
485	$label = i18n::s('Description');
486
487	// use the editor if possible
488	$value = '';
489	if(isset($item['description']) && $item['description'])
490		$value = $item['description'];
491	elseif(isset($_REQUEST['text']))
492		$value = $_REQUEST['text'];
493	elseif(isset($_SESSION['pasted_text']))
494		$value = $_SESSION['pasted_text'];
495	$input = Surfer::get_editor('description', $value);
496	$fields[] = array($label, $input);
497
498	// build the form
499	$context['text'] .= Skin::build_form($fields);
500
501	// bottom commands
502	$menu = array();
503	$menu[] = Skin::build_submit_button(i18n::s('Submit'), i18n::s('Press [s] to submit data'), 's');
504	if(is_object($anchor) && $anchor->is_viewable())
505		$menu[] = Skin::build_link($anchor->get_url(), i18n::s('Cancel'), 'span');
506	$context['text'] .= Skin::finalize_list($menu, 'assistant_bar');
507
508	// associates may decide to not stamp changes -- complex command
509	if(Surfer::is_associate() && Surfer::has_all())
510		$context['text'] .= '<p><input type="checkbox" name="silent" value="Y" /> '.i18n::s('Do not change modification date of the main page.').'</p>';
511
512	// validate page content
513	$context['text'] .= '<p><input type="checkbox" name="option_validate" value="Y" checked="checked" /> '.i18n::s('Ensure this post is valid XHTML.').'</p>';
514
515	// transmit the id as a hidden field
516	if(isset($item['id']) && $item['id'])
517		$context['text'] .= '<input type="hidden" name="id" value="'.$item['id'].'" />';
518
519	// end of the form
520	$context['text'] .= '</div></form>';
521
522	// the script used for form handling at the browser
523	$context['text'] .= JS_PREFIX
524		.'	// check that main fields are not empty'."\n"
525		.'	func'.'tion validateDocumentPost(container) {'."\n"
526		."\n"
527		.'		// link_url is mandatory'."\n"
528		.'		if(!container.link_url.value) {'."\n"
529		.'			alert("'.i18n::s('Please type a valid link.').'");'."\n"
530		.'			Yacs.stopWorking();'."\n"
531		.'			return false;'."\n"
532		.'		}'."\n"
533		."\n"
534		.'		// successful check'."\n"
535		.'		return true;'."\n"
536		.'	}'."\n"
537		."\n"
538		.'// set the focus on first form field'."\n"
539		.'$("link_url").focus();'."\n"
540		.JS_SUFFIX."\n";
541
542	// clear session data now we have populated the form
543	unset($_SESSION['pasted_link']);
544	unset($_SESSION['anchor_reference']);
545	unset($_SESSION['pasted_text']);
546	unset($_SESSION['pasted_title']);
547
548	// details
549	$details = array();
550
551	// last edition
552	if(isset($item['edit_name']) && $item['edit_name'])
553		$details[] = sprintf(i18n::s('edited by %s %s'), Users::get_link($item['edit_name'], $item['edit_address'], $item['edit_id']), Skin::build_date($item['edit_date']));
554
555	// hits
556	if(isset($item['hits']) && ($item['hits'] > 1))
557		$details[] = Skin::build_number($item['hits'], i18n::s('clicks'));
558
559	// all details
560	if(@count($details))
561		$context['page_details'] .= '<p class="details">'.ucfirst(implode(', ', $details))."</p>\n";
562
563	// general help on this form
564	$help = '<p>'.sprintf(i18n::s('You can use following shortcuts to link to other pages of this server: %s'), '&#91;article=&lt;id>] &#91;section=&lt;id>] &#91;category=&lt;id>]').'</p>'
565		.'<p>'.i18n::s('Please set a meaningful title to be used instead of the link itself.').'</p>'
566		.'<p>'.i18n::s('Also, take the time to describe the link. This field is fully indexed for searches.').'</p>'
567		.'<p>'.sprintf(i18n::s('%s and %s are available to enhance text rendering.'), Skin::build_link('codes/', i18n::s('YACS codes'), 'help'), Skin::build_link('smileys/', i18n::s('smileys'), 'help')).'</p>';
568	$context['components']['boxes'] = Skin::build_box(i18n::s('Help'), $help, 'boxes', 'help');
569
570}
571
572// render the skin
573render_skin();
574?>