htmloutput.php 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765
  1. <?php namespace HashOver;
  2. // Copyright (C) 2015-2017 Jacob Barkdull
  3. // This file is part of HashOver.
  4. //
  5. // HashOver is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as
  7. // published by the Free Software Foundation, either version 3 of the
  8. // License, or (at your option) any later version.
  9. //
  10. // HashOver is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with HashOver. If not, see <http://www.gnu.org/licenses/>.
  17. // Display source code
  18. if (basename ($_SERVER['PHP_SELF']) === basename (__FILE__)) {
  19. if (isset ($_GET['source'])) {
  20. header ('Content-type: text/plain; charset=UTF-8');
  21. exit (file_get_contents (basename (__FILE__)));
  22. } else {
  23. exit ('<b>HashOver</b>: This is a class file.');
  24. }
  25. }
  26. class HTMLOutput
  27. {
  28. public $setup;
  29. public $mode;
  30. public $locale;
  31. public $avatars;
  32. public $misc;
  33. public $cookies;
  34. public $login;
  35. public $commentCounts;
  36. public $pageTitle;
  37. public $pageURL;
  38. public $postCommentOn;
  39. public $popularComments;
  40. public $comments;
  41. protected $emphasizedField;
  42. protected $defaultLoginInputs;
  43. public function __construct (Setup $setup, array $counts)
  44. {
  45. $this->setup = $setup;
  46. $this->mode = $setup->usage['mode'];
  47. $this->locale = new Locale ($setup);
  48. $this->login = new Login ($setup);
  49. $this->avatars = new Avatars ($setup);
  50. $this->misc = new Misc ($this->mode);
  51. $this->cookies = new Cookies ($setup);
  52. $this->commentCounts = $counts;
  53. $this->pageTitle = $this->setup->pageTitle;
  54. $this->pageURL = $this->setup->pageURL;
  55. if ($this->mode !== 'php') {
  56. $this->pageTitle = $this->misc->jsEscape ($this->pageTitle);
  57. $this->pageURL = $this->misc->jsEscape ($this->pageURL);
  58. }
  59. // Set the field to emphasize after a failed post
  60. if (!empty ($_COOKIE['failed-on'])) {
  61. $this->emphasizedField = $this->cookies->getValue ('failed-on');
  62. }
  63. // "Post a comment" locale strings
  64. $post_comment_on = $this->locale->get ('post-comment-on');
  65. $this->postCommentOn = $post_comment_on[0];
  66. // Add optional "on <page title>" to "Post a comment" title
  67. if ($this->setup->displaysTitle !== false and !empty ($this->pageTitle)) {
  68. $this->postCommentOn = sprintf ($post_comment_on[1], $this->pageTitle);
  69. }
  70. // Create default login inputs elements
  71. $this->defaultLoginInputs = $this->loginInputs ();
  72. }
  73. protected function injectVar ($var)
  74. {
  75. // Return variable as JavaScript concatenation statement
  76. if ($this->mode !== 'php') {
  77. if (!empty ($var)) {
  78. return '\' + ' . $var . ' + \'';
  79. }
  80. }
  81. // Return variable normally by default
  82. return $var;
  83. }
  84. // Re-encode a URL
  85. protected function safeURLEncode ($url)
  86. {
  87. return urlencode (urldecode ($url));
  88. }
  89. // Creates input elements for user login information
  90. protected function loginInputs ($permalink = '', $editForm = false, $name = '', $website = '')
  91. {
  92. $permalink = !empty ($permalink) ? '-' . $this->injectVar ($permalink) : '';
  93. // Login input attribute information
  94. $login_input_attributes = array (
  95. 'name' => array (
  96. 'wrapper-class' => 'hashover-name-input',
  97. 'label-class' => 'hashover-name-label',
  98. 'placeholder' => $this->locale->get ('name'),
  99. 'input-id' => 'hashover-main-name' . $permalink,
  100. 'input-type' => 'text',
  101. 'input-name' => 'name',
  102. 'input-title' => $this->locale->get ('name-tip'),
  103. 'input-value' => $this->misc->makeXSSsafe ($this->login->name)
  104. ),
  105. 'password' => array (
  106. 'wrapper-class' => 'hashover-password-input',
  107. 'label-class' => 'hashover-password-label',
  108. 'placeholder' => $this->locale->get ('password'),
  109. 'input-id' => 'hashover-main-password' . $permalink,
  110. 'input-type' => 'password',
  111. 'input-name' => 'password',
  112. 'input-title' => $this->locale->get ('password-tip'),
  113. 'input-value' => ''
  114. ),
  115. 'email' => array (
  116. 'wrapper-class' => 'hashover-email-input',
  117. 'label-class' => 'hashover-email-label',
  118. 'placeholder' => $this->locale->get ('email'),
  119. 'input-id' => 'hashover-main-email' . $permalink,
  120. 'input-type' => 'email',
  121. 'input-name' => 'email',
  122. 'input-title' => $this->locale->get ('email-tip'),
  123. 'input-value' => $this->misc->makeXSSsafe ($this->login->email)
  124. ),
  125. 'website' => array (
  126. 'wrapper-class' => 'hashover-website-input',
  127. 'label-class' => 'hashover-website-label',
  128. 'placeholder' => $this->locale->get ('website'),
  129. 'input-id' => 'hashover-main-website' . $permalink,
  130. 'input-type' => 'url',
  131. 'input-name' => 'website',
  132. 'input-title' => $this->locale->get ('website-tip'),
  133. 'input-value' => $this->misc->makeXSSsafe ($this->login->website)
  134. )
  135. );
  136. // Change input values to specified values
  137. if ($editForm === true) {
  138. $login_input_attributes['name']['input-value'] = $this->injectVar ($name);
  139. $login_input_attributes['password']['placeholder'] = $this->locale->get ('confirm-password');
  140. $login_input_attributes['password']['input-title'] = $this->locale->get ('confirm-password');
  141. $login_input_attributes['website']['input-value'] = $this->injectVar ($website);
  142. }
  143. // Create wrapper element for styling login inputs
  144. $login_inputs = new HTMLTag ('div', array (
  145. 'class' => 'hashover-inputs'
  146. ));
  147. // Create and append login input elements to main form inputs wrapper element
  148. foreach ($login_input_attributes as $field => $attributes) {
  149. // Skip disabled input tags
  150. if ($this->setup->fieldOptions[$field] === false) {
  151. continue;
  152. }
  153. // Create cell element for inputs
  154. $input_cell = new HTMLTag ('div', array (
  155. 'class' => 'hashover-input-cell'
  156. ));
  157. if ($this->setup->usesLabels === true) {
  158. // Create label element for input
  159. $label = new HTMLTag ('label', array (
  160. 'for' => $attributes['input-id'],
  161. 'class' => $attributes['label-class'],
  162. 'innerHTML' => $attributes['placeholder']
  163. ), false);
  164. // Add label to cell element
  165. $input_cell->appendChild ($label);
  166. }
  167. // Create wrapper element for input
  168. $input_wrapper = new HTMLTag ('div', array (
  169. 'class' => $attributes['wrapper-class']
  170. ));
  171. // Add a class for indicating a required field
  172. if ($this->setup->fieldOptions[$field] === 'required') {
  173. $input_wrapper->appendAttribute ('class', 'hashover-required-input');
  174. }
  175. // Add a class for indicating a post failure
  176. if ($this->emphasizedField === $field) {
  177. $input_wrapper->appendAttribute ('class', 'hashover-emphasized-input');
  178. }
  179. // Create input element
  180. $input = new HTMLTag ('input', array (
  181. 'id' => $attributes['input-id'],
  182. 'class' => 'hashover-input-info',
  183. 'type' => $attributes['input-type'],
  184. 'name' => $attributes['input-name'],
  185. 'value' => $attributes['input-value'],
  186. 'title' => $attributes['input-title'],
  187. 'placeholder' => $attributes['placeholder']
  188. ), false, true);
  189. // Add input to wrapper element
  190. $input_wrapper->appendChild ($input);
  191. // Add input to cell element
  192. $input_cell->appendChild ($input_wrapper);
  193. // Add input cell to main inputs wrapper element
  194. $login_inputs->appendChild ($input_cell);
  195. }
  196. return $login_inputs;
  197. }
  198. protected function getAvatarBackground ($avatar_src)
  199. {
  200. // Background image CSS
  201. $background_image = 'background-image: url(\'%s\');';
  202. // Escape background image in JavaScript mode
  203. if ($this->mode !== 'php') {
  204. $background_image = $this->misc->jsEscape ($background_image);
  205. }
  206. // Inject avatar URL into background image CSS
  207. return sprintf ($background_image, $avatar_src);
  208. }
  209. protected function avatar ($text)
  210. {
  211. // If avatars set to images
  212. if ($this->setup->iconMode === 'image') {
  213. // Logged in
  214. if ($this->login->userIsLoggedIn === true) {
  215. // Image source is avatar image
  216. $hash = !empty ($this->login->email) ? md5 (mb_strtolower (trim ($this->login->email))) : '';
  217. $avatar_src = $this->avatars->getGravatar ($hash);
  218. } else {
  219. // Logged out
  220. // Image source is local default image
  221. $avatar_src = $this->setup->httpImages . '/first-comment.' . $this->setup->imageFormat;
  222. }
  223. // Create avatar image element
  224. $avatar = new HTMLTag ('div', array (
  225. 'style' => $this->getAvatarBackground ($avatar_src)
  226. ), false);
  227. } else {
  228. // Avatars set to count
  229. // Create element displaying comment number user will be
  230. $avatar = new HTMLTag ('span', array (
  231. 'innerHTML' => $text
  232. ), false);
  233. }
  234. return $avatar;
  235. }
  236. // Creates a wrapper element for each comment
  237. public function commentWrapper ($permalink, $classes = '', $innerHTML = '')
  238. {
  239. $comment_wrapper = new HTMLTag ('div', array (
  240. 'id' => $this->injectVar ($permalink),
  241. 'class' => 'hashover-comment'
  242. ), false);
  243. if ($this->mode !== 'php') {
  244. $comment_wrapper->appendAttribute ('class', $this->injectVar ($classes), false);
  245. $comment_wrapper->innerHTML ($this->injectVar ($innerHTML));
  246. return $comment_wrapper->asHTML ();
  247. }
  248. return $comment_wrapper;
  249. }
  250. // Creates wrapper element to name element
  251. public function nameWrapper ($name_Link, $name_class)
  252. {
  253. $name_wrapper = new HTMLTag ('span', array (
  254. 'class' => 'hashover-comment-name ' . $this->injectVar ($name_class),
  255. 'innerHTML' => $this->injectVar ($name_Link)
  256. ), false);
  257. return $name_wrapper->asHTML ();
  258. }
  259. // Creates name hyperlink/span element
  260. public function nameElement ($element, $name, $permalink, $href = '')
  261. {
  262. $name_text = $this->injectVar ($name);
  263. $name_class = 'hashover-name-' . $this->injectVar ($permalink);
  264. // Decide what kind of element to create
  265. switch ($element) {
  266. case 'a': {
  267. // A hyperlink pointing to the user's input URL
  268. $name_link = new HTMLTag ('a', array (
  269. 'href' => $this->injectVar ($href),
  270. 'class' => $name_class,
  271. 'rel' => 'noopener noreferrer',
  272. 'target' => '_blank',
  273. 'title' => $name_text,
  274. 'innerHTML' => $name_text
  275. ), false);
  276. break;
  277. }
  278. case 'span': {
  279. // A plain wrapper element
  280. $name_link = new HTMLTag ('span', array (
  281. 'class' => $name_class,
  282. 'innerHTML' => $name_text
  283. ), false);
  284. break;
  285. }
  286. }
  287. return $name_link->asHTML ();
  288. }
  289. // Creates "Top of Thread" hyperlink element
  290. public function threadLink ($permalink, $parent, $name)
  291. {
  292. // Get locale string
  293. $thread_locale = $this->locale->get ('thread');
  294. // Inject OP's name into the locale
  295. $inner_html = sprintf ($thread_locale, $this->injectVar ($name));
  296. // Create hyperlink element
  297. $thread_link = new HTMLTag ('a', array (
  298. 'href' => '#' . $this->injectVar ($parent),
  299. 'id' => 'hashover-thread-link-' . $this->injectVar ($permalink),
  300. 'class' => 'hashover-thread-link',
  301. 'title' => $this->locale->get ('thread-tip'),
  302. 'innerHTML' => $inner_html
  303. ), false);
  304. return $thread_link->asHTML ();
  305. }
  306. // Creates hyperlink with URL queries to link reference
  307. protected function queryLink ($href = '', array $queries = array ())
  308. {
  309. $link = new HTMLTag ('a', array ('href' => $href), false);
  310. $queries = array_merge ($this->setup->URLQueryList, $queries);
  311. // Add URL queries to link reference
  312. if (!empty ($queries)) {
  313. $link->appendAttribute ('href', '?' . implode ('&', $queries), false);
  314. }
  315. return $link;
  316. }
  317. // Creates date/permalink hyperlink element
  318. public function dateLink ($permalink, $date)
  319. {
  320. // Create hyperlink element
  321. $date_link = $this->queryLink ($this->setup->filePath);
  322. // Append more attributes
  323. $date_link->appendAttributes (array (
  324. 'href' => '#' . $this->injectVar ($permalink),
  325. 'class' => 'hashover-date-permalink',
  326. 'title' => 'Permalink',
  327. 'innerHTML' => $this->injectVar ($date)
  328. ), false);
  329. return $date_link->asHTML ();
  330. }
  331. // Creates element to hold a count of likes/dislikes each comment has
  332. public function likeCount ($type, $permalink, $text)
  333. {
  334. // CSS class
  335. $class = 'hashover-' . $type;
  336. // Create element
  337. $count = new HTMLTag ('span', array (
  338. 'id' => $class . '-' . $this->injectVar ($permalink),
  339. 'class' => $class,
  340. 'innerHTML' => $this->injectVar ($text)
  341. ), false);
  342. return $count->asHTML ();
  343. }
  344. // Creates "Like"/"Dislike" hyperlink element
  345. public function likeLink ($type, $permalink, $class, $title, $text)
  346. {
  347. // Create hyperlink element
  348. $link = new HTMLTag ('a', array (
  349. 'href' => '#',
  350. 'id' => 'hashover-' . $type . '-' . $this->injectVar ($permalink),
  351. 'class' => $this->injectVar ($class),
  352. 'title' => $this->injectVar ($title),
  353. 'innerHTML' => $this->injectVar ($text)
  354. ), false);
  355. return $link->asHTML ();
  356. }
  357. // Creates a form control hyperlink element
  358. public function formLink ($type, $permalink, $class = '', $title = '')
  359. {
  360. $form = 'hashover-' . $type;
  361. $permalink = $this->injectVar ($permalink);
  362. $link = $this->queryLink ('', array ($form . '=' . $permalink));
  363. $title_locale = ($type === 'reply') ? 'reply-to-comment' : 'edit-your-comment';
  364. // Create more attributes
  365. $link->createAttributes (array (
  366. 'id' => $form. '-link-' . $permalink,
  367. 'class' => 'hashover-comment-' . $type,
  368. 'title' => $this->locale->get ($title_locale)
  369. ));
  370. // Append href attribute
  371. $link->appendAttribute ('href', '#' . $form . '-' . $permalink, false);
  372. // Append attributes
  373. if ($type === 'reply') {
  374. $link->appendAttributes (array (
  375. 'class' => $this->injectVar ($class),
  376. 'title' => '- ' . $this->injectVar ($title)
  377. ));
  378. }
  379. // Add link text
  380. $link->innerHTML ($this->locale->get ($type));
  381. return $link->asHTML ();
  382. }
  383. // Creates "Cancel" hyperlink element
  384. public function cancelLink ($permalink, $for, $class = '')
  385. {
  386. $cancel_link = $this->queryLink ($this->setup->filePath);
  387. $cancel_locale = $this->locale->get ('cancel');
  388. // Append href attribute
  389. $cancel_link->appendAttribute ('href', '#' . $permalink, false);
  390. // Create more attributes
  391. $cancel_link->createAttributes (array (
  392. 'class' => 'hashover-comment-' . $for,
  393. 'title' => $cancel_locale
  394. ));
  395. // Append optional class
  396. if (!empty ($class)) {
  397. $cancel_link->appendAttribute ('class', $class);
  398. }
  399. // Add "Cancel" hyperlink text
  400. $cancel_link->innerHTML ($cancel_locale);
  401. return $cancel_link->asHTML ();
  402. }
  403. public function userAvatar ($text, $href, $src)
  404. {
  405. // If avatars set to images
  406. if ($this->setup->iconMode !== 'none') {
  407. // Create wrapper element for avatar image
  408. $avatar_wrapper = new HTMLTag ('span', array (
  409. 'class' => 'hashover-avatar'
  410. ), false);
  411. if ($this->setup->iconMode === 'image') {
  412. // Create avatar image element
  413. $comments_avatar = new HTMLTag ('div', array (
  414. 'style' => $this->getAvatarBackground ($this->injectVar ($src))
  415. ), false);
  416. } else {
  417. // Avatars set to count
  418. // Create element displaying comment number user will be
  419. $comments_avatar = new HTMLTag ('a', array (
  420. 'href' => '#' . $this->injectVar ($href),
  421. 'title' => 'Permalink',
  422. 'innerHTML' => $this->injectVar ($text)
  423. ), false);
  424. }
  425. // Add comments avatar to avatar image wrapper element
  426. $avatar_wrapper->appendChild ($comments_avatar);
  427. return $avatar_wrapper->asHTML ();
  428. }
  429. }
  430. protected function subscribeLabel ($id = '', $class = 'main', $checked = true)
  431. {
  432. // Create subscribe checkbox label element
  433. $subscribe_label = new HTMLTag ('label', array (
  434. 'for' => 'hashover-subscribe',
  435. 'class' => 'hashover-' . $class . '-label',
  436. 'title' => $this->locale->get ('subscribe-tip')
  437. ));
  438. if (!empty ($id)) {
  439. $subscribe_label->appendAttribute ('for', '-' . $this->injectVar ($id), false);
  440. }
  441. // Create subscribe element checkbox
  442. $subscribe = new HTMLTag ('input', array (
  443. 'id' => 'hashover-subscribe',
  444. 'type' => 'checkbox',
  445. 'name' => 'subscribe'
  446. ), false, true);
  447. if (!empty ($id)) {
  448. $subscribe->appendAttribute ('id', '-' . $this->injectVar ($id), false);
  449. }
  450. // Check checkbox
  451. if ($checked === true) {
  452. $subscribe->createAttribute ('checked', 'true');
  453. }
  454. // Add subscribe checkbox element to subscribe checkbox label element
  455. $subscribe_label->appendChild ($subscribe);
  456. // Add text to subscribe checkbox label element
  457. $subscribe_label->appendInnerHTML ($this->locale->get ('subscribe'));
  458. return $subscribe_label;
  459. }
  460. protected function acceptedFormatCell ($format, $locale_key)
  461. {
  462. $title = new HTMLTag ('p', array ('class' => 'hashover-title'));
  463. $accepted_format = sprintf ($this->locale->get ('accepted-format'), $format);
  464. $title->innerHTML ($accepted_format);
  465. $paragraph = new HTMLTag ('p');
  466. $paragraph->innerHTML ($this->locale->get ($locale_key));
  467. return new HTMLTag ('div', array (
  468. 'children' => array ($title, $paragraph)
  469. ));
  470. }
  471. protected function commentForm (HTMLTag $form, $type, $placeholder, $text, $permalink = '')
  472. {
  473. $permalink = !empty ($permalink) ? '-' . $this->injectVar ($permalink) : '';
  474. $title_locale = ($type === 'reply') ? 'reply-form' : 'comment-form';
  475. // Create textarea
  476. $textarea = new HTMLTag ('textarea', array (
  477. 'id' => 'hashover-' . $type . '-comment' . $permalink,
  478. 'class' => 'hashover-textarea hashover-' . $type . '-textarea',
  479. 'cols' => '63',
  480. 'name' => 'comment',
  481. 'rows' => '6',
  482. 'title' => $this->locale->get ($title_locale)
  483. ), false);
  484. // Set the placeholder attribute if one is given
  485. if (!empty ($placeholder)) {
  486. $textarea->createAttribute ('placeholder', $placeholder);
  487. }
  488. if ($type === 'main') {
  489. // Add a class for indicating a post failure
  490. if ($this->emphasizedField === 'comment') {
  491. $textarea->appendAttribute ('class', 'hashover-emphasized-input');
  492. }
  493. // If the comment was a reply, have the textarea use the reply textarea locale
  494. if (!empty ($_COOKIE['replied'])) {
  495. $reply_form_placeholder = $this->locale->get ('reply-form');
  496. $textarea->createAttribute ('placeholder', $reply_form_placeholder);
  497. }
  498. }
  499. // Set textarea content if given any text
  500. if (!empty ($text)) {
  501. $textarea->innerHTML ($text);
  502. }
  503. // Add textarea element to form element
  504. $form->appendChild ($textarea);
  505. // Create element for various messages when needed
  506. if ($type !== 'main') {
  507. $message = new HTMLTag ('div', array (
  508. 'id' => 'hashover-' . $type . '-message-container' . $permalink,
  509. 'class' => 'hashover-message',
  510. 'children' => array (
  511. new HTMLTag ('div', array (
  512. 'id' => 'hashover-' . $type . '-message' . $permalink
  513. ))
  514. )
  515. ));
  516. // Add message element to form element
  517. $form->appendChild ($message);
  518. }
  519. // Create accepted HTML message element
  520. $accepted_html_message = new HTMLTag ('div', array (
  521. 'id' => 'hashover-' . $type . '-formatting-message' . $permalink,
  522. 'class' => 'hashover-formatting-message',
  523. 'children' => array (
  524. new HTMLTag ('div', array (
  525. 'class' => 'hashover-formatting-table',
  526. 'children' => array (
  527. $this->acceptedFormatCell ('HTML', 'accepted-html'),
  528. $this->acceptedFormatCell ('Markdown', 'accepted-markdown')
  529. )
  530. ))
  531. )
  532. ));
  533. // Ensure the accepted HTML message is open in PHP mode
  534. if ($this->mode === 'php') {
  535. $accepted_html_message->appendAttribute ('class', 'hashover-message-open');
  536. $accepted_html_message->appendAttribute ('class', 'hashover-php-message-open');
  537. }
  538. // Add accepted HTML message element to form element
  539. $form->appendChild ($accepted_html_message);
  540. }
  541. protected function pageInfoFields (HTMLTag $form)
  542. {
  543. // Create hidden page URL input element
  544. $url_input = new HTMLTag ('input', array (
  545. 'type' => 'hidden',
  546. 'name' => 'url',
  547. 'value' => $this->pageURL
  548. ), false, true);
  549. // Add hidden page URL input element to form element
  550. $form->appendChild ($url_input);
  551. // Create hidden page title input element
  552. $title_input = new HTMLTag ('input', array (
  553. 'type' => 'hidden',
  554. 'name' => 'title',
  555. 'value' => $this->pageTitle
  556. ), false, true);
  557. // Add hidden page title input element to form element
  558. $form->appendChild ($title_input);
  559. // Check if the script is being remotely accessed
  560. if ($this->setup->remoteAccess === true) {
  561. // Create hidden input element indicating remote access
  562. $remote_access_input = new HTMLTag ('input', array (
  563. 'type' => 'hidden',
  564. 'name' => 'remote-access',
  565. 'value' => 'true'
  566. ), false, true);
  567. // Add remote access input element to form element
  568. $form->appendChild ($remote_access_input);
  569. }
  570. }
  571. protected function acceptedHTML ($type, $permalink = '')
  572. {
  573. $permalink = !empty ($permalink) ? '-' . $this->injectVar ($permalink) : '';
  574. $accepted_format = $this->locale->get ('comment-formatting');
  575. // Create accepted HTML message revealer hyperlink
  576. $accepted_html = new HTMLTag ('span', array (
  577. 'id' => 'hashover-' . $type . '-formatting' . $permalink,
  578. 'class' => 'hashover-fake-link hashover-formatting',
  579. 'title' => $accepted_format,
  580. 'innerHTML' => $accepted_format
  581. ));
  582. // Return the hyperlink
  583. return $accepted_html;
  584. }
  585. public function initialHTML (array $popular_list, $hashover_wrapper = true)
  586. {
  587. // Create element that HashOver comments will appear in
  588. $hashover_element = new HTMLTag ('div', array (
  589. 'id' => 'hashover'
  590. ), false);
  591. // Add class for differentiating desktop and mobile styling
  592. if ($this->setup->isMobile === true) {
  593. $hashover_element->appendAttribute ('class', 'hashover-mobile');
  594. } else {
  595. $hashover_element->appendAttribute ('class', 'hashover-desktop');
  596. }
  597. // Add class to indicate user login status
  598. if ($this->login->userIsLoggedIn === true) {
  599. $hashover_element->appendAttribute ('class', 'hashover-logged-in');
  600. } else {
  601. $hashover_element->appendAttribute ('class', 'hashover-logged-out');
  602. }
  603. // Create element for jump anchor
  604. $jump_anchor = new HTMLTag ('span', array (
  605. 'id' => 'comments'
  606. ));
  607. // Add jump anchor to HashOver element
  608. $hashover_element->appendChild ($jump_anchor);
  609. // Create primary form wrapper element
  610. $form_section = new HTMLTag ('div', array (
  611. 'id' => 'hashover-form-section'
  612. ));
  613. // Hide primary form wrapper if comments are to be initially hidden
  614. if ($this->mode === 'javascript' and $this->setup->collapsesUI === true) {
  615. $form_section->createAttribute ('style', 'display: none;');
  616. }
  617. // Create element for "Post Comment" title
  618. $post_title = new HTMLTag ('span', array (
  619. 'class' => array (
  620. 'hashover-title',
  621. 'hashover-main-title',
  622. 'hashover-dashed-title'
  623. ),
  624. 'innerHTML' => $this->postCommentOn
  625. ));
  626. // Add "Post Comment" element to primary form wrapper
  627. $form_section->appendChild ($post_title);
  628. // Create element for various messages when needed
  629. $message_container = new HTMLTag ('div', array (
  630. 'id' => 'hashover-message-container',
  631. 'class' => 'hashover-title hashover-message'
  632. ));
  633. // Create element for message text
  634. $message_element = new HTMLTag ('div', array (
  635. 'id' => 'hashover-message'
  636. ));
  637. // Check if message cookie is set
  638. if (!empty ($_COOKIE['message']) or !empty ($_COOKIE['error'])) {
  639. // If so, set the message element to open in PHP mode
  640. if ($this->mode === 'php') {
  641. $message_container->appendAttribute ('class', array (
  642. 'hashover-message-open',
  643. 'hashover-php-message-open'
  644. ));
  645. }
  646. // Check if the message is a normal message
  647. if (!empty ($_COOKIE['message'])) {
  648. // If so, get an XSS safe version of the message
  649. $message = $this->misc->makeXSSsafe ($this->cookies->getValue ('message'));
  650. } else {
  651. // If not, get an XSS safe version of the error message
  652. $message = $this->misc->makeXSSsafe ($this->cookies->getValue ('error'));
  653. // And set a class to the message element indicating an error
  654. $message_container->appendAttribute ('class', 'hashover-message-error');
  655. }
  656. // And put current message into message element
  657. $message_element->innerHTML ($message);
  658. }
  659. // Add message text element to message element
  660. $message_container->appendChild ($message_element);
  661. // Add message element to primary form wrapper
  662. $form_section->appendChild ($message_container);
  663. // Create main HashOver form
  664. $main_form = new HTMLTag ('form', array (
  665. 'id' => 'hashover-form',
  666. 'class' => 'hashover-balloon',
  667. 'name' => 'hashover-form',
  668. 'action' => $this->setup->httpScripts . '/postcomments.php',
  669. 'method' => 'post'
  670. ));
  671. // Create wrapper element for styling inputs
  672. $main_inputs = new HTMLTag ('div', array (
  673. 'class' => 'hashover-inputs'
  674. ));
  675. // If avatars are enabled
  676. if ($this->setup->iconMode !== 'none') {
  677. // Create avatar element for main HashOver form
  678. $main_avatar = new HTMLTag ('div', array (
  679. 'class' => 'hashover-avatar-image'
  680. ));
  681. // Add count element to avatar element
  682. $main_avatar->appendChild ($this->avatar ($this->commentCounts['primary']));
  683. // Add avatar element to inputs wrapper element
  684. $main_inputs->appendChild ($main_avatar);
  685. }
  686. // Logged in
  687. if ($this->login->userIsLoggedIn === true) {
  688. if (!empty ($this->login->name)) {
  689. $user_name = $this->misc->makeXSSsafe ($this->login->name);
  690. } else {
  691. $user_name = $this->setup->defaultName;
  692. }
  693. $user_website = $this->misc->makeXSSsafe ($this->login->website);
  694. $name_class = 'hashover-name-plain';
  695. $is_twitter = false;
  696. // Check if user's name is a Twitter handle
  697. if ($user_name[0] === '@') {
  698. $user_name = mb_substr ($user_name, 1);
  699. $name_class = 'hashover-name-twitter';
  700. $is_twitter = true;
  701. $name_length = mb_strlen ($user_name);
  702. if ($name_length > 1 and $name_length <= 30) {
  703. if (empty ($user_website)) {
  704. $user_website = 'http://twitter.com/' . $user_name;
  705. }
  706. }
  707. }
  708. // Create element for logged user's name
  709. $main_form_column_spanner = new HTMLTag ('div', array (
  710. 'class' => 'hashover-comment-name hashover-top-name'
  711. ), false);
  712. // Check if user gave website
  713. if (!empty ($user_website)) {
  714. if ($is_twitter === false) {
  715. $name_class = 'hashover-name-website';
  716. }
  717. // Create link to user's website
  718. $main_form_hyperlink = new HTMLTag ('a', array (
  719. 'href' => $user_website,
  720. 'rel' => 'noopener noreferrer',
  721. 'target' => '_blank',
  722. 'title' => $user_name,
  723. 'innerHTML' => $user_name
  724. ), false);
  725. // Add username hyperlink to main form column spanner
  726. $main_form_column_spanner->appendChild ($main_form_hyperlink);
  727. } else {
  728. // No website
  729. $main_form_column_spanner->innerHTML ($user_name);
  730. }
  731. // Set classes user's name spanner
  732. $main_form_column_spanner->appendAttribute ('class', $name_class);
  733. // Add main form column spanner to inputs wrapper element
  734. $main_inputs->appendChild ($main_form_column_spanner);
  735. } else {
  736. // Logged out
  737. // Use default login inputs
  738. $main_inputs->appendInnerHTML ($this->defaultLoginInputs->innerHTML);
  739. }
  740. // Add inputs wrapper to form element
  741. $main_form->appendChild ($main_inputs);
  742. // Create fake "required fields" element as a SPAM trap
  743. $required_fields = new HTMLTag ('div', array (
  744. 'id' => 'hashover-requiredFields'
  745. ));
  746. $fake_fields = array (
  747. 'summary' => 'text',
  748. 'age' => 'hidden',
  749. 'lastname' => 'text',
  750. 'address' => 'text',
  751. 'zip' => 'hidden',
  752. );
  753. // Create and append fake input elements to fake required fields
  754. foreach ($fake_fields as $name => $type) {
  755. $fake_input = new HTMLTag ('input', array (
  756. 'type' => $type,
  757. 'name' => $name,
  758. 'value' => ''
  759. ), false, true);
  760. // Add fake summary input element to fake required fields
  761. $required_fields->appendInnerHTML ($fake_input->asHTML ());
  762. }
  763. // Add fake input elements to form element
  764. $main_form->appendChild ($required_fields);
  765. // Post button locale
  766. $post_button = $this->locale->get ('post-button');
  767. // Create label element for comment textarea
  768. if ($this->setup->usesLabels === true) {
  769. $main_comment_label = new HTMLTag ('label', array (
  770. 'for' => 'hashover-main-comment',
  771. 'class' => 'hashover-comment-label',
  772. 'innerHTML' => $post_button
  773. ), false);
  774. // Add comment label to form element
  775. $main_form->appendChild ($main_comment_label);
  776. }
  777. // Get comment text if a comment cookie is set
  778. $comment_text = $this->misc->makeXSSsafe ($this->cookies->getValue ('comment'));
  779. // Comment form placeholder text
  780. $comment_form = $this->locale->get ('comment-form');
  781. // Create main textarea element and add it to form element
  782. $this->commentForm ($main_form, 'main', $comment_form, $comment_text);
  783. // Add page info fields to main form
  784. $this->pageInfoFields ($main_form);
  785. // Check if comment is a failed reply
  786. if (!empty ($_COOKIE['replied'])) {
  787. // If so, get the comment being replied to
  788. $replied = $this->cookies->getValue ('replied');
  789. // Create hidden reply to input element
  790. $reply_to_input = new HTMLTag ('input', array (
  791. 'type' => 'hidden',
  792. 'name' => 'reply-to',
  793. 'value' => $this->misc->makeXSSsafe ($replied)
  794. ), false, true);
  795. // And add hidden reply to input element to form element
  796. $main_form->appendChild ($reply_to_input);
  797. }
  798. // Create wrapper element for main form footer
  799. $main_form_footer = new HTMLTag ('div', array (
  800. 'class' => 'hashover-form-footer'
  801. ));
  802. // Create wrapper for form links
  803. $main_form_links_wrapper = new HTMLTag ('span', array (
  804. 'class' => 'hashover-form-links'
  805. ));
  806. // Add checkbox label element to main form buttons wrapper element
  807. if ($this->setup->fieldOptions['email'] !== false) {
  808. if ($this->login->userIsLoggedIn === false or !empty ($this->login->email)) {
  809. $main_form_links_wrapper->appendChild ($this->subscribeLabel ());
  810. }
  811. }
  812. // Create and add accepted HTML revealer hyperlink
  813. if ($this->mode === 'javascript') {
  814. $main_form_links_wrapper->appendChild ($this->acceptedHTML ('main'));
  815. }
  816. // Add main form links wrapper to main form footer element
  817. $main_form_footer->appendChild ($main_form_links_wrapper);
  818. // Create wrapper for form buttons
  819. $main_form_buttons_wrapper = new HTMLTag ('span', array (
  820. 'class' => 'hashover-form-buttons'
  821. ));
  822. // Create "Login" / "Logout" button element
  823. if ($this->setup->allowsLogin !== false or $this->login->userIsLoggedIn === true) {
  824. $login_button = new HTMLTag ('input', array (
  825. 'id' => 'hashover-login-button',
  826. 'class' => 'hashover-submit',
  827. 'type' => 'submit'
  828. ), false, true);
  829. // Check login state
  830. if ($this->login->userIsLoggedIn === true) {
  831. // Logged in
  832. $login_button->appendAttribute ('class', 'hashover-logout');
  833. $logout_locale = $this->locale->get ('logout');
  834. // Create logged in attributes
  835. $login_button->createAttributes (array (
  836. 'name' => 'logout',
  837. 'value' => $logout_locale,
  838. 'title' => $logout_locale
  839. ));
  840. } else {
  841. // Logged out
  842. $login_button->appendAttribute ('class', 'hashover-login');
  843. // Create logged out attributes
  844. $login_button->createAttributes (array (
  845. 'name' => 'login',
  846. 'value' => $this->locale->get ('login'),
  847. 'title' => $this->locale->get ('login-tip')
  848. ));
  849. }
  850. // Add "Login" / "Logout" element to main form footer element
  851. $main_form_buttons_wrapper->appendChild ($login_button);
  852. }
  853. // Create "Post Comment" button element
  854. $main_post_button = new HTMLTag ('input', array (
  855. 'id' => 'hashover-post-button',
  856. 'class' => 'hashover-submit hashover-post-button',
  857. 'type' => 'submit',
  858. 'name' => 'post',
  859. 'value' => $post_button,
  860. 'title' => $post_button
  861. ), false, true);
  862. // Add "Post Comment" element to main form buttons wrapper element
  863. $main_form_buttons_wrapper->appendChild ($main_post_button);
  864. // Add main form button wrapper to main form footer element
  865. $main_form_footer->appendChild ($main_form_buttons_wrapper);
  866. // Add main form footer to main form element
  867. $main_form->appendChild ($main_form_footer);
  868. // Add main form element to primary form wrapper
  869. $form_section->appendChild ($main_form);
  870. // Check if form position setting set to 'top'
  871. if ($this->setup->formPosition !== 'bottom') {
  872. // Add primary form wrapper to HashOver element
  873. $hashover_element->appendChild ($form_section);
  874. }
  875. if (!empty ($popular_list)) {
  876. // Create wrapper element for popular comments
  877. $popular_section = new HTMLTag ('div', array (
  878. 'id' => 'hashover-popular-section'
  879. ), false);
  880. // Hide popular comments wrapper if comments are to be initially hidden
  881. if ($this->mode === 'javascript') {
  882. if ($this->setup->collapsesUI === true or $this->setup->collapseLimit <= 0) {
  883. $popular_section->createAttribute ('style', 'display: none;');
  884. }
  885. }
  886. // Create wrapper element for popular comments title
  887. $pop_count_wrapper = new HTMLTag ('div', array (
  888. 'class' => 'hashover-dashed-title'
  889. ));
  890. // Create element for popular comments title
  891. $pop_count_element = new HTMLTag ('span', array (
  892. 'class' => 'hashover-title'
  893. ));
  894. // Add popular comments title text
  895. $popPlural = (count ($popular_list) !== 1) ? 1 : 0;
  896. $popular_comments_locale = $this->locale->get ('popular-comments');
  897. $pop_count_element->innerHTML ($popular_comments_locale[$popPlural]);
  898. // Add popular comments title element to wrapper element
  899. $pop_count_wrapper->appendChild ($pop_count_element);
  900. // Add popular comments title wrapper element to popular comments section
  901. $popular_section->appendChild ($pop_count_wrapper);
  902. // Create element for popular comments to appear in
  903. $popular_comments = new HTMLTag ('div', array (
  904. 'id' => 'hashover-top-comments'
  905. ), false);
  906. // Add comments to HashOver element
  907. if (!empty ($this->popularComments)) {
  908. $popular_comments->innerHTML (trim ($this->popularComments));
  909. }
  910. // Add popular comments element to popular comments section
  911. $popular_section->appendChild ($popular_comments);
  912. // Add popular comments section to HashOver element
  913. $hashover_element->appendChild ($popular_section);
  914. }
  915. // Create wrapper element for comments
  916. $comments_section = new HTMLTag ('div', array (
  917. 'id' => 'hashover-comments-section'
  918. ), false);
  919. // Create wrapper element for comment count and sort dropdown menu
  920. $count_sort_wrapper = new HTMLTag ('div', array (
  921. 'id' => 'hashover-count-wrapper',
  922. 'class' => 'hashover-count-sort hashover-dashed-title'
  923. ));
  924. // Create element for comment count
  925. $count_element = new HTMLTag ('span', array (
  926. 'id' => 'hashover-count'
  927. ));
  928. // Add comment count to comment count element
  929. if ($this->commentCounts['total'] > 1) {
  930. $count_element->innerHTML ($this->commentCounts['show-count']);
  931. }
  932. // Add comment count element to wrapper element
  933. $count_sort_wrapper->appendChild ($count_element);
  934. // JavaScript mode specific HTML
  935. if ($this->mode === 'javascript') {
  936. // Hide wrapper if comments are to be initially hidden
  937. if ($this->setup->collapsesUI === true) {
  938. $comments_section->createAttribute ('style', 'display: none;');
  939. }
  940. // Hide comment count if collapse limit is set at zero
  941. if ($this->setup->collapseLimit <= 0 or $this->commentCounts['total'] <= 1) {
  942. $count_sort_wrapper->createAttribute ('style', 'display: none;');
  943. }
  944. if ($this->commentCounts['total'] > 2) {
  945. // Create wrapper element for sort dropdown menu
  946. $sort_wrapper = new HTMLTag ('span', array (
  947. 'id' => 'hashover-sort',
  948. 'class' => 'hashover-select-wrapper'
  949. ));
  950. // Create sort dropdown menu element
  951. $sort_select = new HTMLTag ('select', array (
  952. 'id' => 'hashover-sort-select',
  953. 'name' => 'sort',
  954. 'size' => '1',
  955. 'title' => $this->locale->get ('sort')
  956. ));
  957. // Array of select tag sort options
  958. $sort_options = array (
  959. array ('value' => 'ascending', 'innerHTML' => $this->locale->get ('sort-ascending')),
  960. array ('value' => 'descending', 'innerHTML' => $this->locale->get ('sort-descending')),
  961. array ('value' => 'by-date', 'innerHTML' => $this->locale->get ('sort-by-date')),
  962. array ('value' => 'by-likes', 'innerHTML' => $this->locale->get ('sort-by-likes')),
  963. array ('value' => 'by-replies', 'innerHTML' => $this->locale->get ('sort-by-replies')),
  964. array ('value' => 'by-name', 'innerHTML' => $this->locale->get ('sort-by-name'))
  965. );
  966. // Create sort options for sort dropdown menu element
  967. for ($i = 0, $il = count ($sort_options); $i < $il; $i++) {
  968. $option = new HTMLTag ('option', array (
  969. 'value' => $sort_options[$i]['value'],
  970. 'innerHTML' => $sort_options[$i]['innerHTML']
  971. ), false);
  972. // Add sort option element to sort dropdown menu
  973. $sort_select->appendChild ($option);
  974. }
  975. // Create empty option group as spacer
  976. $spacer_optgroup = new HTMLTag ('optgroup', array (
  977. 'label' => '&nbsp;'
  978. ));
  979. // Add spacer option group to sort dropdown menu
  980. $sort_select->appendChild ($spacer_optgroup);
  981. // Create option group for threaded sort options
  982. $threaded_optgroup = new HTMLTag ('optgroup', array (
  983. 'label' => $this->locale->get ('sort-threads')
  984. ));
  985. // Array of select tag threaded sort options
  986. $threaded_sort_options = array (
  987. array ('value' => 'threaded-descending', 'innerHTML' => $this->locale->get ('sort-descending')),
  988. array ('value' => 'threaded-by-date', 'innerHTML' => $this->locale->get ('sort-by-date')),
  989. array ('value' => 'threaded-by-likes', 'innerHTML' => $this->locale->get ('sort-by-likes')),
  990. array ('value' => 'by-popularity', 'innerHTML' => $this->locale->get ('sort-by-popularity')),
  991. array ('value' => 'by-discussion', 'innerHTML' => $this->locale->get ('sort-by-discussion')),
  992. array ('value' => 'threaded-by-name', 'innerHTML' => $this->locale->get ('sort-by-name'))
  993. );
  994. // Create sort options for sort dropdown menu element
  995. for ($i = 0, $il = count ($threaded_sort_options); $i < $il; $i++) {
  996. $option = new HTMLTag ('option', array (
  997. 'value' => $threaded_sort_options[$i]['value'],
  998. 'innerHTML' => $threaded_sort_options[$i]['innerHTML']
  999. ), false);
  1000. // Add sort option element to threaded option group
  1001. $threaded_optgroup->appendChild ($option);
  1002. }
  1003. // Add threaded sort options group to sort dropdown menu
  1004. $sort_select->appendChild ($threaded_optgroup);
  1005. // Add sort dropdown menu element to sort wrapper element
  1006. $sort_wrapper->appendChild ($sort_select);
  1007. // Add comment count element to wrapper element
  1008. $count_sort_wrapper->appendChild ($sort_wrapper);
  1009. }
  1010. }
  1011. // Add comment count and sort dropdown menu wrapper to comments section
  1012. $comments_section->appendChild ($count_sort_wrapper);
  1013. // Create element that will hold the comments
  1014. $sort_div = new HTMLTag ('div', array (
  1015. 'id' => 'hashover-sort-div'
  1016. ), false);
  1017. // Add comments to HashOver element
  1018. if (!empty ($this->comments)) {
  1019. $sort_div->innerHTML (trim ($this->comments));
  1020. }
  1021. // Add comments element to comments section
  1022. $comments_section->appendChild ($sort_div);
  1023. // Add comments element to HashOver element
  1024. $hashover_element->appendChild ($comments_section);
  1025. // Check if form position setting set to 'bottom'
  1026. if ($this->setup->formPosition === 'bottom') {
  1027. // Add primary form wrapper to HashOver element
  1028. $hashover_element->appendChild ($form_section);
  1029. }
  1030. // Create end links wrapper element
  1031. $end_links_wrapper = new HTMLTag ('div', array (
  1032. 'id' => 'hashover-end-links'
  1033. ));
  1034. // Hide end links wrapper if comments are to be initially hidden
  1035. if ($this->mode === 'javascript' and $this->setup->collapsesUI === true) {
  1036. $end_links_wrapper->createAttribute ('style', 'display: none;');
  1037. }
  1038. // HashOver Comments hyperlink text
  1039. $homepage_link_text = $this->locale->get ('hashover-comments');
  1040. // Create link back to HashOver homepage (fixme! get a real page!)
  1041. $homepage_link = new HTMLTag ('a', array (
  1042. 'href' => 'http://tildehash.com/?page=hashover',
  1043. 'id' => 'hashover-home-link',
  1044. 'target' => '_blank',
  1045. 'title' => $homepage_link_text,
  1046. 'innerHTML' => $homepage_link_text
  1047. ), false);
  1048. // Add link back to HashOver homepage to end links wrapper element
  1049. $end_links_wrapper->innerHTML ($homepage_link->asHTML () . ' &#8210;');
  1050. // End links array
  1051. $end_links = array ();
  1052. if ($this->commentCounts['total'] > 1) {
  1053. if ($this->setup->displaysRSSLink === true
  1054. and $this->setup->APIStatus ('rss') !== 'disabled')
  1055. {
  1056. // Create RSS feed link
  1057. $rss_link = new HTMLTag ('a', array (), false);
  1058. $rss_link->createAttribute ('href', $this->setup->httpRoot . '/api/rss.php');
  1059. $rss_link->appendAttribute ('href', '?url=' . $this->safeURLEncode ($this->setup->pageURL), false);
  1060. // RSS Feed hyperlink text
  1061. $rss_link_text = $this->locale->get ('rss-feed');
  1062. $rss_link->createAttributes (array (
  1063. 'id' => 'hashover-rss-link',
  1064. 'target' => '_blank',
  1065. 'title' => $rss_link_text,
  1066. 'innerHTML' => $rss_link_text
  1067. ));
  1068. // Add RSS hyperlink to end links array
  1069. $end_links[] = $rss_link->asHTML ();
  1070. }
  1071. }
  1072. // Source Code hyperlink text
  1073. $source_link_text = $this->locale->get ('source-code');
  1074. // Create link to HashOver source code (fixme! can be done better)
  1075. $source_link = new HTMLTag ('a', array (
  1076. 'href' => $this->setup->httpScripts . '/hashover.php?source',
  1077. 'id' => 'hashover-source-link',
  1078. 'rel' => 'hashover-source',
  1079. 'target' => '_blank',
  1080. 'title' => $source_link_text,
  1081. 'innerHTML' => $source_link_text
  1082. ), false);
  1083. // Add source code hyperlink to end links array
  1084. $end_links[] = $source_link->asHTML ();
  1085. if ($this->mode === 'javascript') {
  1086. // Create link to HashOver JavaScript source code
  1087. $javascript_link = new HTMLTag ('a', array (
  1088. 'href' => $this->setup->httpScripts . '/hashover-javascript.php',
  1089. 'id' => 'hashover-javascript-link',
  1090. 'rel' => 'hashover-javascript',
  1091. 'target' => '_blank',
  1092. 'title' => 'JavaScript'
  1093. ), false);
  1094. // Append attributes
  1095. $javascript_link->appendAttribute ('href', '?url=' . $this->safeURLEncode ($this->setup->pageURL), false);
  1096. $javascript_link->appendAttribute ('href', '&title=' . $this->safeURLEncode ($this->setup->pageTitle), false);
  1097. if (!empty ($_GET['hashover-script'])) {
  1098. $hashover_script = $this->misc->makeXSSsafe ($this->safeURLEncode ($_GET['hashover-script']));
  1099. $javascript_link->appendAttribute ('href', '&hashover-script=' . $hashover_script, false);
  1100. }
  1101. // Add JavaScript code hyperlink text
  1102. $javascript_link->innerHTML ('JavaScript');
  1103. // Add JavaScript hyperlink to end links array
  1104. $end_links[] = $javascript_link->asHTML ();
  1105. }
  1106. // Add end links to end links wrapper element
  1107. $end_links_wrapper->appendInnerHTML (implode (' &middot;' . PHP_EOL, $end_links));
  1108. // Add end links wrapper element to HashOver element
  1109. $hashover_element->appendChild ($end_links_wrapper);
  1110. // Return all HTML with the HashOver wrapper element
  1111. if ($hashover_wrapper === true) {
  1112. return $hashover_element->asHTML ();
  1113. }
  1114. // Return just the HashOver wrapper element's innerHTML
  1115. return $hashover_element->innerHTML;
  1116. }
  1117. public function cancelButton ($type, $permalink)
  1118. {
  1119. $permalink = $this->injectVar ($permalink);
  1120. $cancel_button = $this->queryLink ($this->setup->filePath);
  1121. $class = 'hashover-' . $type . '-cancel';
  1122. $cancel_locale = $this->locale->get ('cancel');
  1123. // Add ID attribute with JavaScript variable single quote break out
  1124. if (!empty ($permalink)) {
  1125. $cancel_button->createAttribute ('id', $class . '-' . $permalink);
  1126. }
  1127. // Append href attribute
  1128. $cancel_button->appendAttribute ('href', '#' . $permalink, false);
  1129. // Create more attributes
  1130. $cancel_button->createAttributes (array (
  1131. 'class' => 'hashover-submit ' . $class,
  1132. 'title' => $cancel_locale,
  1133. 'innerHTML' => $cancel_locale
  1134. ));
  1135. return $cancel_button;
  1136. }
  1137. public function replyForm ($permalink = '', $file = '', $subscribed = true)
  1138. {
  1139. // Create HashOver reply form
  1140. $reply_form = new HTMLTag ('div', array (
  1141. 'class' => 'hashover-balloon'
  1142. ));
  1143. // If avatars are enabled
  1144. if ($this->setup->iconMode !== 'none') {
  1145. // Create avatar element for HashOver reply form
  1146. $reply_avatar = new HTMLTag ('div', array (
  1147. 'class' => 'hashover-avatar-image'
  1148. ));
  1149. // Add count element to avatar element
  1150. $reply_avatar->appendChild ($this->avatar ('+'));
  1151. // Add avatar element to inputs wrapper element
  1152. $reply_form->appendChild ($reply_avatar);
  1153. }
  1154. // Display default login inputs when logged out
  1155. if ($this->login->userIsLoggedIn === false) {
  1156. $reply_login_inputs = $this->loginInputs ($permalink);
  1157. $reply_form->appendChild ($reply_login_inputs);
  1158. }
  1159. // Create label element for comment textarea
  1160. if ($this->setup->usesLabels === true) {
  1161. $reply_comment_label = new HTMLTag ('label', array (
  1162. 'for' => 'hashover-reply-comment-' . $this->injectVar ($permalink),
  1163. 'class' => 'hashover-comment-label',
  1164. 'innerHTML' => $this->locale->get ('reply-to-comment')
  1165. ), false);
  1166. // Add comment label to form element
  1167. $reply_form->appendChild ($reply_comment_label);
  1168. }
  1169. // Reply form locale
  1170. $reply_form_placeholder = $this->locale->get ('reply-form');
  1171. // Create reply textarea element and add it to form element
  1172. $this->commentForm ($reply_form, 'reply', $reply_form_placeholder, '', $permalink);
  1173. // Add page info fields to reply form
  1174. $this->pageInfoFields ($reply_form);
  1175. // Create hidden reply to input element
  1176. if (!empty ($file)) {
  1177. $reply_to_input = new HTMLTag ('input', array (
  1178. 'type' => 'hidden',
  1179. 'name' => 'reply-to',
  1180. 'value' => $this->injectVar ($file)
  1181. ), false, true);
  1182. // Add hidden reply to input element to form element
  1183. $reply_form->appendChild ($reply_to_input);
  1184. }
  1185. // Create reply form footer element
  1186. $reply_form_footer = new HTMLTag ('div', array (
  1187. 'class' => 'hashover-form-footer'
  1188. ));
  1189. // Create wrapper for form links
  1190. $reply_form_links_wrapper = new HTMLTag ('span', array (
  1191. 'class' => 'hashover-form-links'
  1192. ));
  1193. // Add checkbox label element to reply form footer element
  1194. if ($this->setup->fieldOptions['email'] !== false) {
  1195. if ($this->login->userIsLoggedIn === false or !empty ($this->login->email)) {
  1196. $reply_form_links_wrapper->appendChild ($this->subscribeLabel ($permalink, 'reply', $subscribed));
  1197. }
  1198. }
  1199. // Create and add accepted HTML revealer hyperlink
  1200. if ($this->mode === 'javascript') {
  1201. $reply_form_links_wrapper->appendChild ($this->acceptedHTML ('reply', $permalink));
  1202. }
  1203. // Add reply form links wrapper to reply form footer element
  1204. $reply_form_footer->appendChild ($reply_form_links_wrapper);
  1205. // Create wrapper for form buttons
  1206. $reply_form_buttons_wrapper = new HTMLTag ('span', array (
  1207. 'class' => 'hashover-form-buttons'
  1208. ));
  1209. // Create "Cancel" link element
  1210. if ($this->setup->usesCancelButtons === true) {
  1211. // Add "Cancel" link element to reply form footer element
  1212. $reply_cancel_button = $this->cancelButton ('reply', $permalink);
  1213. $reply_form_buttons_wrapper->appendChild ($reply_cancel_button);
  1214. }
  1215. // Create "Post Comment" button element
  1216. $reply_post_button = new HTMLTag ('input', array (), false, true);
  1217. // Add ID attribute with JavaScript variable single quote break out
  1218. if (!empty ($permalink)) {
  1219. $reply_post_button->createAttribute ('id', 'hashover-reply-post-' . $this->injectVar ($permalink));
  1220. }
  1221. // Post reply locale
  1222. $post_reply = $this->locale->get ('post-reply');
  1223. // Continue with other attributes
  1224. $reply_post_button->createAttributes (array (
  1225. 'class' => 'hashover-submit hashover-reply-post',
  1226. 'type' => 'submit',
  1227. 'name' => 'post',
  1228. 'value' => $post_reply,
  1229. 'title' => $post_reply
  1230. ));
  1231. // Add "Post Comment" element to reply form footer element
  1232. $reply_form_buttons_wrapper->appendChild ($reply_post_button);
  1233. // Add reply form buttons wrapper to reply form footer element
  1234. $reply_form_footer->appendChild ($reply_form_buttons_wrapper);
  1235. // Add reply form footer to reply form element
  1236. $reply_form->appendChild ($reply_form_footer);
  1237. return $reply_form->asHTML ();
  1238. }
  1239. public function editForm ($permalink, $file, $name = '', $website = '', $body, $status = '', $subscribed = true)
  1240. {
  1241. // "Edit Comment" locale string
  1242. $edit_comment = $this->locale->get ('edit-comment');
  1243. // "Save Edit" locale string
  1244. $save_edit = $this->locale->get ('save-edit');
  1245. // "Cancel" locale string
  1246. $cancel_edit = $this->locale->get ('cancel');
  1247. // "Delete" locale string
  1248. $delete_comment = $this->locale->get ('delete');
  1249. // Create wrapper element
  1250. $edit_form = new HTMLTag ('div');
  1251. // Create edit form title element
  1252. $edit_form_title = new HTMLTag ('div', array (
  1253. 'class' => 'hashover-title hashover-dashed-title',
  1254. 'innerHTML' => $edit_comment
  1255. ), false);
  1256. if ($this->login->userIsAdmin === true) {
  1257. // Create status dropdown wrapper element
  1258. $edit_status_wrapper = new HTMLTag ('span', array (
  1259. 'class' => 'hashover-edit-status',
  1260. 'innerHTML' => $this->locale->get ('status')
  1261. ), false);
  1262. // Create select wrapper element
  1263. $edit_status_select_wrapper = new HTMLTag ('span', array (
  1264. 'class' => 'hashover-select-wrapper'
  1265. ), false);
  1266. // Status dropdown menu options
  1267. $status_options = array (
  1268. 'approved' => $this->locale->get ('status-approved'),
  1269. 'pending' => $this->locale->get ('status-pending'),
  1270. 'deleted' => $this->locale->get ('status-deleted')
  1271. );
  1272. // Create status dropdown menu element
  1273. $edit_status_dropdown = new HTMLTag ('select', array (
  1274. 'id' => 'hashover-edit-status-' . $this->injectVar ($permalink),
  1275. 'name' => 'status',
  1276. 'size' => '1'
  1277. ));
  1278. foreach ($status_options as $value => $inner_html) {
  1279. // Create status dropdown menu option element
  1280. $edit_status_option = new HTMLTag ('option', array (
  1281. 'value' => $value,
  1282. 'innerHTML' => $inner_html
  1283. ));
  1284. // Set option as selected if it matches the comment status given
  1285. if ($value === $status) {
  1286. $edit_status_option->createAttribute ('selected', 'true');
  1287. }
  1288. // Add option element to status dropdown menu
  1289. $edit_status_dropdown->appendChild ($edit_status_option);
  1290. }
  1291. // Add status dropdown menu to select wrapper element
  1292. $edit_status_select_wrapper->appendChild ($edit_status_dropdown);
  1293. // Add select wrapper to status dropdown wrapper element
  1294. $edit_status_wrapper->appendChild ($edit_status_select_wrapper);
  1295. // Add status dropdown wrapper to edit form title element
  1296. $edit_form_title->appendChild ($edit_status_wrapper);
  1297. }
  1298. // Append edit form title to edit form wrapper
  1299. $edit_form->appendChild ($edit_form_title);
  1300. // Append default login inputs
  1301. $edit_login_inputs = $this->loginInputs ($permalink, true, $name, $website);
  1302. $edit_form->appendChild ($edit_login_inputs);
  1303. // Create label element for comment textarea
  1304. if ($this->setup->usesLabels === true) {
  1305. $edit_comment_label = new HTMLTag ('label', array (
  1306. 'for' => 'hashover-edit-comment-' . $this->injectVar ($permalink),
  1307. 'class' => 'hashover-comment-label',
  1308. 'innerHTML' => $this->locale->get ('edit-your-comment')
  1309. ), false);
  1310. // Add comment label to form element
  1311. $edit_form->appendChild ($edit_comment_label);
  1312. }
  1313. // Comment form placeholder text
  1314. $edit_placeholder = $this->locale->get ('comment-form');
  1315. // Edit form textarea text value
  1316. $edit_body = $this->injectVar ($body);
  1317. // Create edit textarea element and add it to form element
  1318. $this->commentForm ($edit_form, 'edit', $edit_placeholder, $edit_body, $permalink);
  1319. // Add page info fields to edit form
  1320. $this->pageInfoFields ($edit_form);
  1321. // Create hidden comment file input element
  1322. $edit_file_input = new HTMLTag ('input', array (
  1323. 'type' => 'hidden',
  1324. 'name' => 'file',
  1325. 'value' => $this->injectVar ($file)
  1326. ), false, true);
  1327. // Add hidden page title input element to form element
  1328. $edit_form->appendChild ($edit_file_input);
  1329. // Create wrapper element for edit form buttons
  1330. $edit_form_footer = new HTMLTag ('div', array (
  1331. 'class' => 'hashover-form-footer'
  1332. ));
  1333. // Create wrapper for form links
  1334. $edit_form_links_wrapper = new HTMLTag ('span', array (
  1335. 'class' => 'hashover-form-links'
  1336. ));
  1337. // Add checkbox label element to edit form buttons wrapper element
  1338. if ($this->setup->fieldOptions['email'] !== false) {
  1339. $edit_form_links_wrapper->appendChild ($this->subscribeLabel ($permalink, 'edit', $subscribed));
  1340. }
  1341. // Create and add accepted HTML revealer hyperlink
  1342. if ($this->mode === 'javascript') {
  1343. $edit_form_links_wrapper->appendChild ($this->acceptedHTML ('edit', $permalink));
  1344. }
  1345. // Add edit form links wrapper to edit form footer element
  1346. $edit_form_footer->appendChild ($edit_form_links_wrapper);
  1347. // Create wrapper for form buttons
  1348. $edit_form_buttons_wrapper = new HTMLTag ('span', array (
  1349. 'class' => 'hashover-form-buttons'
  1350. ));
  1351. // Create "Cancel" link element
  1352. if ($this->setup->usesCancelButtons === true) {
  1353. // Add "Cancel" hyperlink element to edit form footer element
  1354. $edit_cancel_button = $this->cancelButton ('edit', $permalink);
  1355. $edit_form_buttons_wrapper->appendChild ($edit_cancel_button);
  1356. }
  1357. // Create "Post Comment" button element
  1358. $save_edit_button = new HTMLTag ('input', array (), false, true);
  1359. // Add ID attribute with JavaScript variable single quote break out
  1360. if (!empty ($permalink)) {
  1361. $save_edit_button->createAttribute ('id', 'hashover-edit-post-' . $this->injectVar ($permalink));
  1362. }
  1363. // Continue with other attributes
  1364. $save_edit_button->createAttributes (array (
  1365. 'class' => 'hashover-submit hashover-edit-post',
  1366. 'type' => 'submit',
  1367. 'name' => 'edit',
  1368. 'value' => $save_edit,
  1369. 'title' => $save_edit
  1370. ));
  1371. // Add "Save Edit" element to edit form footer element
  1372. $edit_form_buttons_wrapper->appendChild ($save_edit_button);
  1373. // Create "Delete" button element
  1374. $delete_button = new HTMLTag ('input', array (), false, true);
  1375. // Add ID attribute with JavaScript variable single quote break out
  1376. if (!empty ($permalink)) {
  1377. $delete_button->createAttribute ('id', 'hashover-edit-delete-' . $this->injectVar ($permalink));
  1378. }
  1379. // Continue with other attributes
  1380. $delete_button->createAttributes (array (
  1381. 'class' => 'hashover-submit hashover-edit-delete',
  1382. 'type' => 'submit',
  1383. 'name' => 'delete',
  1384. 'value' => $delete_comment,
  1385. 'title' => $delete_comment
  1386. ));
  1387. // Add "Delete" element to edit form footer element
  1388. $edit_form_buttons_wrapper->appendChild ($delete_button);
  1389. // Add edit form buttons wrapper to edit form footer element
  1390. $edit_form_footer->appendChild ($edit_form_buttons_wrapper);
  1391. // Add form buttons to edit form element
  1392. $edit_form->appendChild ($edit_form_footer);
  1393. return $edit_form->innerHTML;
  1394. }
  1395. public function asJSVar ($html, $var_name, $indent = "\t")
  1396. {
  1397. // Check if JavaScript minification is enabled
  1398. if ($this->setup->minifiesJavaScript === true and $this->setup->minifyLevel >= 3) {
  1399. // If so, remove whitespace collapsing code to a single line
  1400. $html = str_replace (array ("\t", PHP_EOL), array ('', ' '), $html);
  1401. } else {
  1402. // If not, convert literal tabs to JavaScript tabs
  1403. $html = str_replace ("\t", '\t', $html);
  1404. }
  1405. // Trim newlines from start of end of input HTML
  1406. $html = trim ($html, "\r\n");
  1407. // Split the HTML into an array of lines
  1408. $lines = explode (PHP_EOL, $html);
  1409. $line_count = count ($lines);
  1410. // Initial output HTML
  1411. $var = '';
  1412. if ($line_count > 0) {
  1413. // Variable declaration code line
  1414. $var .= $indent . 'var ' . $var_name . ' = \'' . $lines[0];
  1415. $var .= (($line_count > 1) ? '\n' : '') . '\';' . PHP_EOL;
  1416. // Run through the rest of the lines
  1417. for ($i = 1; $i < $line_count; $i++) {
  1418. // Skip empty lines
  1419. if (trim ($lines[$i]) === '') {
  1420. continue;
  1421. }
  1422. // Append indentation
  1423. $var .= $indent;
  1424. // Append variable concatenation code
  1425. $var .= ' ' . $var_name . ' += \'';
  1426. // Append the current line
  1427. $var .= $lines[$i];
  1428. // And close concatenation code
  1429. $var .= '\n\';' . PHP_EOL;
  1430. }
  1431. }
  1432. return $var;
  1433. }
  1434. }