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.
 
 
 
 
 
 

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