PageRenderTime 57ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/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. // common definitions and initial processing
  115. include_once '../shared/global.php';
  116. include_once '../shared/xml.php'; // input validation
  117. include_once 'links.php';
  118. // allow for direct login
  119. if(isset($_REQUEST['account']) && isset($_REQUEST['password'])) {
  120. // authenticate the surfer and start a session
  121. if($user = Users::login($_REQUEST['account'], $_REQUEST['password']))
  122. Surfer::set($user);
  123. }
  124. // look for the id
  125. $id = NULL;
  126. if(isset($_REQUEST['id']))
  127. $id = $_REQUEST['id'];
  128. elseif(isset($context['arguments'][0]) && !isset($context['arguments'][1]))
  129. $id = $context['arguments'][0];
  130. $id = strip_tags($id);
  131. // get the item from the database
  132. $item =& Links::get($id);
  133. // get the related anchor, if any
  134. $anchor = NULL;
  135. if(isset($_REQUEST['anchor']))
  136. $anchor =& Anchors::get($_REQUEST['anchor']);
  137. elseif(isset($_REQUEST['category']) && $_REQUEST['category'])
  138. $anchor =& Anchors::get('category:'.$_REQUEST['category']);
  139. elseif(isset($_REQUEST['section']) && $_REQUEST['section'])
  140. $anchor =& Anchors::get('section:'.$_REQUEST['section']);
  141. elseif(isset($context['arguments'][1]))
  142. $anchor =& Anchors::get($context['arguments'][0].':'.$context['arguments'][1]);
  143. elseif(isset($item['anchor']))
  144. $anchor =& Anchors::get($item['anchor']);
  145. // anchor owners can do what they want
  146. if(is_object($anchor) && $anchor->is_owned()) {
  147. Surfer::empower();
  148. $permitted = TRUE;
  149. // editors can move forward
  150. } elseif(!isset($item['id']) && Links::allow_creation($anchor, $item))
  151. $permitted = TRUE;
  152. // the anchor has to be viewable by this surfer
  153. elseif(is_object($anchor) && !$anchor->is_viewable())
  154. $permitted = FALSE;
  155. // surfer owns the item
  156. elseif(isset($item['edit_id']) && Surfer::is($item['edit_id']))
  157. $permitted = TRUE;
  158. // the default is to disallow access
  159. else
  160. $permitted = FALSE;
  161. // do not always show the edition form
  162. $with_form = FALSE;
  163. // load the skin, maybe with a variant
  164. load_skin('links', $anchor);
  165. // clear the tab we are in, if any
  166. if(is_object($anchor))
  167. $context['current_focus'] = $anchor->get_focus();
  168. // the path to this page
  169. if(is_object($anchor) && $anchor->is_viewable())
  170. $context['path_bar'] = $anchor->get_path_bar();
  171. else
  172. $context['path_bar'] = array( 'links/' => i18n::s('Links') );
  173. // the title of the page
  174. if($item['id'])
  175. $context['page_title'] = i18n::s('Edit a link');
  176. else
  177. $context['page_title'] = i18n::s('Add a link');
  178. // save data in session, if any, to pass through login step or through anchor selection step
  179. if(!Surfer::is_logged() || !is_object($anchor)) {
  180. if(isset($_REQUEST['anchor']) && $_REQUEST['anchor'])
  181. $_SESSION['anchor_reference'] = $_REQUEST['anchor'];
  182. elseif(isset($_REQUEST['category']) && $_REQUEST['category'])
  183. $_SESSION['anchor_reference'] = 'category:'.$_REQUEST['category'];
  184. elseif(isset($_REQUEST['section']) && $_REQUEST['section'])
  185. $_SESSION['anchor_reference'] = 'section:'.$_REQUEST['section'];
  186. if(isset($_REQUEST['link']) && $_REQUEST['link'])
  187. $_SESSION['pasted_link'] = utf8::encode($_REQUEST['link']);
  188. if(isset($_REQUEST['title']) && $_REQUEST['title'])
  189. $_SESSION['pasted_title'] = utf8::encode($_REQUEST['title']);
  190. if(isset($_REQUEST['text']) && $_REQUEST['text'])
  191. $_SESSION['pasted_text'] = utf8::encode($_REQUEST['text']);
  192. }
  193. // validate input syntax only if required
  194. if(isset($_REQUEST['option_validate']) && ($_REQUEST['option_validate'] == 'Y')) {
  195. if(isset($_REQUEST['description']))
  196. xml::validate($_REQUEST['description']);
  197. }
  198. // stop crawlers
  199. if(Surfer::is_crawler()) {
  200. Safe::header('Status: 401 Unauthorized', TRUE, 401);
  201. Logger::error(i18n::s('You are not allowed to perform this operation.'));
  202. // permission denied
  203. } elseif(!$permitted) {
  204. // anonymous users are invited to log in or to register
  205. if(!Surfer::is_logged()) {
  206. if(isset($item['id']))
  207. $link = Links::get_url($item['id'], 'edit');
  208. elseif(isset($_REQUEST['anchor']))
  209. $link = 'links/edit.php?anchor='.urlencode($_REQUEST['anchor']);
  210. else
  211. $link = 'links/edit.php';
  212. Safe::redirect($context['url_to_home'].$context['url_to_root'].'users/login.php?url='.urlencode($link));
  213. }
  214. // permission denied to authenticated user
  215. Safe::header('Status: 401 Unauthorized', TRUE, 401);
  216. Logger::error(i18n::s('You are not allowed to perform this operation.'));
  217. // an error occured
  218. } elseif(count($context['error'])) {
  219. $item = $_REQUEST;
  220. $with_form = TRUE;
  221. // process uploaded data
  222. } elseif(isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'POST')) {
  223. // track anonymous surfers
  224. Surfer::track($_REQUEST);
  225. // we have to translate this link to an internal link
  226. if(($attributes = Links::transform_reference($_REQUEST['link_url'])) && $attributes[0]) {
  227. $_REQUEST['link_url'] = $attributes[0];
  228. if(!$_REQUEST['title'] && $attributes[1])
  229. $_REQUEST['title'] = $attributes[1];
  230. if(!$_REQUEST['description'] && $attributes[2])
  231. $_REQUEST['description'] = $attributes[2];
  232. // rewrite links if necessary
  233. } else {
  234. $from = array(
  235. '/^www\.([^\W\.]+?)\.([^\W]+?)/i',
  236. '/^(http:|https:)\/\/('.preg_quote($context['host_name'], '/').'|'.$_SERVER['SERVER_ADDR'].')(.+)/i',
  237. '/^(http:|https:)\/\/('.preg_quote($context['host_name'], '/').'|'.$_SERVER['SERVER_ADDR'].')$/i',
  238. '/^([^:]+?)@([^\W\.]+?)\.([^\W]+?)/i'
  239. );
  240. $to = array(
  241. 'http://\\0',
  242. '\\3',
  243. '/',
  244. 'mailto:\\0'
  245. );
  246. $_REQUEST['link_url'] = preg_replace($from, $to, $_REQUEST['link_url']);
  247. }
  248. // an anchor is mandatory
  249. if(!is_object($anchor)) {
  250. Logger::error(i18n::s('No anchor has been found.'));
  251. $item = $_REQUEST;
  252. $with_form = TRUE;
  253. // stop robots
  254. } elseif(Surfer::may_be_a_robot()) {
  255. Logger::error(i18n::s('Please prove you are not a robot.'));
  256. $item = $_REQUEST;
  257. $with_form = TRUE;
  258. // reward the poster for new posts
  259. } elseif(!isset($item['id'])) {
  260. // display the form on error
  261. if(!$_REQUEST['id'] = Links::post($_REQUEST)) {
  262. $item = $_REQUEST;
  263. $with_form = TRUE;
  264. // follow-up
  265. } else {
  266. // touch the related anchor
  267. $anchor->touch('link:create', $_REQUEST['id'], isset($_REQUEST['silent']) && ($_REQUEST['silent'] == 'Y'), TRUE, TRUE);
  268. // clear cache
  269. Links::clear($_REQUEST);
  270. // increment the post counter of the surfer
  271. Users::increment_posts(Surfer::get_id());
  272. // thanks
  273. $context['page_title'] = i18n::s('Thank you for your contribution');
  274. // the action
  275. $context['text'] .= '<p>'.i18n::s('The link has been successfully recorded.').'</p>';
  276. // list persons that have been notified
  277. $context['text'] .= Mailer::build_recipients(i18n::s('Persons that have been notified'));
  278. // follow-up commands
  279. $follow_up = i18n::s('What do you want to do now?');
  280. $menu = array();
  281. if(is_object($anchor)) {
  282. $menu = array_merge($menu, array($anchor->get_url('links') => i18n::s('View the page')));
  283. $menu = array_merge($menu, array('links/edit.php?anchor='.$anchor->get_reference() => i18n::s('Submit another link')));
  284. }
  285. $follow_up .= Skin::build_list($menu, 'menu_bar');
  286. $context['text'] .= Skin::build_block($follow_up, 'bottom');
  287. // log the submission of a new link by a non-associate
  288. if(!Surfer::is_associate() && is_object($anchor)) {
  289. $label = sprintf(i18n::c('New link at %s'), strip_tags($anchor->get_title()));
  290. $link = $context['url_to_home'].$context['url_to_root'].$anchor->get_url().'#links';
  291. $description = $_REQUEST['link_url']."\n"
  292. .sprintf(i18n::c('at %s'),'<a href="'.$link.'">'.$link.'</a>');
  293. Logger::notify('links/edit.php', $label, $description);
  294. }
  295. }
  296. // update an existing link
  297. } else {
  298. // display the form on error
  299. if(!Links::put($_REQUEST)) {
  300. $item = $_REQUEST;
  301. $with_form = TRUE;
  302. // follow-up
  303. } else {
  304. // touch the related anchor
  305. $anchor->touch('link:update', $_REQUEST['id'], isset($_REQUEST['silent']) && ($_REQUEST['silent'] == 'Y'));
  306. // clear cache
  307. Links::clear($_REQUEST);
  308. // forward to the updated anchor page
  309. Safe::redirect($context['url_to_home'].$context['url_to_root'].$anchor->get_url().'#links');
  310. }
  311. }
  312. // display the form on GET
  313. } else
  314. $with_form = TRUE;
  315. // display the form
  316. if($with_form) {
  317. // the form to edit a link
  318. $context['text'] .= '<form method="post" action="'.$context['script_url'].'" onsubmit="return validateDocumentPost(this)" id="main_form"><div>';
  319. // the category, for direct uploads
  320. if(!$anchor) {
  321. // a splash message for new users
  322. $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";
  323. $label = i18n::s('Section');
  324. $input = '<select name="anchor">'.Sections::get_options(NULL, 'bookmarks').'</select>';
  325. $hint = i18n::s('Please carefully select a section for your link');
  326. $fields[] = array($label, $input, $hint);
  327. // allow for section change
  328. } elseif($item['id'] && preg_match('/section:/', $current = $anchor->get_reference())) {
  329. $label = i18n::s('Section');
  330. $input = '<select name="anchor">'.Sections::get_options($current, NULL).'</select>';
  331. $hint = i18n::s('Please carefully select a section for your link');
  332. $fields[] = array($label, $input, $hint);
  333. // else preserve the previous anchor
  334. } elseif(is_object($anchor))
  335. $context['text'] .= '<input type="hidden" name="anchor" value="'.$anchor->get_reference().'" />';
  336. // additional fields for anonymous surfers
  337. if(!isset($item['id']) && !Surfer::is_logged()) {
  338. // splash
  339. if(isset($item['id'])) {
  340. if(is_object($anchor))
  341. $login_url = $context['url_to_root'].'users/login.php?url='.urlencode('links/edit.php?id='.$item['id'].'&anchor='.$anchor->get_reference());
  342. else
  343. $login_url = $context['url_to_root'].'users/login.php?url='.urlencode('links/edit.php?id='.$item['id']);
  344. } else {
  345. if(is_object($anchor))
  346. $login_url = $context['url_to_root'].'users/login.php?url='.urlencode('links/edit.php?anchor='.$anchor->get_reference());
  347. else
  348. $login_url = $context['url_to_root'].'users/login.php?url='.urlencode('links/edit.php');
  349. }
  350. $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";
  351. // the name, if any
  352. $label = i18n::s('Your name');
  353. $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(' ')).'" />';
  354. $hint = i18n::s('This optional field can be left blank if you wish.');
  355. $fields[] = array($label, $input, $hint);
  356. // the address, if any
  357. $label = i18n::s('Your address');
  358. $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()).'" />';
  359. $hint = i18n::s('e-mail or web address; this field is optional');
  360. $fields[] = array($label, $input, $hint);
  361. // stop robots
  362. if($field = Surfer::get_robot_stopper())
  363. $fields[] = $field;
  364. }
  365. // the link url
  366. $label = i18n::s('Web address');
  367. $value = '';
  368. if(isset($item['link_url']) && $item['link_url'])
  369. $value = $item['link_url'];
  370. elseif(isset($_REQUEST['link']))
  371. $value = $_REQUEST['link'];
  372. elseif(isset($_SESSION['pasted_link']))
  373. $value = $_SESSION['pasted_link'];
  374. $input = '<input type="text" name="link_url" id="link_url" size="55" value="'.encode_field($value).'" maxlength="255" accesskey="a" />';
  375. $hint = i18n::s('You can either type a plain url (http://) or use [article=&lt;id&gt;] notation');
  376. $fields[] = array($label, $input, $hint);
  377. // the title
  378. $label = i18n::s('Title');
  379. $value = '';
  380. if(isset($item['title']) && $item['title'])
  381. $value = $item['title'];
  382. elseif(isset($_REQUEST['title']))
  383. $value = $_REQUEST['title'];
  384. elseif(isset($_SESSION['pasted_title']))
  385. $value = $_SESSION['pasted_title'];
  386. $input = '<input name="title" value="'.encode_field($value).'" size="55" maxlength="255" />';
  387. $hint = i18n::s('Please provide a meaningful title.');
  388. $fields[] = array($label, $input, $hint);
  389. // the hovering title
  390. $label = i18n::s('Hovering popup');
  391. $value = '';
  392. if(isset($item['link_title']) && $item['link_title'])
  393. $value = $item['link_title'];
  394. $input = '<input name="link_title" value="'.encode_field($value).'" size="55" maxlength="255" />';
  395. $hint = i18n::s('This will appear near the link when the mouse is placed on top of it');
  396. $fields[] = array($label, $input, $hint);
  397. // the target flag: Inside the existing window or Blank window
  398. $label = i18n::s('Target window');
  399. $input = '<input type="radio" name="link_target" value="B"';
  400. if(!isset($item['link_target']) || ($item['link_target'] != 'I'))
  401. $input .= ' checked="checked"';
  402. $input .= '/> '.i18n::s('Open a separate window for external links')
  403. .BR.'<input type="radio" name="link_target" value="I"';
  404. if(isset($item['link_target']) && ($item['link_target'] == 'I'))
  405. $input .= ' checked="checked"';
  406. $input .= '/> '.i18n::s('Stay in same window on click')."\n";
  407. $fields[] = array($label, $input);
  408. // the description
  409. $label = i18n::s('Description');
  410. // use the editor if possible
  411. $value = '';
  412. if(isset($item['description']) && $item['description'])
  413. $value = $item['description'];
  414. elseif(isset($_REQUEST['text']))
  415. $value = $_REQUEST['text'];
  416. elseif(isset($_SESSION['pasted_text']))
  417. $value = $_SESSION['pasted_text'];
  418. $input = Surfer::get_editor('description', $value);
  419. $fields[] = array($label, $input);
  420. // build the form
  421. $context['text'] .= Skin::build_form($fields);
  422. // bottom commands
  423. $menu = array();
  424. $menu[] = Skin::build_submit_button(i18n::s('Submit'), i18n::s('Press [s] to submit data'), 's');
  425. if(is_object($anchor) && $anchor->is_viewable())
  426. $menu[] = Skin::build_link($anchor->get_url(), i18n::s('Cancel'), 'span');
  427. $context['text'] .= Skin::finalize_list($menu, 'assistant_bar');
  428. // associates may decide to not stamp changes -- complex command
  429. if(Surfer::is_associate() && Surfer::has_all())
  430. $context['text'] .= '<p><input type="checkbox" name="silent" value="Y" /> '.i18n::s('Do not change modification date of the main page.').'</p>';
  431. // validate page content
  432. $context['text'] .= '<p><input type="checkbox" name="option_validate" value="Y" checked="checked" /> '.i18n::s('Ensure this post is valid XHTML.').'</p>';
  433. // transmit the id as a hidden field
  434. if(isset($item['id']) && $item['id'])
  435. $context['text'] .= '<input type="hidden" name="id" value="'.$item['id'].'" />';
  436. // end of the form
  437. $context['text'] .= '</div></form>';
  438. // the script used for form handling at the browser
  439. $context['text'] .= JS_PREFIX
  440. .' // check that main fields are not empty'."\n"
  441. .' func'.'tion validateDocumentPost(container) {'."\n"
  442. ."\n"
  443. .' // link_url is mandatory'."\n"
  444. .' if(!container.link_url.value) {'."\n"
  445. .' alert("'.i18n::s('Please type a valid link.').'");'."\n"
  446. .' Yacs.stopWorking();'."\n"
  447. .' return false;'."\n"
  448. .' }'."\n"
  449. ."\n"
  450. .' // successful check'."\n"
  451. .' return true;'."\n"
  452. .' }'."\n"
  453. ."\n"
  454. .'// set the focus on first form field'."\n"
  455. .'$("link_url").focus();'."\n"
  456. .JS_SUFFIX."\n";
  457. // clear session data now we have populated the form
  458. unset($_SESSION['pasted_link']);
  459. unset($_SESSION['anchor_reference']);
  460. unset($_SESSION['pasted_text']);
  461. unset($_SESSION['pasted_title']);
  462. // details
  463. $details = array();
  464. // last edition
  465. if(isset($item['edit_name']) && $item['edit_name'])
  466. $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']));
  467. // hits
  468. if(isset($item['hits']) && ($item['hits'] > 1))
  469. $details[] = Skin::build_number($item['hits'], i18n::s('clicks'));
  470. // all details
  471. if(@count($details))
  472. $context['page_details'] .= '<p class="details">'.ucfirst(implode(', ', $details))."</p>\n";
  473. // general help on this form
  474. $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>'
  475. .'<p>'.i18n::s('Please set a meaningful title to be used instead of the link itself.').'</p>'
  476. .'<p>'.i18n::s('Also, take the time to describe the link. This field is fully indexed for searches.').'</p>'
  477. .'<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>';
  478. $context['components']['boxes'] = Skin::build_box(i18n::s('Help'), $help, 'boxes', 'help');
  479. }
  480. // render the skin
  481. render_skin();
  482. ?>