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.
 
 
 
 
 
 

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