Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

927 lines
26 KiB

  1. <?php
  2. /**
  3. * @file src/Object/Post.php
  4. */
  5. namespace Friendica\Object;
  6. use Friendica\BaseObject;
  7. use Friendica\Content\ContactSelector;
  8. use Friendica\Content\Feature;
  9. use Friendica\Core\Addon;
  10. use Friendica\Core\Config;
  11. use Friendica\Core\L10n;
  12. use Friendica\Core\Logger;
  13. use Friendica\Core\PConfig;
  14. use Friendica\Core\Protocol;
  15. use Friendica\Core\Renderer;
  16. use Friendica\Database\DBA;
  17. use Friendica\Model\Contact;
  18. use Friendica\Model\Item;
  19. use Friendica\Model\Term;
  20. use Friendica\Util\Crypto;
  21. use Friendica\Util\DateTimeFormat;
  22. use Friendica\Util\Proxy as ProxyUtils;
  23. use Friendica\Util\Strings;
  24. use Friendica\Util\Temporal;
  25. /**
  26. * An item
  27. */
  28. class Post extends BaseObject
  29. {
  30. private $data = [];
  31. private $template = null;
  32. private $available_templates = [
  33. 'wall' => 'wall_thread.tpl',
  34. 'wall2wall' => 'wallwall_thread.tpl'
  35. ];
  36. private $comment_box_template = 'comment_item.tpl';
  37. private $toplevel = false;
  38. private $writable = false;
  39. private $children = [];
  40. private $parent = null;
  41. private $thread = null;
  42. private $redirect_url = null;
  43. private $owner_url = '';
  44. private $owner_photo = '';
  45. private $owner_name = '';
  46. private $wall_to_wall = false;
  47. private $threaded = false;
  48. private $visiting = false;
  49. /**
  50. * Constructor
  51. *
  52. * @param array $data data array
  53. */
  54. public function __construct(array $data)
  55. {
  56. $a = self::getApp();
  57. $this->data = $data;
  58. $this->setTemplate('wall');
  59. $this->toplevel = $this->getId() == $this->getDataValue('parent');
  60. if (!empty($_SESSION['remote']) && is_array($_SESSION['remote'])) {
  61. foreach ($_SESSION['remote'] as $visitor) {
  62. if ($visitor['cid'] == $this->getDataValue('contact-id')) {
  63. $this->visiting = true;
  64. break;
  65. }
  66. }
  67. }
  68. $this->writable = $this->getDataValue('writable') || $this->getDataValue('self');
  69. $author = ['uid' => 0, 'id' => $this->getDataValue('author-id'),
  70. 'network' => $this->getDataValue('author-network'),
  71. 'url' => $this->getDataValue('author-link')];
  72. $this->redirect_url = Contact::magicLinkbyContact($author);
  73. if (!$this->isToplevel()) {
  74. $this->threaded = true;
  75. }
  76. // Prepare the children
  77. if (!empty($data['children'])) {
  78. foreach ($data['children'] as $item) {
  79. // Only add will be displayed
  80. if ($item['network'] === Protocol::MAIL && local_user() != $item['uid']) {
  81. continue;
  82. } elseif (!visible_activity($item)) {
  83. continue;
  84. }
  85. // You can always comment on Diaspora and OStatus items
  86. if (in_array($item['network'], [Protocol::OSTATUS, Protocol::DIASPORA]) && (local_user() == $item['uid'])) {
  87. $item['writable'] = true;
  88. }
  89. $item['pagedrop'] = $data['pagedrop'];
  90. $child = new Post($item);
  91. $this->addChild($child);
  92. }
  93. }
  94. }
  95. /**
  96. * Get data in a form usable by a conversation template
  97. *
  98. * @param object $conv_responses conversation responses
  99. * @param integer $thread_level default = 1
  100. *
  101. * @return mixed The data requested on success
  102. * false on failure
  103. */
  104. public function getTemplateData($conv_responses, $thread_level = 1)
  105. {
  106. $result = [];
  107. $a = self::getApp();
  108. $item = $this->getData();
  109. $edited = false;
  110. // If the time between "created" and "edited" differs we add
  111. // a notice that the post was edited.
  112. // Note: In some networks reshared items seem to have (sometimes) a difference
  113. // between creation time and edit time of a second. Thats why we add the notice
  114. // only if the difference is more than 1 second.
  115. if (strtotime($item['edited']) - strtotime($item['created']) > 1) {
  116. $edited = [
  117. 'label' => L10n::t('This entry was edited'),
  118. 'date' => DateTimeFormat::local($item['edited'], 'r'),
  119. 'relative' => Temporal::getRelativeDate($item['edited'])
  120. ];
  121. }
  122. $commentww = '';
  123. $sparkle = '';
  124. $buttons = '';
  125. $dropping = false;
  126. $star = false;
  127. $ignore = false;
  128. $isstarred = "unstarred";
  129. $indent = '';
  130. $shiny = '';
  131. $osparkle = '';
  132. $total_children = $this->countDescendants();
  133. $conv = $this->getThread();
  134. $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid'])
  135. || strlen($item['deny_cid']) || strlen($item['deny_gid']))))
  136. ? L10n::t('Private Message')
  137. : false);
  138. $shareable = in_array($conv->getProfileOwner(), [0, local_user()]) && $item['private'] != 1;
  139. $edpost = false;
  140. if (local_user()) {
  141. if (Strings::compareLink($a->contact['url'], $item['author-link'])) {
  142. if ($item["event-id"] != 0) {
  143. $edpost = ["events/event/" . $item['event-id'], L10n::t("Edit")];
  144. } else {
  145. $edpost = ["editpost/" . $item['id'], L10n::t("Edit")];
  146. }
  147. }
  148. $dropping = in_array($item['uid'], [0, local_user()]);
  149. }
  150. // Editing on items of not subscribed users isn't currently possible
  151. // There are some issues on editing that prevent this.
  152. // But also it is an issue of the supported protocols that doesn't allow editing at all.
  153. if ($item['uid'] == 0) {
  154. $edpost = false;
  155. }
  156. if (($this->getDataValue('uid') == local_user()) || $this->isVisiting()) {
  157. $dropping = true;
  158. }
  159. $origin = $item['origin'];
  160. if (!$origin) {
  161. /// @todo This shouldn't be done as query here, but better during the data creation.
  162. // it is now done here, since during the RC phase we shouldn't make to intense changes.
  163. $parent = Item::selectFirst(['origin'], ['id' => $item['parent']]);
  164. if (DBA::isResult($parent)) {
  165. $origin = $parent['origin'];
  166. }
  167. }
  168. if ($origin && ($item['id'] != $item['parent']) && ($item['network'] == Protocol::ACTIVITYPUB)) {
  169. // ActivityPub doesn't allow removal of remote comments
  170. $delete = L10n::t('Delete locally');
  171. } else {
  172. // Showing the one or the other text, depending upon if we can only hide it or really delete it.
  173. $delete = $origin ? L10n::t('Delete globally') : L10n::t('Remove locally');
  174. }
  175. $drop = [
  176. 'dropping' => $dropping,
  177. 'pagedrop' => $item['pagedrop'],
  178. 'select' => L10n::t('Select'),
  179. 'delete' => $delete,
  180. ];
  181. if (!local_user()) {
  182. $drop = false;
  183. }
  184. $filer = (($conv->getProfileOwner() == local_user() && ($item['uid'] != 0)) ? L10n::t("save to folder") : false);
  185. $profile_name = $item['author-name'];
  186. if (!empty($item['author-link']) && empty($item['author-name'])) {
  187. $profile_name = $item['author-link'];
  188. }
  189. $author = ['uid' => 0, 'id' => $item['author-id'],
  190. 'network' => $item['author-network'], 'url' => $item['author-link']];
  191. if (local_user() || remote_user()) {
  192. $profile_link = Contact::magicLinkbyContact($author);
  193. } else {
  194. $profile_link = $item['author-link'];
  195. }
  196. if (strpos($profile_link, 'redir/') === 0) {
  197. $sparkle = ' sparkle';
  198. }
  199. $locate = ['location' => $item['location'], 'coord' => $item['coord'], 'html' => ''];
  200. Addon::callHooks('render_location', $locate);
  201. $location = ((strlen($locate['html'])) ? $locate['html'] : render_location_dummy($locate));
  202. // process action responses - e.g. like/dislike/attend/agree/whatever
  203. $response_verbs = ['like', 'dislike'];
  204. $isevent = false;
  205. $attend = [];
  206. if ($item['object-type'] === ACTIVITY_OBJ_EVENT) {
  207. $response_verbs[] = 'attendyes';
  208. $response_verbs[] = 'attendno';
  209. $response_verbs[] = 'attendmaybe';
  210. if ($conv->isWritable()) {
  211. $isevent = true;
  212. $attend = [L10n::t('I will attend'), L10n::t('I will not attend'), L10n::t('I might attend')];
  213. }
  214. }
  215. $responses = get_responses($conv_responses, $response_verbs, $this, $item);
  216. foreach ($response_verbs as $value => $verbs) {
  217. $responses[$verbs]['output'] = !empty($conv_responses[$verbs][$item['uri']]) ? format_like($conv_responses[$verbs][$item['uri']], $conv_responses[$verbs][$item['uri'] . '-l'], $verbs, $item['uri']) : '';
  218. }
  219. /*
  220. * We should avoid doing this all the time, but it depends on the conversation mode
  221. * And the conv mode may change when we change the conv, or it changes its mode
  222. * Maybe we should establish a way to be notified about conversation changes
  223. */
  224. $this->checkWallToWall();
  225. if ($this->isWallToWall() && ($this->getOwnerUrl() == $this->getRedirectUrl())) {
  226. $osparkle = ' sparkle';
  227. }
  228. $tagger = '';
  229. if ($this->isToplevel()) {
  230. if(local_user()) {
  231. $thread = Item::selectFirstThreadForUser(local_user(), ['ignored'], ['iid' => $item['id']]);
  232. if (DBA::isResult($thread)) {
  233. $ignore = [
  234. 'do' => L10n::t("ignore thread"),
  235. 'undo' => L10n::t("unignore thread"),
  236. 'toggle' => L10n::t("toggle ignore status"),
  237. 'classdo' => $thread['ignored'] ? "hidden" : "",
  238. 'classundo' => $thread['ignored'] ? "" : "hidden",
  239. 'ignored' => L10n::t('ignored'),
  240. ];
  241. }
  242. if ($conv->getProfileOwner() == local_user() && ($item['uid'] != 0)) {
  243. $isstarred = (($item['starred']) ? "starred" : "unstarred");
  244. $star = [
  245. 'do' => L10n::t("add star"),
  246. 'undo' => L10n::t("remove star"),
  247. 'toggle' => L10n::t("toggle star status"),
  248. 'classdo' => $item['starred'] ? "hidden" : "",
  249. 'classundo' => $item['starred'] ? "" : "hidden",
  250. 'starred' => L10n::t('starred'),
  251. ];
  252. $tagger = [
  253. 'add' => L10n::t("add tag"),
  254. 'class' => "",
  255. ];
  256. }
  257. }
  258. } else {
  259. $indent = 'comment';
  260. }
  261. if ($conv->isWritable()) {
  262. $buttons = [
  263. 'like' => [L10n::t("I like this \x28toggle\x29"), L10n::t("like")],
  264. 'dislike' => [L10n::t("I don't like this \x28toggle\x29"), L10n::t("dislike")],
  265. ];
  266. if ($shareable) {
  267. $buttons['share'] = [L10n::t('Share this'), L10n::t('share')];
  268. }
  269. }
  270. $comment = $this->getCommentBox($indent);
  271. if (strcmp(DateTimeFormat::utc($item['created']), DateTimeFormat::utc('now - 12 hours')) > 0) {
  272. $shiny = 'shiny';
  273. }
  274. localize_item($item);
  275. $body = Item::prepareBody($item, true);
  276. list($categories, $folders) = get_cats_and_terms($item);
  277. $body_e = $body;
  278. $text_e = strip_tags($body);
  279. $name_e = $profile_name;
  280. if (!empty($item['content-warning']) && PConfig::get(local_user(), 'system', 'disable_cw', false)) {
  281. $title_e = ucfirst($item['content-warning']);
  282. } else {
  283. $title_e = $item['title'];
  284. }
  285. $location_e = $location;
  286. $owner_name_e = $this->getOwnerName();
  287. // Disable features that aren't available in several networks
  288. if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA]) && isset($buttons["dislike"])) {
  289. unset($buttons["dislike"]);
  290. $isevent = false;
  291. $tagger = '';
  292. }
  293. if (($item["network"] == Protocol::FEED) && isset($buttons["like"])) {
  294. unset($buttons["like"]);
  295. }
  296. if (($item["network"] == Protocol::MAIL) && isset($buttons["like"])) {
  297. unset($buttons["like"]);
  298. }
  299. $tags = Term::populateTagsFromItem($item);
  300. $tmp_item = [
  301. 'template' => $this->getTemplate(),
  302. 'type' => implode("", array_slice(explode("/", $item['verb']), -1)),
  303. 'suppress_tags' => Config::get('system', 'suppress_tags'),
  304. 'tags' => $tags['tags'],
  305. 'hashtags' => $tags['hashtags'],
  306. 'mentions' => $tags['mentions'],
  307. 'txt_cats' => L10n::t('Categories:'),
  308. 'txt_folders' => L10n::t('Filed under:'),
  309. 'has_cats' => ((count($categories)) ? 'true' : ''),
  310. 'has_folders' => ((count($folders)) ? 'true' : ''),
  311. 'categories' => $categories,
  312. 'folders' => $folders,
  313. 'body' => $body_e,
  314. 'text' => $text_e,
  315. 'id' => $this->getId(),
  316. 'guid' => urlencode($item['guid']),
  317. 'isevent' => $isevent,
  318. 'attend' => $attend,
  319. 'linktitle' => L10n::t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
  320. 'olinktitle' => L10n::t('View %s\'s profile @ %s', $this->getOwnerName(), $item['owner-link']),
  321. 'to' => L10n::t('to'),
  322. 'via' => L10n::t('via'),
  323. 'wall' => L10n::t('Wall-to-Wall'),
  324. 'vwall' => L10n::t('via Wall-To-Wall:'),
  325. 'profile_url' => $profile_link,
  326. 'item_photo_menu' => item_photo_menu($item),
  327. 'name' => $name_e,
  328. 'thumb' => $a->removeBaseURL(ProxyUtils::proxifyUrl($item['author-avatar'], false, ProxyUtils::SIZE_THUMB)),
  329. 'osparkle' => $osparkle,
  330. 'sparkle' => $sparkle,
  331. 'title' => $title_e,
  332. 'localtime' => DateTimeFormat::local($item['created'], 'r'),
  333. 'ago' => $item['app'] ? L10n::t('%s from %s', Temporal::getRelativeDate($item['created']), $item['app']) : Temporal::getRelativeDate($item['created']),
  334. 'app' => $item['app'],
  335. 'created' => Temporal::getRelativeDate($item['created']),
  336. 'lock' => $lock,
  337. 'location' => $location_e,
  338. 'indent' => $indent,
  339. 'shiny' => $shiny,
  340. 'owner_url' => $this->getOwnerUrl(),
  341. 'owner_photo' => $a->removeBaseURL(ProxyUtils::proxifyUrl($item['owner-avatar'], false, ProxyUtils::SIZE_THUMB)),
  342. 'owner_name' => $owner_name_e,
  343. 'plink' => Item::getPlink($item),
  344. 'edpost' => $edpost,
  345. 'isstarred' => $isstarred,
  346. 'star' => $star,
  347. 'ignore' => $ignore,
  348. 'tagger' => $tagger,
  349. 'filer' => $filer,
  350. 'drop' => $drop,
  351. 'vote' => $buttons,
  352. 'like' => $responses['like']['output'],
  353. 'dislike' => $responses['dislike']['output'],
  354. 'responses' => $responses,
  355. 'switchcomment' => L10n::t('Comment'),
  356. 'comment' => $comment,
  357. 'previewing' => $conv->isPreview() ? ' preview ' : '',
  358. 'wait' => L10n::t('Please wait'),
  359. 'thread_level' => $thread_level,
  360. 'edited' => $edited,
  361. 'network' => $item["network"],
  362. 'network_name' => ContactSelector::networkToName($item['network'], $item['author-link']),
  363. 'received' => $item['received'],
  364. 'commented' => $item['commented'],
  365. 'created_date' => $item['created'],
  366. 'return' => ($a->cmd) ? bin2hex($a->cmd) : '',
  367. ];
  368. $arr = ['item' => $item, 'output' => $tmp_item];
  369. Addon::callHooks('display_item', $arr);
  370. $result = $arr['output'];
  371. $result['children'] = [];
  372. $children = $this->getChildren();
  373. $nb_children = count($children);
  374. if ($nb_children > 0) {
  375. foreach ($children as $child) {
  376. $result['children'][] = $child->getTemplateData($conv_responses, $thread_level + 1);
  377. }
  378. // Collapse
  379. if (($nb_children > 2) || ($thread_level > 1)) {
  380. $result['children'][0]['comment_firstcollapsed'] = true;
  381. $result['children'][0]['num_comments'] = L10n::tt('%d comment', '%d comments', $total_children);
  382. $result['children'][0]['hidden_comments_num'] = $total_children;
  383. $result['children'][0]['hidden_comments_text'] = L10n::tt('comment', 'comments', $total_children);
  384. $result['children'][0]['hide_text'] = L10n::t('show more');
  385. if ($thread_level > 1) {
  386. $result['children'][$nb_children - 1]['comment_lastcollapsed'] = true;
  387. } else {
  388. $result['children'][$nb_children - 3]['comment_lastcollapsed'] = true;
  389. }
  390. }
  391. }
  392. if ($this->isToplevel()) {
  393. $result['total_comments_num'] = "$total_children";
  394. $result['total_comments_text'] = L10n::tt('comment', 'comments', $total_children);
  395. }
  396. $result['private'] = $item['private'];
  397. $result['toplevel'] = ($this->isToplevel() ? 'toplevel_item' : '');
  398. if ($this->isThreaded()) {
  399. $result['flatten'] = false;
  400. $result['threaded'] = true;
  401. } else {
  402. $result['flatten'] = true;
  403. $result['threaded'] = false;
  404. }
  405. return $result;
  406. }
  407. /**
  408. * @return integer
  409. */
  410. public function getId()
  411. {
  412. return $this->getDataValue('id');
  413. }
  414. /**
  415. * @return boolean
  416. */
  417. public function isThreaded()
  418. {
  419. return $this->threaded;
  420. }
  421. /**
  422. * Add a child item
  423. *
  424. * @param object $item The child item to add
  425. *
  426. * @return mixed
  427. */
  428. public function addChild(Post $item)
  429. {
  430. $item_id = $item->getId();
  431. if (!$item_id) {
  432. Logger::log('[ERROR] Post::addChild : Item has no ID!!', Logger::DEBUG);
  433. return false;
  434. } elseif ($this->getChild($item->getId())) {
  435. Logger::log('[WARN] Post::addChild : Item already exists (' . $item->getId() . ').', Logger::DEBUG);
  436. return false;
  437. }
  438. /*
  439. * Only add what will be displayed
  440. */
  441. if ($item->getDataValue('network') === Protocol::MAIL && local_user() != $item->getDataValue('uid')) {
  442. return false;
  443. } elseif (activity_match($item->getDataValue('verb'), ACTIVITY_LIKE) || activity_match($item->getDataValue('verb'), ACTIVITY_DISLIKE)) {
  444. return false;
  445. }
  446. $item->setParent($this);
  447. $this->children[] = $item;
  448. return end($this->children);
  449. }
  450. /**
  451. * Get a child by its ID
  452. *
  453. * @param integer $id The child id
  454. *
  455. * @return mixed
  456. */
  457. public function getChild($id)
  458. {
  459. foreach ($this->getChildren() as $child) {
  460. if ($child->getId() == $id) {
  461. return $child;
  462. }
  463. }
  464. return null;
  465. }
  466. /**
  467. * Get all our children
  468. *
  469. * @return object
  470. */
  471. public function getChildren()
  472. {
  473. return $this->children;
  474. }
  475. /**
  476. * Set our parent
  477. *
  478. * @param object $item The item to set as parent
  479. *
  480. * @return void
  481. */
  482. protected function setParent($item)
  483. {
  484. $parent = $this->getParent();
  485. if ($parent) {
  486. $parent->removeChild($this);
  487. }
  488. $this->parent = $item;
  489. $this->setThread($item->getThread());
  490. }
  491. /**
  492. * Remove our parent
  493. *
  494. * @return void
  495. */
  496. protected function removeParent()
  497. {
  498. $this->parent = null;
  499. $this->thread = null;
  500. }
  501. /**
  502. * Remove a child
  503. *
  504. * @param object $item The child to be removed
  505. *
  506. * @return boolean Success or failure
  507. */
  508. public function removeChild($item)
  509. {
  510. $id = $item->getId();
  511. foreach ($this->getChildren() as $key => $child) {
  512. if ($child->getId() == $id) {
  513. $child->removeParent();
  514. unset($this->children[$key]);
  515. // Reindex the array, in order to make sure there won't be any trouble on loops using count()
  516. $this->children = array_values($this->children);
  517. return true;
  518. }
  519. }
  520. Logger::log('[WARN] Item::removeChild : Item is not a child (' . $id . ').', Logger::DEBUG);
  521. return false;
  522. }
  523. /**
  524. * Get parent item
  525. *
  526. * @return object
  527. */
  528. protected function getParent()
  529. {
  530. return $this->parent;
  531. }
  532. /**
  533. * Set conversation
  534. *
  535. * @param object $conv The conversation
  536. *
  537. * @return void
  538. */
  539. public function setThread($conv)
  540. {
  541. $previous_mode = ($this->thread ? $this->thread->getMode() : '');
  542. $this->thread = $conv;
  543. // Set it on our children too
  544. foreach ($this->getChildren() as $child) {
  545. $child->setThread($conv);
  546. }
  547. }
  548. /**
  549. * Get conversation
  550. *
  551. * @return object
  552. */
  553. public function getThread()
  554. {
  555. return $this->thread;
  556. }
  557. /**
  558. * Get raw data
  559. *
  560. * We shouldn't need this
  561. *
  562. * @return array
  563. */
  564. public function getData()
  565. {
  566. return $this->data;
  567. }
  568. /**
  569. * Get a data value
  570. *
  571. * @param object $name key
  572. *
  573. * @return mixed value on success
  574. * false on failure
  575. */
  576. public function getDataValue($name)
  577. {
  578. if (!isset($this->data[$name])) {
  579. // Logger::log('[ERROR] Item::getDataValue : Item has no value name "'. $name .'".', Logger::DEBUG);
  580. return false;
  581. }
  582. return $this->data[$name];
  583. }
  584. /**
  585. * Set template
  586. *
  587. * @param object $name template name
  588. *
  589. * @return void
  590. */
  591. private function setTemplate($name)
  592. {
  593. if (empty($this->available_templates[$name])) {
  594. Logger::log('[ERROR] Item::setTemplate : Template not available ("' . $name . '").', Logger::DEBUG);
  595. return false;
  596. }
  597. $this->template = $this->available_templates[$name];
  598. }
  599. /**
  600. * Get template
  601. *
  602. * @return object
  603. */
  604. private function getTemplate()
  605. {
  606. return $this->template;
  607. }
  608. /**
  609. * Check if this is a toplevel post
  610. *
  611. * @return boolean
  612. */
  613. private function isToplevel()
  614. {
  615. return $this->toplevel;
  616. }
  617. /**
  618. * Check if this is writable
  619. *
  620. * @return boolean
  621. */
  622. private function isWritable()
  623. {
  624. $conv = $this->getThread();
  625. if ($conv) {
  626. // This will allow us to comment on wall-to-wall items owned by our friends
  627. // and community forums even if somebody else wrote the post.
  628. // bug #517 - this fixes for conversation owner
  629. if ($conv->getMode() == 'profile' && $conv->getProfileOwner() == local_user()) {
  630. return true;
  631. }
  632. // this fixes for visitors
  633. return ($this->writable || ($this->isVisiting() && $conv->getMode() == 'profile'));
  634. }
  635. return $this->writable;
  636. }
  637. /**
  638. * Count the total of our descendants
  639. *
  640. * @return integer
  641. */
  642. private function countDescendants()
  643. {
  644. $children = $this->getChildren();
  645. $total = count($children);
  646. if ($total > 0) {
  647. foreach ($children as $child) {
  648. $total += $child->countDescendants();
  649. }
  650. }
  651. return $total;
  652. }
  653. /**
  654. * Get the template for the comment box
  655. *
  656. * @return string
  657. */
  658. private function getCommentBoxTemplate()
  659. {
  660. return $this->comment_box_template;
  661. }
  662. /**
  663. * Get the comment box
  664. *
  665. * @param string $indent Indent value
  666. *
  667. * @return mixed The comment box string (empty if no comment box)
  668. * false on failure
  669. */
  670. private function getCommentBox($indent)
  671. {
  672. $a = self::getApp();
  673. $comment_box = '';
  674. $conv = $this->getThread();
  675. $ww = '';
  676. if (($conv->getMode() === 'network') && $this->isWallToWall()) {
  677. $ww = 'ww';
  678. }
  679. if ($conv->isWritable() && $this->isWritable()) {
  680. $qc = $qcomment = null;
  681. /*
  682. * Hmmm, code depending on the presence of a particular addon?
  683. * This should be better if done by a hook
  684. */
  685. if (Addon::isEnabled('qcomment')) {
  686. $qc = ((local_user()) ? PConfig::get(local_user(), 'qcomment', 'words') : null);
  687. $qcomment = (($qc) ? explode("\n", $qc) : null);
  688. }
  689. // Fetch the user id from the parent when the owner user is empty
  690. $uid = $conv->getProfileOwner();
  691. $parent_uid = $this->getDataValue('uid');
  692. if (!is_null($parent_uid) && ($uid != $parent_uid)) {
  693. $uid = $parent_uid;
  694. }
  695. $template = Renderer::getMarkupTemplate($this->getCommentBoxTemplate());
  696. $comment_box = Renderer::replaceMacros($template, [
  697. '$return_path' => $a->query_string,
  698. '$threaded' => $this->isThreaded(),
  699. '$jsreload' => '',
  700. '$wall' => ($conv->getMode() === 'profile'),
  701. '$id' => $this->getId(),
  702. '$parent' => $this->getId(),
  703. '$qcomment' => $qcomment,
  704. '$profile_uid' => $uid,
  705. '$mylink' => $a->removeBaseURL($a->contact['url']),
  706. '$mytitle' => L10n::t('This is you'),
  707. '$myphoto' => $a->removeBaseURL($a->contact['thumb']),
  708. '$comment' => L10n::t('Comment'),
  709. '$submit' => L10n::t('Submit'),
  710. '$edbold' => L10n::t('Bold'),
  711. '$editalic' => L10n::t('Italic'),
  712. '$eduline' => L10n::t('Underline'),
  713. '$edquote' => L10n::t('Quote'),
  714. '$edcode' => L10n::t('Code'),
  715. '$edimg' => L10n::t('Image'),
  716. '$edurl' => L10n::t('Link'),
  717. '$edattach' => L10n::t('Link or Media'),
  718. '$prompttext' => L10n::t('Please enter a image/video/audio/webpage URL:'),
  719. '$preview' => L10n::t('Preview'),
  720. '$indent' => $indent,
  721. '$sourceapp' => L10n::t($a->sourcename),
  722. '$ww' => $conv->getMode() === 'network' ? $ww : '',
  723. '$rand_num' => Crypto::randomDigits(12)
  724. ]);
  725. }
  726. return $comment_box;
  727. }
  728. /**
  729. * @return string
  730. */
  731. private function getRedirectUrl()
  732. {
  733. return $this->redirect_url;
  734. }
  735. /**
  736. * Check if we are a wall to wall item and set the relevant properties
  737. *
  738. * @return void
  739. */
  740. protected function checkWallToWall()
  741. {
  742. $a = self::getApp();
  743. $conv = $this->getThread();
  744. $this->wall_to_wall = false;
  745. if ($this->isToplevel()) {
  746. if ($conv->getMode() !== 'profile') {
  747. if ($this->getDataValue('wall') && !$this->getDataValue('self')) {
  748. // On the network page, I am the owner. On the display page it will be the profile owner.
  749. // This will have been stored in $a->page_contact by our calling page.
  750. // Put this person as the wall owner of the wall-to-wall notice.
  751. $this->owner_url = Contact::magicLink($a->page_contact['url']);
  752. $this->owner_photo = $a->page_contact['thumb'];
  753. $this->owner_name = $a->page_contact['name'];
  754. $this->wall_to_wall = true;
  755. } elseif ($this->getDataValue('owner-link')) {
  756. $owner_linkmatch = (($this->getDataValue('owner-link')) && Strings::compareLink($this->getDataValue('owner-link'), $this->getDataValue('author-link')));
  757. $alias_linkmatch = (($this->getDataValue('alias')) && Strings::compareLink($this->getDataValue('alias'), $this->getDataValue('author-link')));
  758. $owner_namematch = (($this->getDataValue('owner-name')) && $this->getDataValue('owner-name') == $this->getDataValue('author-name'));
  759. if (!$owner_linkmatch && !$alias_linkmatch && !$owner_namematch) {
  760. // The author url doesn't match the owner (typically the contact)
  761. // and also doesn't match the contact alias.
  762. // The name match is a hack to catch several weird cases where URLs are
  763. // all over the park. It can be tricked, but this prevents you from
  764. // seeing "Bob Smith to Bob Smith via Wall-to-wall" and you know darn
  765. // well that it's the same Bob Smith.
  766. // But it could be somebody else with the same name. It just isn't highly likely.
  767. $this->owner_photo = $this->getDataValue('owner-avatar');
  768. $this->owner_name = $this->getDataValue('owner-name');
  769. $this->wall_to_wall = true;
  770. $owner = ['uid' => 0, 'id' => $this->getDataValue('owner-id'),
  771. 'network' => $this->getDataValue('owner-network'),
  772. 'url' => $this->getDataValue('owner-link')];
  773. $this->owner_url = Contact::magicLinkbyContact($owner);
  774. }
  775. }
  776. }
  777. }
  778. if (!$this->wall_to_wall) {
  779. $this->setTemplate('wall');
  780. $this->owner_url = '';
  781. $this->owner_photo = '';
  782. $this->owner_name = '';
  783. }
  784. }
  785. /**
  786. * @return boolean
  787. */
  788. private function isWallToWall()
  789. {
  790. return $this->wall_to_wall;
  791. }
  792. /**
  793. * @return string
  794. */
  795. private function getOwnerUrl()
  796. {
  797. return $this->owner_url;
  798. }
  799. /**
  800. * @return string
  801. */
  802. private function getOwnerName()
  803. {
  804. return $this->owner_name;
  805. }
  806. /**
  807. * @return boolean
  808. */
  809. private function isVisiting()
  810. {
  811. return $this->visiting;
  812. }
  813. }