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.

1709 lines
54KB

  1. <?php
  2. /**
  3. * @file mod/photos.php
  4. */
  5. use Friendica\App;
  6. use Friendica\Content\Feature;
  7. use Friendica\Content\Nav;
  8. use Friendica\Content\Text\BBCode;
  9. use Friendica\Core\ACL;
  10. use Friendica\Core\Addon;
  11. use Friendica\Core\Config;
  12. use Friendica\Core\L10n;
  13. use Friendica\Core\System;
  14. use Friendica\Core\Worker;
  15. use Friendica\Database\DBM;
  16. use Friendica\Model\Contact;
  17. use Friendica\Model\Group;
  18. use Friendica\Model\Item;
  19. use Friendica\Model\Photo;
  20. use Friendica\Model\Profile;
  21. use Friendica\Model\User;
  22. use Friendica\Network\Probe;
  23. use Friendica\Object\Image;
  24. use Friendica\Protocol\DFRN;
  25. use Friendica\Util\DateTimeFormat;
  26. use Friendica\Util\Map;
  27. use Friendica\Util\Temporal;
  28. require_once 'include/items.php';
  29. require_once 'include/security.php';
  30. function photos_init(App $a) {
  31. if ($a->argc > 1) {
  32. DFRN::autoRedir($a, $a->argv[1]);
  33. }
  34. if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
  35. return;
  36. }
  37. Nav::setSelected('home');
  38. if ($a->argc > 1) {
  39. $nick = $a->argv[1];
  40. $user = q("SELECT * FROM `user` WHERE `nickname` = '%s' AND `blocked` = 0 LIMIT 1",
  41. dbesc($nick)
  42. );
  43. if (!DBM::is_result($user)) {
  44. return;
  45. }
  46. $a->data['user'] = $user[0];
  47. $a->profile_uid = $user[0]['uid'];
  48. $is_owner = (local_user() && (local_user() == $a->profile_uid));
  49. $profile = Profile::getByNickname($nick, $a->profile_uid);
  50. $account_type = Contact::getAccountType($profile);
  51. $tpl = get_markup_template("vcard-widget.tpl");
  52. $vcard_widget = replace_macros($tpl, [
  53. '$name' => $profile['name'],
  54. '$photo' => $profile['photo'],
  55. '$addr' => defaults($profile, 'addr', ''),
  56. '$account_type' => $account_type,
  57. '$pdesc' => defaults($profile, 'pdesc', ''),
  58. ]);
  59. $albums = Photo::getAlbums($a->data['user']['uid']);
  60. $albums_visible = ((intval($a->data['user']['hidewall']) && !local_user() && !remote_user()) ? false : true);
  61. // add various encodings to the array so we can just loop through and pick them out in a template
  62. $ret = ['success' => false];
  63. if ($albums) {
  64. $a->data['albums'] = $albums;
  65. if ($albums_visible) {
  66. $ret['success'] = true;
  67. }
  68. $ret['albums'] = [];
  69. foreach ($albums as $k => $album) {
  70. //hide profile photos to others
  71. if (!$is_owner && !remote_user() && ($album['album'] == L10n::t('Profile Photos')))
  72. continue;
  73. $entry = [
  74. 'text' => $album['album'],
  75. 'total' => $album['total'],
  76. 'url' => 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album['album']),
  77. 'urlencode' => urlencode($album['album']),
  78. 'bin2hex' => bin2hex($album['album'])
  79. ];
  80. $ret['albums'][] = $entry;
  81. }
  82. }
  83. if (local_user() && $a->data['user']['uid'] == local_user()) {
  84. $can_post = true;
  85. }
  86. if ($ret['success']) {
  87. $photo_albums_widget = replace_macros(get_markup_template('photo_albums.tpl'), [
  88. '$nick' => $a->data['user']['nickname'],
  89. '$title' => L10n::t('Photo Albums'),
  90. '$recent' => L10n::t('Recent Photos'),
  91. '$albums' => $ret['albums'],
  92. '$baseurl' => System::baseUrl(),
  93. '$upload' => [L10n::t('Upload New Photos'), 'photos/' . $a->data['user']['nickname'] . '/upload'],
  94. '$can_post' => $can_post
  95. ]);
  96. }
  97. if (!x($a->page, 'aside')) {
  98. $a->page['aside'] = '';
  99. }
  100. $a->page['aside'] .= $vcard_widget;
  101. $a->page['aside'] .= $photo_albums_widget;
  102. $tpl = get_markup_template("photos_head.tpl");
  103. $a->page['htmlhead'] .= replace_macros($tpl,[
  104. '$ispublic' => L10n::t('everybody')
  105. ]);
  106. }
  107. return;
  108. }
  109. function photos_post(App $a)
  110. {
  111. logger('mod-photos: photos_post: begin' , LOGGER_DEBUG);
  112. logger('mod_photos: REQUEST ' . print_r($_REQUEST, true), LOGGER_DATA);
  113. logger('mod_photos: FILES ' . print_r($_FILES, true), LOGGER_DATA);
  114. $phototypes = Image::supportedTypes();
  115. $can_post = false;
  116. $visitor = 0;
  117. $page_owner_uid = $a->data['user']['uid'];
  118. $community_page = $a->data['user']['page-flags'] == PAGE_COMMUNITY;
  119. if (local_user() && (local_user() == $page_owner_uid)) {
  120. $can_post = true;
  121. } else {
  122. if ($community_page && remote_user()) {
  123. $contact_id = 0;
  124. if (x($_SESSION, 'remote') && is_array($_SESSION['remote'])) {
  125. foreach ($_SESSION['remote'] as $v) {
  126. if ($v['uid'] == $page_owner_uid) {
  127. $contact_id = $v['cid'];
  128. break;
  129. }
  130. }
  131. }
  132. if ($contact_id) {
  133. $r = q("SELECT `uid` FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `id` = %d AND `uid` = %d LIMIT 1",
  134. intval($contact_id),
  135. intval($page_owner_uid)
  136. );
  137. if (DBM::is_result($r)) {
  138. $can_post = true;
  139. $visitor = $contact_id;
  140. }
  141. }
  142. }
  143. }
  144. if (!$can_post) {
  145. notice(L10n::t('Permission denied.') . EOL);
  146. killme();
  147. }
  148. $owner_record = User::getOwnerDataById($page_owner_uid);
  149. if (!$owner_record) {
  150. notice(L10n::t('Contact information unavailable') . EOL);
  151. logger('photos_post: unable to locate contact record for page owner. uid=' . $page_owner_uid);
  152. killme();
  153. }
  154. if ($a->argc > 3 && $a->argv[2] === 'album') {
  155. $album = hex2bin($a->argv[3]);
  156. if ($album === L10n::t('Profile Photos') || $album === 'Contact Photos' || $album === L10n::t('Contact Photos')) {
  157. goaway($_SESSION['photo_return']);
  158. return; // NOTREACHED
  159. }
  160. $r = q("SELECT `album` FROM `photo` WHERE `album` = '%s' AND `uid` = %d",
  161. dbesc($album),
  162. intval($page_owner_uid)
  163. );
  164. if (!DBM::is_result($r)) {
  165. notice(L10n::t('Album not found.') . EOL);
  166. goaway($_SESSION['photo_return']);
  167. return; // NOTREACHED
  168. }
  169. // Check if the user has responded to a delete confirmation query
  170. if ($_REQUEST['canceled']) {
  171. goaway($_SESSION['photo_return']);
  172. }
  173. // RENAME photo album
  174. $newalbum = notags(trim($_POST['albumname']));
  175. if ($newalbum != $album) {
  176. q("UPDATE `photo` SET `album` = '%s' WHERE `album` = '%s' AND `uid` = %d",
  177. dbesc($newalbum),
  178. dbesc($album),
  179. intval($page_owner_uid)
  180. );
  181. // Update the photo albums cache
  182. Photo::clearAlbumCache($page_owner_uid);
  183. $newurl = str_replace(bin2hex($album), bin2hex($newalbum), $_SESSION['photo_return']);
  184. goaway($newurl);
  185. return; // NOTREACHED
  186. }
  187. /*
  188. * DELETE photo album and all its photos
  189. */
  190. if ($_POST['dropalbum'] == L10n::t('Delete Album')) {
  191. // Check if we should do HTML-based delete confirmation
  192. if (x($_REQUEST, 'confirm')) {
  193. $drop_url = $a->query_string;
  194. $extra_inputs = [
  195. ['name' => 'albumname', 'value' => $_POST['albumname']],
  196. ];
  197. $a->page['content'] = replace_macros(get_markup_template('confirm.tpl'), [
  198. '$method' => 'post',
  199. '$message' => L10n::t('Do you really want to delete this photo album and all its photos?'),
  200. '$extra_inputs' => $extra_inputs,
  201. '$confirm' => L10n::t('Delete Album'),
  202. '$confirm_url' => $drop_url,
  203. '$confirm_name' => 'dropalbum', // Needed so that confirmation will bring us back into this if statement
  204. '$cancel' => L10n::t('Cancel'),
  205. ]);
  206. $a->error = 1; // Set $a->error so the other module functions don't execute
  207. return;
  208. }
  209. $res = [];
  210. // get the list of photos we are about to delete
  211. if ($visitor) {
  212. $r = q("SELECT distinct(`resource-id`) as `rid` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d AND `album` = '%s'",
  213. intval($visitor),
  214. intval($page_owner_uid),
  215. dbesc($album)
  216. );
  217. } else {
  218. $r = q("SELECT distinct(`resource-id`) as `rid` FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
  219. intval(local_user()),
  220. dbesc($album)
  221. );
  222. }
  223. if (DBM::is_result($r)) {
  224. foreach ($r as $rr) {
  225. $res[] = "'" . dbesc($rr['rid']) . "'" ;
  226. }
  227. } else {
  228. goaway($_SESSION['photo_return']);
  229. return; // NOTREACHED
  230. }
  231. $str_res = implode(',', $res);
  232. // remove the associated photos
  233. q("DELETE FROM `photo` WHERE `resource-id` IN ($str_res) AND `uid` = %d",
  234. intval($page_owner_uid)
  235. );
  236. // find and delete the corresponding item with all the comments and likes/dislikes
  237. Item::deleteForUser(['resource-id' => $res, 'uid' => $page_owner_uid], $page_owner_uid);
  238. // Update the photo albums cache
  239. Photo::clearAlbumCache($page_owner_uid);
  240. }
  241. goaway('photos/' . $a->data['user']['nickname']);
  242. return; // NOTREACHED
  243. }
  244. // Check if the user has responded to a delete confirmation query for a single photo
  245. if ($a->argc > 2 && x($_REQUEST, 'canceled')) {
  246. goaway($_SESSION['photo_return']);
  247. }
  248. if ($a->argc > 2 && defaults($_POST, 'delete', '') === L10n::t('Delete Photo')) {
  249. // same as above but remove single photo
  250. // Check if we should do HTML-based delete confirmation
  251. if (x($_REQUEST, 'confirm')) {
  252. $drop_url = $a->query_string;
  253. $a->page['content'] = replace_macros(get_markup_template('confirm.tpl'), [
  254. '$method' => 'post',
  255. '$message' => L10n::t('Do you really want to delete this photo?'),
  256. '$extra_inputs' => [],
  257. '$confirm' => L10n::t('Delete Photo'),
  258. '$confirm_url' => $drop_url,
  259. '$confirm_name' => 'delete', // Needed so that confirmation will bring us back into this if statement
  260. '$cancel' => L10n::t('Cancel'),
  261. ]);
  262. $a->error = 1; // Set $a->error so the other module functions don't execute
  263. return;
  264. }
  265. if ($visitor) {
  266. $r = q("SELECT `id`, `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d AND `resource-id` = '%s' LIMIT 1",
  267. intval($visitor),
  268. intval($page_owner_uid),
  269. dbesc($a->argv[2])
  270. );
  271. } else {
  272. $r = q("SELECT `id`, `resource-id` FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' LIMIT 1",
  273. intval(local_user()),
  274. dbesc($a->argv[2])
  275. );
  276. }
  277. if (DBM::is_result($r)) {
  278. q("DELETE FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s'",
  279. intval($page_owner_uid),
  280. dbesc($r[0]['resource-id'])
  281. );
  282. Item::deleteForUser(['resource-id' => $r[0]['resource-id'], 'uid' => $page_owner_uid], $page_owner_uid);
  283. // Update the photo albums cache
  284. Photo::clearAlbumCache($page_owner_uid);
  285. }
  286. goaway('photos/' . $a->data['user']['nickname']);
  287. return; // NOTREACHED
  288. }
  289. if ($a->argc > 2 && (x($_POST, 'desc') !== false || x($_POST, 'newtag') !== false || x($_POST, 'albname') !== false)) {
  290. $desc = x($_POST, 'desc') ? notags(trim($_POST['desc'])) : '';
  291. $rawtags = x($_POST, 'newtag') ? notags(trim($_POST['newtag'])) : '';
  292. $item_id = x($_POST, 'item_id') ? intval($_POST['item_id']) : 0;
  293. $albname = x($_POST, 'albname') ? notags(trim($_POST['albname'])) : '';
  294. $origaname = x($_POST, 'origaname') ? notags(trim($_POST['origaname'])) : '';
  295. $str_group_allow = perms2str($_POST['group_allow']);
  296. $str_contact_allow = perms2str($_POST['contact_allow']);
  297. $str_group_deny = perms2str($_POST['group_deny']);
  298. $str_contact_deny = perms2str($_POST['contact_deny']);
  299. $resource_id = $a->argv[2];
  300. if (!strlen($albname)) {
  301. $albname = DateTimeFormat::localNow('Y');
  302. }
  303. if (x($_POST,'rotate') !== false &&
  304. (intval($_POST['rotate']) == 1 || intval($_POST['rotate']) == 2)) {
  305. logger('rotate');
  306. $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d AND `scale` = 0 LIMIT 1",
  307. dbesc($resource_id),
  308. intval($page_owner_uid)
  309. );
  310. if (DBM::is_result($r)) {
  311. $Image = new Image($r[0]['data'], $r[0]['type']);
  312. if ($Image->isValid()) {
  313. $rotate_deg = ((intval($_POST['rotate']) == 1) ? 270 : 90);
  314. $Image->rotate($rotate_deg);
  315. $width = $Image->getWidth();
  316. $height = $Image->getHeight();
  317. $x = q("UPDATE `photo` SET `data` = '%s', `height` = %d, `width` = %d WHERE `resource-id` = '%s' AND `uid` = %d AND `scale` = 0",
  318. dbesc($Image->asString()),
  319. intval($height),
  320. intval($width),
  321. dbesc($resource_id),
  322. intval($page_owner_uid)
  323. );
  324. if ($width > 640 || $height > 640) {
  325. $Image->scaleDown(640);
  326. $width = $Image->getWidth();
  327. $height = $Image->getHeight();
  328. $x = q("UPDATE `photo` SET `data` = '%s', `height` = %d, `width` = %d WHERE `resource-id` = '%s' AND `uid` = %d AND `scale` = 1",
  329. dbesc($Image->asString()),
  330. intval($height),
  331. intval($width),
  332. dbesc($resource_id),
  333. intval($page_owner_uid)
  334. );
  335. }
  336. if ($width > 320 || $height > 320) {
  337. $Image->scaleDown(320);
  338. $width = $Image->getWidth();
  339. $height = $Image->getHeight();
  340. $x = q("UPDATE `photo` SET `data` = '%s', `height` = %d, `width` = %d WHERE `resource-id` = '%s' AND `uid` = %d AND `scale` = 2",
  341. dbesc($Image->asString()),
  342. intval($height),
  343. intval($width),
  344. dbesc($resource_id),
  345. intval($page_owner_uid)
  346. );
  347. }
  348. }
  349. }
  350. }
  351. $p = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ORDER BY `scale` DESC",
  352. dbesc($resource_id),
  353. intval($page_owner_uid)
  354. );
  355. if (DBM::is_result($p)) {
  356. $ext = $phototypes[$p[0]['type']];
  357. $r = q("UPDATE `photo` SET `desc` = '%s', `album` = '%s', `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' WHERE `resource-id` = '%s' AND `uid` = %d",
  358. dbesc($desc),
  359. dbesc($albname),
  360. dbesc($str_contact_allow),
  361. dbesc($str_group_allow),
  362. dbesc($str_contact_deny),
  363. dbesc($str_group_deny),
  364. dbesc($resource_id),
  365. intval($page_owner_uid)
  366. );
  367. // Update the photo albums cache if album name was changed
  368. if ($albname !== $origaname) {
  369. Photo::clearAlbumCache($page_owner_uid);
  370. }
  371. }
  372. /* Don't make the item visible if the only change was the album name */
  373. $visibility = 0;
  374. if ($p[0]['desc'] !== $desc || strlen($rawtags)) {
  375. $visibility = 1;
  376. }
  377. if (!$item_id) {
  378. // Create item container
  379. $title = '';
  380. $uri = Item::newURI($page_owner_uid);
  381. $arr = [];
  382. $arr['guid'] = get_guid(32);
  383. $arr['uid'] = $page_owner_uid;
  384. $arr['uri'] = $uri;
  385. $arr['parent-uri'] = $uri;
  386. $arr['type'] = 'photo';
  387. $arr['wall'] = 1;
  388. $arr['resource-id'] = $p[0]['resource-id'];
  389. $arr['contact-id'] = $owner_record['id'];
  390. $arr['owner-name'] = $owner_record['name'];
  391. $arr['owner-link'] = $owner_record['url'];
  392. $arr['owner-avatar'] = $owner_record['thumb'];
  393. $arr['author-name'] = $owner_record['name'];
  394. $arr['author-link'] = $owner_record['url'];
  395. $arr['author-avatar'] = $owner_record['thumb'];
  396. $arr['title'] = $title;
  397. $arr['allow_cid'] = $p[0]['allow_cid'];
  398. $arr['allow_gid'] = $p[0]['allow_gid'];
  399. $arr['deny_cid'] = $p[0]['deny_cid'];
  400. $arr['deny_gid'] = $p[0]['deny_gid'];
  401. $arr['visible'] = $visibility;
  402. $arr['origin'] = 1;
  403. $arr['body'] = '[url=' . System::baseUrl() . '/photos/' . $a->data['user']['nickname'] . '/image/' . $p[0]['resource-id'] . ']'
  404. . '[img]' . System::baseUrl() . '/photo/' . $p[0]['resource-id'] . '-' . $p[0]['scale'] . '.'. $ext . '[/img]'
  405. . '[/url]';
  406. $item_id = Item::insert($arr);
  407. }
  408. if ($item_id) {
  409. $item = Item::selectFirst(['tag', 'inform'], ['id' => $item_id, 'uid' => $page_owner_uid]);
  410. }
  411. if (DBM::is_result($item)) {
  412. $old_tag = $item['tag'];
  413. $old_inform = $item['inform'];
  414. }
  415. if (strlen($rawtags)) {
  416. $str_tags = '';
  417. $inform = '';
  418. // if the new tag doesn't have a namespace specifier (@foo or #foo) give it a hashtag
  419. $x = substr($rawtags, 0, 1);
  420. if ($x !== '@' && $x !== '#') {
  421. $rawtags = '#' . $rawtags;
  422. }
  423. $taginfo = [];
  424. $tags = get_tags($rawtags);
  425. if (count($tags)) {
  426. foreach ($tags as $tag) {
  427. if (strpos($tag, '@') === 0) {
  428. $profile = '';
  429. $name = substr($tag,1);
  430. if ((strpos($name, '@')) || (strpos($name, 'http://'))) {
  431. $newname = $name;
  432. $links = @Probe::lrdd($name);
  433. if (count($links)) {
  434. foreach ($links as $link) {
  435. if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') {
  436. $profile = $link['@attributes']['href'];
  437. }
  438. if ($link['@attributes']['rel'] === 'salmon') {
  439. $salmon = '$url:' . str_replace(',', '%sc', $link['@attributes']['href']);
  440. if (strlen($inform)) {
  441. $inform .= ',';
  442. }
  443. $inform .= $salmon;
  444. }
  445. }
  446. }
  447. $taginfo[] = [$newname, $profile, $salmon];
  448. } else {
  449. $newname = $name;
  450. $alias = '';
  451. $tagcid = 0;
  452. if (strrpos($newname, '+')) {
  453. $tagcid = intval(substr($newname, strrpos($newname, '+') + 1));
  454. }
  455. if ($tagcid) {
  456. $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
  457. intval($tagcid),
  458. intval($profile_uid)
  459. );
  460. } else {
  461. $newname = str_replace('_',' ',$name);
  462. //select someone from this user's contacts by name
  463. $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `uid` = %d LIMIT 1",
  464. dbesc($newname),
  465. intval($page_owner_uid)
  466. );
  467. if (!DBM::is_result($r)) {
  468. //select someone by attag or nick and the name passed in
  469. $r = q("SELECT * FROM `contact` WHERE `attag` = '%s' OR `nick` = '%s' AND `uid` = %d ORDER BY `attag` DESC LIMIT 1",
  470. dbesc($name),
  471. dbesc($name),
  472. intval($page_owner_uid)
  473. );
  474. }
  475. }
  476. if (DBM::is_result($r)) {
  477. $newname = $r[0]['name'];
  478. $profile = $r[0]['url'];
  479. $notify = 'cid:' . $r[0]['id'];
  480. if (strlen($inform)) {
  481. $inform .= ',';
  482. }
  483. $inform .= $notify;
  484. }
  485. }
  486. if ($profile) {
  487. if (substr($notify, 0, 4) === 'cid:') {
  488. $taginfo[] = [$newname, $profile, $notify, $r[0], '@[url=' . str_replace(',','%2c',$profile) . ']' . $newname . '[/url]'];
  489. } else {
  490. $taginfo[] = [$newname, $profile, $notify, null, $str_tags .= '@[url=' . $profile . ']' . $newname . '[/url]'];
  491. }
  492. if (strlen($str_tags)) {
  493. $str_tags .= ',';
  494. }
  495. $profile = str_replace(',', '%2c', $profile);
  496. $str_tags .= '@[url='.$profile.']'.$newname.'[/url]';
  497. }
  498. } elseif (strpos($tag, '#') === 0) {
  499. $tagname = substr($tag, 1);
  500. $str_tags .= '#[url=' . System::baseUrl() . "/search?tag=" . $tagname . ']' . $tagname . '[/url]';
  501. }
  502. }
  503. }
  504. $newtag = $old_tag;
  505. if (strlen($newtag) && strlen($str_tags)) {
  506. $newtag .= ',';
  507. }
  508. $newtag .= $str_tags;
  509. $newinform = $old_inform;
  510. if (strlen($newinform) && strlen($inform)) {
  511. $newinform .= ',';
  512. }
  513. $newinform .= $inform;
  514. $fields = ['tag' => $newtag, 'inform' => $newinform, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
  515. $condition = ['id' => $item_id];
  516. Item::update($fields, $condition);
  517. $best = 0;
  518. foreach ($p as $scales) {
  519. if (intval($scales['scale']) == 2) {
  520. $best = 2;
  521. break;
  522. }
  523. if (intval($scales['scale']) == 4) {
  524. $best = 4;
  525. break;
  526. }
  527. }
  528. if (count($taginfo)) {
  529. foreach ($taginfo as $tagged) {
  530. $uri = Item::newURI($page_owner_uid);
  531. $arr = [];
  532. $arr['guid'] = get_guid(32);
  533. $arr['uid'] = $page_owner_uid;
  534. $arr['uri'] = $uri;
  535. $arr['parent-uri'] = $uri;
  536. $arr['type'] = 'activity';
  537. $arr['wall'] = 1;
  538. $arr['contact-id'] = $owner_record['id'];
  539. $arr['owner-name'] = $owner_record['name'];
  540. $arr['owner-link'] = $owner_record['url'];
  541. $arr['owner-avatar'] = $owner_record['thumb'];
  542. $arr['author-name'] = $owner_record['name'];
  543. $arr['author-link'] = $owner_record['url'];
  544. $arr['author-avatar'] = $owner_record['thumb'];
  545. $arr['title'] = '';
  546. $arr['allow_cid'] = $p[0]['allow_cid'];
  547. $arr['allow_gid'] = $p[0]['allow_gid'];
  548. $arr['deny_cid'] = $p[0]['deny_cid'];
  549. $arr['deny_gid'] = $p[0]['deny_gid'];
  550. $arr['visible'] = 1;
  551. $arr['verb'] = ACTIVITY_TAG;
  552. $arr['object-type'] = ACTIVITY_OBJ_PERSON;
  553. $arr['target-type'] = ACTIVITY_OBJ_IMAGE;
  554. $arr['tag'] = $tagged[4];
  555. $arr['inform'] = $tagged[2];
  556. $arr['origin'] = 1;
  557. $arr['body'] = L10n::t('%1$s was tagged in %2$s by %3$s', '[url=' . $tagged[1] . ']' . $tagged[0] . '[/url]', '[url=' . System::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $p[0]['resource-id'] . ']' . L10n::t('a photo') . '[/url]', '[url=' . $owner_record['url'] . ']' . $owner_record['name'] . '[/url]') ;
  558. $arr['body'] .= "\n\n" . '[url=' . System::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $p[0]['resource-id'] . ']' . '[img]' . System::baseUrl() . "/photo/" . $p[0]['resource-id'] . '-' . $best . '.' . $ext . '[/img][/url]' . "\n" ;
  559. $arr['object'] = '<object><type>' . ACTIVITY_OBJ_PERSON . '</type><title>' . $tagged[0] . '</title><id>' . $tagged[1] . '/' . $tagged[0] . '</id>';
  560. $arr['object'] .= '<link>' . xmlify('<link rel="alternate" type="text/html" href="' . $tagged[1] . '" />' . "\n");
  561. if ($tagged[3]) {
  562. $arr['object'] .= xmlify('<link rel="photo" type="'.$p[0]['type'].'" href="' . $tagged[3]['photo'] . '" />' . "\n");
  563. }
  564. $arr['object'] .= '</link></object>' . "\n";
  565. $arr['target'] = '<target><type>' . ACTIVITY_OBJ_IMAGE . '</type><title>' . $p[0]['desc'] . '</title><id>'
  566. . System::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $p[0]['resource-id'] . '</id>';
  567. $arr['target'] .= '<link>' . xmlify('<link rel="alternate" type="text/html" href="' . System::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $p[0]['resource-id'] . '" />' . "\n" . '<link rel="preview" type="'.$p[0]['type'].'" href="' . System::baseUrl() . "/photo/" . $p[0]['resource-id'] . '-' . $best . '.' . $ext . '" />') . '</link></target>';
  568. $item_id = Item::insert($arr);
  569. if ($item_id) {
  570. Worker::add(PRIORITY_HIGH, "Notifier", "tag", $item_id);
  571. }
  572. }
  573. }
  574. }
  575. goaway($_SESSION['photo_return']);
  576. return; // NOTREACHED
  577. }
  578. // default post action - upload a photo
  579. Addon::callHooks('photo_post_init', $_POST);
  580. // Determine the album to use
  581. $album = x($_REQUEST, 'album') ? notags(trim($_REQUEST['album'])) : '';
  582. $newalbum = x($_REQUEST, 'newalbum') ? notags(trim($_REQUEST['newalbum'])) : '';
  583. logger('mod/photos.php: photos_post(): album= ' . $album . ' newalbum= ' . $newalbum , LOGGER_DEBUG);
  584. if (!strlen($album)) {
  585. if (strlen($newalbum)) {
  586. $album = $newalbum;
  587. } else {
  588. $album = DateTimeFormat::localNow('Y');
  589. }
  590. }
  591. /*
  592. * We create a wall item for every photo, but we don't want to
  593. * overwhelm the data stream with a hundred newly uploaded photos.
  594. * So we will make the first photo uploaded to this album in the last several hours
  595. * visible by default, the rest will become visible over time when and if
  596. * they acquire comments, likes, dislikes, and/or tags
  597. */
  598. $r = q("SELECT * FROM `photo` WHERE `album` = '%s' AND `uid` = %d AND `created` > UTC_TIMESTAMP() - INTERVAL 3 HOUR ",
  599. dbesc($album),
  600. intval($page_owner_uid)
  601. );
  602. if (!DBM::is_result($r) || ($album == L10n::t('Profile Photos'))) {
  603. $visible = 1;
  604. } else {
  605. $visible = 0;
  606. }
  607. if (x($_REQUEST, 'not_visible') && $_REQUEST['not_visible'] !== 'false') {
  608. $visible = 0;
  609. }
  610. $group_allow = defaults($_REQUEST, 'group_allow' , []);
  611. $contact_allow = defaults($_REQUEST, 'contact_allow', []);
  612. $group_deny = defaults($_REQUEST, 'group_deny' , []);
  613. $contact_deny = defaults($_REQUEST, 'contact_deny' , []);
  614. $str_group_allow = perms2str(is_array($group_allow) ? $group_allow : explode(',', $group_allow));
  615. $str_contact_allow = perms2str(is_array($contact_allow) ? $contact_allow : explode(',', $contact_allow));
  616. $str_group_deny = perms2str(is_array($group_deny) ? $group_deny : explode(',', $group_deny));
  617. $str_contact_deny = perms2str(is_array($contact_deny) ? $contact_deny : explode(',', $contact_deny));
  618. $ret = ['src' => '', 'filename' => '', 'filesize' => 0, 'type' => ''];
  619. Addon::callHooks('photo_post_file', $ret);
  620. if (x($ret, 'src') && x($ret, 'filesize')) {
  621. $src = $ret['src'];
  622. $filename = $ret['filename'];
  623. $filesize = $ret['filesize'];
  624. $type = $ret['type'];
  625. $error = UPLOAD_ERR_OK;
  626. } else {
  627. $src = $_FILES['userfile']['tmp_name'];
  628. $filename = basename($_FILES['userfile']['name']);
  629. $filesize = intval($_FILES['userfile']['size']);
  630. $type = $_FILES['userfile']['type'];
  631. $error = $_FILES['userfile']['error'];
  632. }
  633. if ($error !== UPLOAD_ERR_OK) {
  634. switch ($error) {
  635. case UPLOAD_ERR_INI_SIZE:
  636. notice(L10n::t('Image exceeds size limit of %s', ini_get('upload_max_filesize')) . EOL);
  637. break;
  638. case UPLOAD_ERR_FORM_SIZE:
  639. notice(L10n::t('Image exceeds size limit of %s', formatBytes(defaults($_REQUEST, 'MAX_FILE_SIZE', 0))) . EOL);
  640. break;
  641. case UPLOAD_ERR_PARTIAL:
  642. notice(L10n::t('Image upload didn\'t complete, please try again') . EOL);
  643. break;
  644. case UPLOAD_ERR_NO_FILE:
  645. notice(L10n::t('Image file is missing') . EOL);
  646. break;
  647. case UPLOAD_ERR_NO_TMP_DIR:
  648. case UPLOAD_ERR_CANT_WRITE:
  649. case UPLOAD_ERR_EXTENSION:
  650. notice(L10n::t('Server can\'t accept new file upload at this time, please contact your administrator') . EOL);
  651. break;
  652. }
  653. @unlink($src);
  654. $foo = 0;
  655. Addon::callHooks('photo_post_end', $foo);
  656. return;
  657. }
  658. if ($type == "") {
  659. $type = Image::guessType($filename);
  660. }
  661. logger('photos: upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes', LOGGER_DEBUG);
  662. $maximagesize = Config::get('system', 'maximagesize');
  663. if ($maximagesize && ($filesize > $maximagesize)) {
  664. notice(L10n::t('Image exceeds size limit of %s', formatBytes($maximagesize)) . EOL);
  665. @unlink($src);
  666. $foo = 0;
  667. Addon::callHooks('photo_post_end', $foo);
  668. return;
  669. }
  670. if (!$filesize) {
  671. notice(L10n::t('Image file is empty.') . EOL);
  672. @unlink($src);
  673. $foo = 0;
  674. Addon::callHooks('photo_post_end', $foo);
  675. return;
  676. }
  677. logger('mod/photos.php: photos_post(): loading the contents of ' . $src , LOGGER_DEBUG);
  678. $imagedata = @file_get_contents($src);
  679. $Image = new Image($imagedata, $type);
  680. if (!$Image->isValid()) {
  681. logger('mod/photos.php: photos_post(): unable to process image' , LOGGER_DEBUG);
  682. notice(L10n::t('Unable to process image.') . EOL);
  683. @unlink($src);
  684. $foo = 0;
  685. Addon::callHooks('photo_post_end',$foo);
  686. killme();
  687. }
  688. $exif = $Image->orient($src);
  689. @unlink($src);
  690. $max_length = Config::get('system', 'max_image_length');
  691. if (!$max_length) {
  692. $max_length = MAX_IMAGE_LENGTH;
  693. }
  694. if ($max_length > 0) {
  695. $Image->scaleDown($max_length);
  696. }
  697. $width = $Image->getWidth();
  698. $height = $Image->getHeight();
  699. $smallest = 0;
  700. $photo_hash = Photo::newResource();
  701. $r = Photo::store($Image, $page_owner_uid, $visitor, $photo_hash, $filename, $album, 0 , 0, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny);
  702. if (!$r) {
  703. logger('mod/photos.php: photos_post(): image store failed', LOGGER_DEBUG);
  704. notice(L10n::t('Image upload failed.') . EOL);
  705. killme();
  706. }
  707. if ($width > 640 || $height > 640) {
  708. $Image->scaleDown(640);
  709. Photo::store($Image, $page_owner_uid, $visitor, $photo_hash, $filename, $album, 1, 0, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny);
  710. $smallest = 1;
  711. }
  712. if ($width > 320 || $height > 320) {
  713. $Image->scaleDown(320);
  714. Photo::store($Image, $page_owner_uid, $visitor, $photo_hash, $filename, $album, 2, 0, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny);
  715. $smallest = 2;
  716. }
  717. $uri = Item::newURI($page_owner_uid);
  718. // Create item container
  719. $lat = $lon = null;
  720. if ($exif && $exif['GPS'] && Feature::isEnabled($page_owner_uid, 'photo_location')) {
  721. $lat = Photo::getGps($exif['GPS']['GPSLatitude'], $exif['GPS']['GPSLatitudeRef']);
  722. $lon = Photo::getGps($exif['GPS']['GPSLongitude'], $exif['GPS']['GPSLongitudeRef']);
  723. }
  724. $arr = [];
  725. if ($lat && $lon) {
  726. $arr['coord'] = $lat . ' ' . $lon;
  727. }
  728. $arr['guid'] = get_guid(32);
  729. $arr['uid'] = $page_owner_uid;
  730. $arr['uri'] = $uri;
  731. $arr['parent-uri'] = $uri;
  732. $arr['type'] = 'photo';
  733. $arr['wall'] = 1;
  734. $arr['resource-id'] = $photo_hash;
  735. $arr['contact-id'] = $owner_record['id'];
  736. $arr['owner-name'] = $owner_record['name'];
  737. $arr['owner-link'] = $owner_record['url'];
  738. $arr['owner-avatar'] = $owner_record['thumb'];
  739. $arr['author-name'] = $owner_record['name'];
  740. $arr['author-link'] = $owner_record['url'];
  741. $arr['author-avatar'] = $owner_record['thumb'];
  742. $arr['title'] = '';
  743. $arr['allow_cid'] = $str_contact_allow;
  744. $arr['allow_gid'] = $str_group_allow;
  745. $arr['deny_cid'] = $str_contact_deny;
  746. $arr['deny_gid'] = $str_group_deny;
  747. $arr['visible'] = $visible;
  748. $arr['origin'] = 1;
  749. $arr['body'] = '[url=' . System::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo_hash . ']'
  750. . '[img]' . System::baseUrl() . "/photo/{$photo_hash}-{$smallest}.".$Image->getExt() . '[/img]'
  751. . '[/url]';
  752. $item_id = Item::insert($arr);
  753. // Update the photo albums cache
  754. Photo::clearAlbumCache($page_owner_uid);
  755. if ($visible) {
  756. Worker::add(PRIORITY_HIGH, "Notifier", 'wall-new', $item_id);
  757. }
  758. Addon::callHooks('photo_post_end', intval($item_id));
  759. // addon uploaders should call "killme()" [e.g. exit] within the photo_post_end hook
  760. // if they do not wish to be redirected
  761. goaway($_SESSION['photo_return']);
  762. // NOTREACHED
  763. }
  764. function photos_content(App $a)
  765. {
  766. // URLs:
  767. // photos/name
  768. // photos/name/upload
  769. // photos/name/upload/xxxxx (xxxxx is album name)
  770. // photos/name/album/xxxxx
  771. // photos/name/album/xxxxx/edit
  772. // photos/name/image/xxxxx
  773. // photos/name/image/xxxxx/edit
  774. if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
  775. notice(L10n::t('Public access denied.') . EOL);
  776. return;
  777. }
  778. require_once 'include/security.php';
  779. require_once 'include/conversation.php';
  780. if (!x($a->data,'user')) {
  781. notice(L10n::t('No photos selected') . EOL);
  782. return;
  783. }
  784. $phototypes = Image::supportedTypes();
  785. $_SESSION['photo_return'] = $a->cmd;
  786. // Parse arguments
  787. $datum = null;
  788. if ($a->argc > 3) {
  789. $datatype = $a->argv[2];
  790. $datum = $a->argv[3];
  791. } elseif (($a->argc > 2) && ($a->argv[2] === 'upload')) {
  792. $datatype = 'upload';
  793. } else {
  794. $datatype = 'summary';
  795. }
  796. if ($a->argc > 4) {
  797. $cmd = $a->argv[4];
  798. } else {
  799. $cmd = 'view';
  800. }
  801. // Setup permissions structures
  802. $can_post = false;
  803. $visitor = 0;
  804. $contact = null;
  805. $remote_contact = false;
  806. $contact_id = 0;
  807. $owner_uid = $a->data['user']['uid'];
  808. $community_page = (($a->data['user']['page-flags'] == PAGE_COMMUNITY) ? true : false);
  809. if (local_user() && (local_user() == $owner_uid)) {
  810. $can_post = true;
  811. } else {
  812. if ($community_page && remote_user()) {
  813. if (is_array($_SESSION['remote'])) {
  814. foreach ($_SESSION['remote'] as $v) {
  815. if ($v['uid'] == $owner_uid) {
  816. $contact_id = $v['cid'];
  817. break;
  818. }
  819. }
  820. }
  821. if ($contact_id) {
  822. $r = q("SELECT `uid` FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `id` = %d AND `uid` = %d LIMIT 1",
  823. intval($contact_id),
  824. intval($owner_uid)
  825. );
  826. if (DBM::is_result($r)) {
  827. $can_post = true;
  828. $contact = $r[0];
  829. $remote_contact = true;
  830. $visitor = $contact_id;
  831. }
  832. }
  833. }
  834. }
  835. $groups = [];
  836. // perhaps they're visiting - but not a community page, so they wouldn't have write access
  837. if (remote_user() && !$visitor) {
  838. $contact_id = 0;
  839. if (is_array($_SESSION['remote'])) {
  840. foreach ($_SESSION['remote'] as $v) {
  841. if ($v['uid'] == $owner_uid) {
  842. $contact_id = $v['cid'];
  843. break;
  844. }
  845. }
  846. }
  847. if ($contact_id) {
  848. $groups = Group::getIdsByContactId($contact_id);
  849. $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `id` = %d AND `uid` = %d LIMIT 1",
  850. intval($contact_id),
  851. intval($owner_uid)
  852. );
  853. if (DBM::is_result($r)) {
  854. $contact = $r[0];
  855. $remote_contact = true;
  856. }
  857. }
  858. }
  859. if (!$remote_contact && local_user()) {
  860. $contact_id = $_SESSION['cid'];
  861. $contact = $a->contact;
  862. }
  863. if ($a->data['user']['hidewall'] && (local_user() != $owner_uid) && !$remote_contact) {
  864. notice(L10n::t('Access to this item is restricted.') . EOL);
  865. return;
  866. }
  867. $sql_extra = permissions_sql($owner_uid, $remote_contact, $groups);
  868. $o = "";
  869. // tabs
  870. $is_owner = (local_user() && (local_user() == $owner_uid));
  871. $o .= Profile::getTabs($a, $is_owner, $a->data['user']['nickname']);
  872. // Display upload form
  873. if ($datatype === 'upload') {
  874. if (!$can_post) {
  875. notice(L10n::t('Permission denied.'));
  876. return;
  877. }
  878. $selname = $datum ? hex2bin($datum) : '';
  879. $albumselect = '';
  880. $albumselect .= '<option value="" ' . (!$selname ? ' selected="selected" ' : '') . '>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</option>';
  881. if (count($a->data['albums'])) {
  882. foreach ($a->data['albums'] as $album) {
  883. if (($album['album'] === '') || ($album['album'] === 'Contact Photos') || ($album['album'] === L10n::t('Contact Photos'))) {
  884. continue;
  885. }
  886. $selected = (($selname === $album['album']) ? ' selected="selected" ' : '');
  887. $albumselect .= '<option value="' . $album['album'] . '"' . $selected . '>' . $album['album'] . '</option>';
  888. }
  889. }
  890. $uploader = '';
  891. $ret = ['post_url' => 'photos/' . $a->data['user']['nickname'],
  892. 'addon_text' => $uploader,
  893. 'default_upload' => true];
  894. Addon::callHooks('photo_upload_form',$ret);
  895. $default_upload_box = replace_macros(get_markup_template('photos_default_uploader_box.tpl'), []);
  896. $default_upload_submit = replace_macros(get_markup_template('photos_default_uploader_submit.tpl'), [
  897. '$submit' => L10n::t('Submit'),
  898. ]);
  899. $usage_message = '';
  900. $tpl = get_markup_template('photos_upload.tpl');
  901. $aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML($a->user));
  902. $o .= replace_macros($tpl,[
  903. '$pagename' => L10n::t('Upload Photos'),
  904. '$sessid' => session_id(),
  905. '$usage' => $usage_message,
  906. '$nickname' => $a->data['user']['nickname'],
  907. '$newalbum' => L10n::t('New album name: '),
  908. '$existalbumtext' => L10n::t('or existing album name: '),
  909. '$nosharetext' => L10n::t('Do not show a status post for this upload'),
  910. '$albumselect' => $albumselect,
  911. '$permissions' => L10n::t('Permissions'),
  912. '$aclselect' => $aclselect_e,
  913. '$alt_uploader' => $ret['addon_text'],
  914. '$default_upload_box' => ($ret['default_upload'] ? $default_upload_box : ''),
  915. '$default_upload_submit' => ($ret['default_upload'] ? $default_upload_submit : ''),
  916. '$uploadurl' => $ret['post_url'],
  917. // ACL permissions box
  918. '$group_perms' => L10n::t('Show to Groups'),
  919. '$contact_perms' => L10n::t('Show to Contacts'),
  920. '$return_path' => $a->query_string,
  921. ]);
  922. return $o;
  923. }
  924. // Display a single photo album
  925. if ($datatype === 'album') {
  926. $album = hex2bin($datum);
  927. $r = q("SELECT `resource-id`, max(`scale`) AS `scale` FROM `photo` WHERE `uid` = %d AND `album` = '%s'
  928. AND `scale` <= 4 $sql_extra GROUP BY `resource-id`",
  929. intval($owner_uid),
  930. dbesc($album)
  931. );
  932. if (DBM::is_result($r)) {
  933. $a->set_pager_total(count($r));
  934. $a->set_pager_itemspage(20);
  935. }
  936. /// @TODO I have seen this many times, maybe generalize it script-wide and encapsulate it?
  937. $order_field = defaults($_GET, 'order', '');
  938. if ($order_field === 'posted') {
  939. $order = 'ASC';
  940. } else {
  941. $order = 'DESC';
  942. }
  943. $r = q("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`,
  944. ANY_VALUE(`type`) AS `type`, max(`scale`) AS `scale`, ANY_VALUE(`desc`) as `desc`,
  945. ANY_VALUE(`created`) as `created`
  946. FROM `photo` WHERE `uid` = %d AND `album` = '%s'
  947. AND `scale` <= 4 $sql_extra GROUP BY `resource-id` ORDER BY `created` $order LIMIT %d , %d",
  948. intval($owner_uid),
  949. dbesc($album),
  950. intval($a->pager['start']),
  951. intval($a->pager['itemspage'])
  952. );
  953. // edit album name
  954. if ($cmd === 'edit') {
  955. if (($album !== L10n::t('Profile Photos')) && ($album !== 'Contact Photos') && ($album !== L10n::t('Contact Photos'))) {
  956. if ($can_post) {
  957. $edit_tpl = get_markup_template('album_edit.tpl');
  958. $album_e = $album;
  959. $o .= replace_macros($edit_tpl,[
  960. '$nametext' => L10n::t('New album name: '),
  961. '$nickname' => $a->data['user']['nickname'],
  962. '$album' => $album_e,
  963. '$hexalbum' => bin2hex($album),
  964. '$submit' => L10n::t('Submit'),
  965. '$dropsubmit' => L10n::t('Delete Album')
  966. ]);
  967. }
  968. }
  969. } else {
  970. if (($album !== L10n::t('Profile Photos')) && ($album !== 'Contact Photos') && ($album !== L10n::t('Contact Photos')) && $can_post) {
  971. $edit = [L10n::t('Edit Album'), 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album) . '/edit'];
  972. }
  973. }
  974. if ($order_field === 'posted') {
  975. $order = [L10n::t('Show Newest First'), 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album)];
  976. } else {
  977. $order = [L10n::t('Show Oldest First'), 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album) . '?f=&order=posted'];
  978. }
  979. $photos = [];
  980. if (DBM::is_result($r)) {
  981. // "Twist" is only used for the duepunto theme with style "slackr"
  982. $twist = false;
  983. foreach ($r as $rr) {
  984. $twist = !$twist;
  985. $ext = $phototypes[$rr['type']];
  986. $imgalt_e = $rr['filename'];
  987. $desc_e = $rr['desc'];
  988. $photos[] = [
  989. 'id' => $rr['id'],
  990. 'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2,4),
  991. 'link' => 'photos/' . $a->data['user']['nickname'] . '/image/' . $rr['resource-id']
  992. . ($order_field === 'posted' ? '?f=&order=posted' : ''),
  993. 'title' => L10n::t('View Photo'),
  994. 'src' => 'photo/' . $rr['resource-id'] . '-' . $rr['scale'] . '.' .$ext,
  995. 'alt' => $imgalt_e,
  996. 'desc'=> $desc_e,
  997. 'ext' => $ext,
  998. 'hash'=> $rr['resource-id'],
  999. ];
  1000. }
  1001. }
  1002. $tpl = get_markup_template('photo_album.tpl');
  1003. $o .= replace_macros($tpl, [
  1004. '$photos' => $photos,
  1005. '$album' => $album,
  1006. '$can_post' => $can_post,
  1007. '$upload' => [L10n::t('Upload New Photos'), 'photos/' . $a->data['user']['nickname'] . '/upload/' . bin2hex($album)],
  1008. '$order' => $order,
  1009. '$edit' => $edit,
  1010. '$paginate' => paginate($a),
  1011. ]);
  1012. return $o;
  1013. }
  1014. // Display one photo
  1015. if ($datatype === 'image') {
  1016. // fetch image, item containing image, then comments
  1017. $ph = q("SELECT * FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s'
  1018. $sql_extra ORDER BY `scale` ASC ",
  1019. intval($owner_uid),
  1020. dbesc($datum)
  1021. );
  1022. if (!DBM::is_result($ph)) {
  1023. $ph = q("SELECT `id` FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s'
  1024. LIMIT 1",
  1025. intval($owner_uid),
  1026. dbesc($datum)
  1027. );
  1028. if (DBM::is_result($ph)) {
  1029. notice(L10n::t('Permission denied. Access to this item may be restricted.'));
  1030. } else {
  1031. notice(L10n::t('Photo not available') . EOL);
  1032. }
  1033. return;
  1034. }
  1035. $prevlink = '';
  1036. $nextlink = '';
  1037. /*
  1038. * @todo This query is totally bad, the whole functionality has to be changed
  1039. * The query leads to a really intense used index.
  1040. * By now we hide it if someone wants to.
  1041. */
  1042. if (!Config::get('system', 'no_count', false)) {
  1043. $order_field = defaults($_GET, 'order', '');
  1044. if ($order_field === 'posted') {
  1045. $order = 'ASC';
  1046. } else {
  1047. $order = 'DESC';
  1048. }
  1049. $prvnxt = q("SELECT `resource-id` FROM `photo` WHERE `album` = '%s' AND `uid` = %d AND `scale` = 0
  1050. $sql_extra ORDER BY `created` $order ",
  1051. dbesc($ph[0]['album']),
  1052. intval($owner_uid)
  1053. );
  1054. if (DBM::is_result($prvnxt)) {
  1055. foreach ($prvnxt as $z => $entry) {
  1056. if ($entry['resource-id'] == $ph[0]['resource-id']) {
  1057. $prv = $z - 1;
  1058. $nxt = $z + 1;
  1059. if ($prv < 0) {
  1060. $prv = count($prvnxt) - 1;
  1061. }
  1062. if ($nxt >= count($prvnxt)) {
  1063. $nxt = 0;
  1064. }
  1065. break;
  1066. }
  1067. }
  1068. $edit_suffix = ((($cmd === 'edit') && $can_post) ? '/edit' : '');
  1069. $prevlink = 'photos/' . $a->data['user']['nickname'] . '/image/' . $prvnxt[$prv]['resource-id'] . $edit_suffix . ($order_field === 'posted' ? '?f=&order=posted' : '');
  1070. $nextlink = 'photos/' . $a->data['user']['nickname'] . '/image/' . $prvnxt[$nxt]['resource-id'] . $edit_suffix . ($order_field === 'posted' ? '?f=&order=posted' : '');
  1071. }
  1072. }
  1073. if (count($ph) == 1) {
  1074. $hires = $lores = $ph[0];
  1075. }
  1076. if (count($ph) > 1) {
  1077. if ($ph[1]['scale'] == 2) {
  1078. // original is 640 or less, we can display it directly
  1079. $hires = $lores = $ph[0];
  1080. } else {
  1081. $hires = $ph[0];
  1082. $lores = $ph[1];
  1083. }
  1084. }
  1085. $album_link = 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($ph[0]['album']);
  1086. $tools = null;
  1087. $lock = null;
  1088. if ($can_post && ($ph[0]['uid'] == $owner_uid)) {
  1089. $tools = [
  1090. 'edit' => ['photos/' . $a->data['user']['nickname'] . '/image/' . $datum . (($cmd === 'edit') ? '' : '/edit'), (($cmd === 'edit') ? L10n::t('View photo') : L10n::t('Edit photo'))],
  1091. 'profile'=>['profile_photo/use/'.$ph[0]['resource-id'], L10n::t('Use as profile photo')],
  1092. ];
  1093. // lock
  1094. $lock = ((($ph[0]['uid'] == local_user()) && (strlen($ph[0]['allow_cid']) || strlen($ph[0]['allow_gid'])
  1095. || strlen($ph[0]['deny_cid']) || strlen($ph[0]['deny_gid'])))
  1096. ? L10n::t('Private Message')
  1097. : Null);
  1098. }
  1099. if ($cmd === 'edit') {
  1100. $tpl = get_markup_template('photo_edit_head.tpl');
  1101. $a->page['htmlhead'] .= replace_macros($tpl,[
  1102. '$prevlink' => $prevlink,
  1103. '$nextlink' => $nextlink
  1104. ]);
  1105. }
  1106. if ($prevlink) {
  1107. $prevlink = [$prevlink, '<div class="icon prev"></div>'] ;
  1108. }
  1109. $photo = [
  1110. 'href' => 'photo/' . $hires['resource-id'] . '-' . $hires['scale'] . '.' . $phototypes[$hires['type']],
  1111. 'title'=> L10n::t('View Full Size'),
  1112. 'src' => 'photo/' . $lores['resource-id'] . '-' . $lores['scale'] . '.' . $phototypes[$lores['type']] . '?f=&_u=' . DateTimeFormat::utcNow('ymdhis'),
  1113. 'height' => $hires['height'],
  1114. 'width' => $hires['width'],
  1115. 'album' => $hires['album'],
  1116. 'filename' => $hires['filename'],
  1117. ];
  1118. if ($nextlink) {
  1119. $nextlink = [$nextlink, '<div class="icon next"></div>'];
  1120. }
  1121. // Do we have an item for this photo?
  1122. // FIXME! - replace following code to display the conversation with our normal
  1123. // conversation functions so that it works correctly and tracks changes
  1124. // in the evolving conversation code.
  1125. // The difference is that we won't be displaying the conversation head item
  1126. // as a "post" but displaying instead the photo it is linked to
  1127. /// @todo Rewrite this query. To do so, $sql_extra must be changed
  1128. $linked_items = q("SELECT `id` FROM `item` WHERE `resource-id` = '%s' $sql_extra LIMIT 1",
  1129. dbesc($datum)
  1130. );
  1131. $map = null;
  1132. $link_item = [];
  1133. if (DBM::is_result($linked_items)) {
  1134. // This is a workaround to not being forced to rewrite the while $sql_extra handling
  1135. $link_item = Item::selectFirstForUser(local_user(), [], ['id' => $linked_items[0]['id']]);
  1136. $r = q("SELECT COUNT(*) AS `total`
  1137. FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
  1138. WHERE `parent-uri` = '%s' AND `uri` != '%s' AND `item`.`deleted` = 0 and `item`.`moderated` = 0
  1139. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  1140. AND `item`.`uid` = %d
  1141. $sql_extra ",
  1142. dbesc($link_item['uri']),
  1143. dbesc($link_item['uri']),
  1144. intval($link_item['uid'])
  1145. );
  1146. if (DBM::is_result($r)) {
  1147. $a->set_pager_total($r[0]['total']);
  1148. }
  1149. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  1150. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`network`,
  1151. `contact`.`rel`, `contact`.`thumb`, `contact`.`self`,
  1152. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  1153. FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
  1154. WHERE `parent-uri` = '%s' AND `uri` != '%s' AND `item`.`deleted` = 0 and `item`.`moderated` = 0
  1155. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  1156. AND `item`.`uid` = %d
  1157. $sql_extra
  1158. ORDER BY `parent` DESC, `id` ASC LIMIT %d ,%d ",
  1159. dbesc($link_item['uri']),
  1160. dbesc($link_item['uri']),
  1161. intval($link_item['uid']),
  1162. intval($a->pager['start']),
  1163. intval($a->pager['itemspage'])
  1164. );
  1165. if (local_user() && (local_user() == $link_item['uid'])) {
  1166. Item::update(['unseen' => false], ['parent' => $link_item['parent']]);
  1167. }
  1168. if ($link_item['coord']) {
  1169. $map = Map::byCoordinates($link_item['coord']);
  1170. }
  1171. }
  1172. $tags = null;
  1173. if (count($linked_items) && strlen($link_item['tag'])) {
  1174. $arr = explode(',', $link_item['tag']);
  1175. // parse tags and add links
  1176. $tag_str = '';
  1177. foreach ($arr as $t) {
  1178. if (strlen($tag_str)) {
  1179. $tag_str .= ', ';
  1180. }
  1181. $tag_str .= BBCode::convert($t);
  1182. }
  1183. $tags = [L10n::t('Tags: '), $tag_str];
  1184. if ($cmd === 'edit') {
  1185. $tags[] = 'tagrm/' . $link_item['id'];
  1186. $tags[] = L10n::t('[Remove any tag]');
  1187. }
  1188. }
  1189. $edit = Null;
  1190. if ($cmd === 'edit' && $can_post) {
  1191. $edit_tpl = get_markup_template('photo_edit.tpl');
  1192. $album_e = $ph[0]['album'];
  1193. $caption_e = $ph[0]['desc'];
  1194. $aclselect_e = ACL::getFullSelectorHTML($ph[0]);
  1195. $edit = replace_macros($edit_tpl, [
  1196. '$id' => $ph[0]['id'],
  1197. '$album' => ['albname', L10n::t('New album name'), $album_e,''],
  1198. '$caption' => ['desc', L10n::t('Caption'), $caption_e, ''],
  1199. '$tags' => ['newtag', L10n::t('Add a Tag'), "", L10n::t('Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping')],
  1200. '$rotate_none' => ['rotate', L10n::t('Do not rotate'),0,'', true],
  1201. '$rotate_cw' => ['rotate', L10n::t("Rotate CW \x28right\x29"),1,''],
  1202. '$rotate_ccw' => ['rotate', L10n::t("Rotate CCW \x28left\x29"),2,''],
  1203. '$nickname' => $a->data['user']['nickname'],
  1204. '$resource_id' => $ph[0]['resource-id'],
  1205. '$permissions' => L10n::t('Permissions'),
  1206. '$aclselect' => $aclselect_e,
  1207. '$item_id' => defaults($link_item, 'id', 0),
  1208. '$submit' => L10n::t('Submit'),
  1209. '$delete' => L10n::t('Delete Photo'),
  1210. // ACL permissions box
  1211. '$group_perms' => L10n::t('Show to Groups'),
  1212. '$contact_perms' => L10n::t('Show to Contacts'),
  1213. '$return_path' => $a->query_string,
  1214. ]);
  1215. }
  1216. $like = '';
  1217. $dislike = '';
  1218. $likebuttons = '';
  1219. $comments = '';
  1220. $paginate = '';
  1221. $responses = '';
  1222. if (count($linked_items)) {
  1223. $cmnt_tpl = get_markup_template('comment_item.tpl');
  1224. $tpl = get_markup_template('photo_item.tpl');
  1225. $return_url = $a->cmd;
  1226. if ($can_post || can_write_wall($owner_uid)) {
  1227. $like_tpl = get_markup_template('like_noshare.tpl');
  1228. $likebuttons = replace_macros($like_tpl, [
  1229. '$id' => $link_item['id'],
  1230. '$likethis' => L10n::t("I like this \x28toggle\x29"),
  1231. '$nolike' => (Feature::isEnabled(local_user(), 'dislike') ? L10n::t("I don't like this \x28toggle\x29") : ''),
  1232. '$wait' => L10n::t('Please wait'),
  1233. '$return_path' => $a->query_string,
  1234. ]);
  1235. }
  1236. if (!DBM::is_result($r)) {
  1237. if (($can_post || can_write_wall($owner_uid))) {
  1238. $comments .= replace_macros($cmnt_tpl, [
  1239. '$return_path' => '',
  1240. '$jsreload' => $return_url,
  1241. '$type' => 'wall-comment',
  1242. '$id' => $link_item['id'],
  1243. '$parent' => $link_item['id'],
  1244. '$profile_uid' => $owner_uid,
  1245. '$mylink' => $contact['url'],
  1246. '$mytitle' => L10n::t('This is you'),
  1247. '$myphoto' => $contact['thumb'],
  1248. '$comment' => L10n::t('Comment'),
  1249. '$submit' => L10n::t('Submit'),
  1250. '$preview' => L10n::t('Preview'),
  1251. '$sourceapp' => L10n::t($a->sourcename),
  1252. '$ww' => '',
  1253. '$rand_num' => random_digits(12)
  1254. ]);
  1255. }
  1256. }
  1257. $conv_responses = [
  1258. 'like' => ['title' => L10n::t('Likes','title')],'dislike' => ['title' => L10n::t('Dislikes','title')],
  1259. 'attendyes' => ['title' => L10n::t('Attending','title')], 'attendno' => ['title' => L10n::t('Not attending','title')], 'attendmaybe' => ['title' => L10n::t('Might attend','title')]
  1260. ];
  1261. // display comments
  1262. if (DBM::is_result($r)) {
  1263. foreach ($r as $item) {
  1264. builtin_activity_puller($item, $conv_responses);
  1265. }
  1266. if (x($conv_responses['like'], $link_item['uri'])) {
  1267. $like = format_like($conv_responses['like'][$link_item['uri']], $conv_responses['like'][$link_item['uri'] . '-l'], 'like', $link_item['id']);
  1268. }
  1269. if (x($conv_responses['dislike'], $link_item['uri'])) {
  1270. $dislike = format_like($conv_responses['dislike'][$link_item['uri']], $conv_responses['dislike'][$link_item['uri'] . '-l'], 'dislike', $link_item['id']);
  1271. }
  1272. if (($can_post || can_write_wall($owner_uid))) {
  1273. $comments .= replace_macros($cmnt_tpl,[
  1274. '$return_path' => '',
  1275. '$jsreload' => $return_url,
  1276. '$type' => 'wall-comment',
  1277. '$id' => $link_item['id'],
  1278. '$parent' => $link_item['id'],
  1279. '$profile_uid' => $owner_uid,
  1280. '$mylink' => $contact['url'],
  1281. '$mytitle' => L10n::t('This is you'),
  1282. '$myphoto' => $contact['thumb'],
  1283. '$comment' => L10n::t('Comment'),
  1284. '$submit' => L10n::t('Submit'),
  1285. '$preview' => L10n::t('Preview'),
  1286. '$sourceapp' => L10n::t($a->sourcename),
  1287. '$ww' => '',
  1288. '$rand_num' => random_digits(12)
  1289. ]);
  1290. }
  1291. foreach ($r as $item) {
  1292. $comment = '';
  1293. $template = $tpl;
  1294. $sparkle = '';
  1295. if ((activity_match($item['verb'], ACTIVITY_LIKE) || activity_match($item['verb'], ACTIVITY_DISLIKE)) && ($item['id'] != $item['parent'])) {
  1296. continue;
  1297. }
  1298. $profile_url = Contact::MagicLinkById($item['cid']);
  1299. if (strpos($profile_url, 'redir/') === 0) {
  1300. $sparkle = ' sparkle';
  1301. } else {
  1302. $sparkle = '';
  1303. }
  1304. $diff_author = (($item['url'] !== $item['author-link']) ? true : false);
  1305. $profile_name = ((strlen($item['author-name']) && $diff_author) ? $item['author-name'] : $item['name']);
  1306. $profile_avatar = ((strlen($item['author-avatar']) && $diff_author) ? $item['author-avatar'] : $item['thumb']);
  1307. $profile_link = $profile_url;
  1308. $dropping = (($item['contact-id'] == $contact_id) || ($item['uid'] == local_user()));
  1309. $drop = [
  1310. 'dropping' => $dropping,
  1311. 'pagedrop' => false,
  1312. 'select' => L10n::t('Select'),
  1313. 'delete' => L10n::t('Delete'),
  1314. ];
  1315. $name_e = $profile_name;
  1316. $title_e = $item['title'];
  1317. $body_e = BBCode::convert($item['body']);
  1318. $comments .= replace_macros($template,[
  1319. '$id' => $item['item_id'],
  1320. '$profile_url' => $profile_link,
  1321. '$name' => $name_e,
  1322. '$thumb' => $profile_avatar,
  1323. '$sparkle' => $sparkle,
  1324. '$title' => $title_e,
  1325. '$body' => $body_e,
  1326. '$ago' => Temporal::getRelativeDate($item['created']),
  1327. '$indent' => (($item['parent'] != $item['item_id']) ? ' comment' : ''),
  1328. '$drop' => $drop,
  1329. '$comment' => $comment
  1330. ]);
  1331. if (($can_post || can_write_wall($owner_uid))) {
  1332. $comments .= replace_macros($cmnt_tpl, [
  1333. '$return_path' => '',
  1334. '$jsreload' => $return_url,
  1335. '$type' => 'wall-comment',
  1336. '$id' => $item['item_id'],
  1337. '$parent' => $item['parent'],
  1338. '$profile_uid' => $owner_uid,
  1339. '$mylink' => $contact['url'],
  1340. '$mytitle' => L10n::t('This is you'),
  1341. '$myphoto' => $contact['thumb'],
  1342. '$comment' => L10n::t('Comment'),
  1343. '$submit' => L10n::t('Submit'),
  1344. '$preview' => L10n::t('Preview'),
  1345. '$sourceapp' => L10n::t($a->sourcename),
  1346. '$ww' => '',
  1347. '$rand_num' => random_digits(12)
  1348. ]);
  1349. }
  1350. }
  1351. }
  1352. $response_verbs = ['like'];
  1353. if (Feature::isEnabled($owner_uid, 'dislike')) {
  1354. $response_verbs[] = 'dislike';
  1355. }
  1356. $responses = get_responses($conv_responses, $response_verbs, '', $link_item);
  1357. $paginate = paginate($a);
  1358. }
  1359. $photo_tpl = get_markup_template('photo_view.tpl');
  1360. $o .= replace_macros($photo_tpl, [
  1361. '$id' => $ph[0]['id'],
  1362. '$album' => [$album_link, $ph[0]['album']],
  1363. '$tools' => $tools,
  1364. '$lock' => $lock,
  1365. '$photo' => $photo,
  1366. '$prevlink' => $prevlink,
  1367. '$nextlink' => $nextlink,
  1368. '$desc' => $ph[0]['desc'],
  1369. '$tags' => $tags,
  1370. '$edit' => $edit,
  1371. '$map' => $map,
  1372. '$map_text' => L10n::t('Map'),
  1373. '$likebuttons' => $likebuttons,
  1374. '$like' => $like,
  1375. '$dislike' => $dislike,
  1376. 'responses' => $responses,
  1377. '$comments' => $comments,
  1378. '$paginate' => $paginate,
  1379. ]);
  1380. $a->page['htmlhead'] .= "\n" . '<meta name="twitter:card" content="photo" />' . "\n";
  1381. $a->page['htmlhead'] .= '<meta name="twitter:title" content="' . $photo["album"] . '" />' . "\n";
  1382. $a->page['htmlhead'] .= '<meta name="twitter:image" content="' . $photo["href"] . '" />' . "\n";
  1383. $a->page['htmlhead'] .= '<meta name="twitter:image:width" content="' . $photo["width"] . '" />' . "\n";
  1384. $a->page['htmlhead'] .= '<meta name="twitter:image:height" content="' . $photo["height"] . '" />' . "\n";
  1385. return $o;
  1386. }
  1387. // Default - show recent photos with upload link (if applicable)
  1388. //$o = '';
  1389. $r = q("SELECT `resource-id`, max(`scale`) AS `scale` FROM `photo` WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s'
  1390. $sql_extra GROUP BY `resource-id`",
  1391. intval($a->data['user']['uid']),
  1392. dbesc('Contact Photos'),
  1393. dbesc(L10n::t('Contact Photos'))
  1394. );
  1395. if (DBM::is_result($r)) {
  1396. $a->set_pager_total(count($r));
  1397. $a->set_pager_itemspage(20);
  1398. }
  1399. $r = q("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`,
  1400. ANY_VALUE(`type`) AS `type`, ANY_VALUE(`album`) AS `album`, max(`scale`) AS `scale`,
  1401. ANY_VALUE(`created`) AS `created` FROM `photo`
  1402. WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s'
  1403. $sql_extra GROUP BY `resource-id` ORDER BY `created` DESC LIMIT %d , %d",
  1404. intval($a->data['user']['uid']),
  1405. dbesc('Contact Photos'),
  1406. dbesc(L10n::t('Contact Photos')),
  1407. intval($a->pager['start']),
  1408. intval($a->pager['itemspage'])
  1409. );
  1410. $photos = [];
  1411. if (DBM::is_result($r)) {
  1412. // "Twist" is only used for the duepunto theme with style "slackr"
  1413. $twist = false;
  1414. foreach ($r as $rr) {
  1415. //hide profile photos to others
  1416. if (!$is_owner && !remote_user() && ($rr['album'] == L10n::t('Profile Photos'))) {
  1417. continue;
  1418. }
  1419. $twist = !$twist;
  1420. $ext = $phototypes[$rr['type']];
  1421. $alt_e = $rr['filename'];
  1422. $name_e = $rr['album'];
  1423. $photos[] = [
  1424. 'id' => $rr['id'],
  1425. 'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2,4),
  1426. 'link' => 'photos/' . $a->data['user']['nickname'] . '/image/' . $rr['resource-id'],
  1427. 'title' => L10n::t('View Photo'),
  1428. 'src' => 'photo/' . $rr['resource-id'] . '-' . ((($rr['scale']) == 6) ? 4 : $rr['scale']) . '.' . $ext,
  1429. 'alt' => $alt_e,
  1430. 'album' => [
  1431. 'link' => 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($rr['album']),
  1432. 'name' => $name_e,
  1433. 'alt' => L10n::t('View Album'),
  1434. ],
  1435. ];
  1436. }
  1437. }
  1438. $tpl = get_markup_template('photos_recent.tpl');
  1439. $o .= replace_macros($tpl, [
  1440. '$title' => L10n::t('Recent Photos'),
  1441. '$can_post' => $can_post,
  1442. '$upload' => [L10n::t('Upload New Photos'), 'photos/'.$a->data['user']['nickname'].'/upload'],
  1443. '$photos' => $photos,
  1444. '$paginate' => paginate($a),
  1445. ]);
  1446. return $o;
  1447. }