Merge remote-tracking branch 'upstream/develop' into no-api-post

This commit is contained in:
Michael 2022-11-23 06:28:24 +00:00
commit 25992b063a
71 changed files with 5016 additions and 4336 deletions

258
.ddev/config.yaml Normal file
View File

@ -0,0 +1,258 @@
name: my-friendica
type: php
docroot: ""
php_version: "7.3"
webserver_type: apache-fpm
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
database:
type: mariadb
version: "10.4"
nfs_mount_enabled: false
mutagen_enabled: false
use_dns_when_possible: true
composer_version: "1"
web_environment: []
nodejs_version: "16"
webimage_extra_packages: [php7.3-gmp]
# Key features of ddev's config.yaml:
# name: <projectname> # Name of the project, automatically provides
# http://projectname.ddev.site and https://projectname.ddev.site
# type: <projecttype> # drupal6/7/8, backdrop, typo3, wordpress, php
# docroot: <relative_path> # Relative path to the directory containing index.php.
# php_version: "7.4" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2"
# You can explicitly specify the webimage but this
# is not recommended, as the images are often closely tied to ddev's' behavior,
# so this can break upgrades.
# webimage: <docker_image> # nginx/php docker image.
# database:
# type: <dbtype> # mysql, mariadb
# version: <version> # database version, like "10.3" or "8.0"
# Note that mariadb_version or mysql_version from v1.18 and earlier
# will automatically be converted to this notation with just a "ddev config --auto"
# router_http_port: <port> # Port to be used for http (defaults to port 80)
# router_https_port: <port> # Port for https (defaults to 443)
# xdebug_enabled: false # Set to true to enable xdebug and "ddev start" or "ddev restart"
# Note that for most people the commands
# "ddev xdebug" to enable xdebug and "ddev xdebug off" to disable it work better,
# as leaving xdebug enabled all the time is a big performance hit.
# xhprof_enabled: false # Set to true to enable xhprof and "ddev start" or "ddev restart"
# Note that for most people the commands
# "ddev xhprof" to enable xhprof and "ddev xhprof off" to disable it work better,
# as leaving xhprof enabled all the time is a big performance hit.
# webserver_type: nginx-fpm # or apache-fpm
# timezone: Europe/Berlin
# This is the timezone used in the containers and by PHP;
# it can be set to any valid timezone,
# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# For example Europe/Dublin or MST7MDT
# composer_root: <relative_path>
# Relative path to the composer root directory from the project root. This is
# the directory which contains the composer.json and where all Composer related
# commands are executed.
# composer_version: "2"
# You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1
# to use the latest major version available at the time your container is built.
# It is also possible to use each other Composer version channel. This includes:
# - 2.2 (latest Composer LTS version)
# - stable
# - preview
# - snapshot
# Alternatively, an explicit Composer version may be specified, for example "2.2.18".
# To reinstall Composer after the image was built, run "ddev debug refresh".
# nodejs_version: "16"
# change from the default system Node.js version to another supported version, like 12, 14, 17, 18.
# Note that you can use 'ddev nvm' or nvm inside the web container to provide nearly any
# Node.js version, including v6, etc.
# additional_hostnames:
# - somename
# - someothername
# would provide http and https URLs for "somename.ddev.site"
# and "someothername.ddev.site".
# additional_fqdns:
# - example.com
# - sub1.example.com
# would provide http and https URLs for "example.com" and "sub1.example.com"
# Please take care with this because it can cause great confusion.
# upload_dir: custom/upload/dir
# would set the destination path for ddev import-files to <docroot>/custom/upload/dir
# When mutagen is enabled this path is bind-mounted so that all the files
# in the upload_dir don't have to be synced into mutagen
# working_dir:
# web: /var/www/html
# db: /home
# would set the default working directory for the web and db services.
# These values specify the destination directory for ddev ssh and the
# directory in which commands passed into ddev exec are run.
# omit_containers: [db, dba, ddev-ssh-agent]
# Currently only these containers are supported. Some containers can also be
# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit
# the "db" container, several standard features of ddev that access the
# database container will be unusable. In the global configuration it is also
# possible to omit ddev-router, but not here.
# nfs_mount_enabled: false
# Great performance improvement but requires host configuration first.
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs
# mutagen_enabled: false
# Performance improvement using mutagen asynchronous updates.
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen
# fail_on_hook_fail: False
# Decide whether 'ddev start' should be interrupted by a failing hook
# host_https_port: "59002"
# The host port binding for https can be explicitly specified. It is
# dynamic unless otherwise specified.
# This is not used by most people, most people use the *router* instead
# of the localhost port.
# host_webserver_port: "59001"
# The host port binding for the ddev-webserver can be explicitly specified. It is
# dynamic unless otherwise specified.
# This is not used by most people, most people use the *router* instead
# of the localhost port.
# host_db_port: "59002"
# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic
# unless explicitly specified.
# phpmyadmin_port: "8036"
# phpmyadmin_https_port: "8037"
# The PHPMyAdmin ports can be changed from the default 8036 and 8037
# host_phpmyadmin_port: "8036"
# The phpmyadmin (dba) port is not normally bound on the host at all, instead being routed
# through ddev-router, but it can be specified and bound.
# mailhog_port: "8025"
# mailhog_https_port: "8026"
# The MailHog ports can be changed from the default 8025 and 8026
# host_mailhog_port: "8025"
# The mailhog port is not normally bound on the host at all, instead being routed
# through ddev-router, but it can be bound directly to localhost if specified here.
# webimage_extra_packages: [php7.4-tidy, php-bcmath]
# Extra Debian packages that are needed in the webimage can be added here
# dbimage_extra_packages: [telnet,netcat]
# Extra Debian packages that are needed in the dbimage can be added here
# use_dns_when_possible: true
# If the host has internet access and the domain configured can
# successfully be looked up, DNS will be used for hostname resolution
# instead of editing /etc/hosts
# Defaults to true
# project_tld: ddev.site
# The top-level domain used for project URLs
# The default "ddev.site" allows DNS lookup via a wildcard
# If you prefer you can change this to "ddev.local" to preserve
# pre-v1.9 behavior.
# ngrok_args: --basic-auth username:pass1234
# Provide extra flags to the "ngrok http" command, see
# https://ngrok.com/docs#http or run "ngrok http -h"
# disable_settings_management: false
# If true, ddev will not create CMS-specific settings files like
# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php
# In this case the user must provide all such settings.
# You can inject environment variables into the web container with:
# web_environment:
# - SOMEENV=somevalue
# - SOMEOTHERENV=someothervalue
# no_project_mount: false
# (Experimental) If true, ddev will not mount the project into the web container;
# the user is responsible for mounting it manually or via a script.
# This is to enable experimentation with alternate file mounting strategies.
# For advanced users only!
# bind_all_interfaces: false
# If true, host ports will be bound on all network interfaces,
# not just the localhost interface. This means that ports
# will be available on the local network if the host firewall
# allows it.
# default_container_timeout: 120
# The default time that ddev waits for all containers to become ready can be increased from
# the default 120. This helps in importing huge databases, for example.
#web_extra_exposed_ports:
#- name: nodejs
# container_port: 3000
# http_port: 2999
# https_port: 3000
#- name: something
# container_port: 4000
# https_port: 4000
# http_port: 3999
# Allows a set of extra ports to be exposed via ddev-router
# The port behavior on the ddev-webserver must be arranged separately, for example
# using web_extra_daemons.
# For example, with a web app on port 3000 inside the container, this config would
# expose that web app on https://<project>.ddev.site:9999 and http://<project>.ddev.site:9998
# web_extra_exposed_ports:
# - container_port: 3000
# http_port: 9998
# https_port: 9999
#web_extra_daemons:
#- name: "http-1"
# command: "/var/www/html/node_modules/.bin/http-server -p 3000"
# directory: /var/www/html
#- name: "http-2"
# command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000"
# directory: /var/www/html
# override_config: false
# By default, config.*.yaml files are *merged* into the configuration
# But this means that some things can't be overridden
# For example, if you have 'nfs_mount_enabled: true'' you can't override it with a merge
# and you can't erase existing hooks or all environment variables.
# However, with "override_config: true" in a particular config.*.yaml file,
# 'nfs_mount_enabled: false' can override the existing values, and
# hooks:
# post-start: []
# or
# web_environment: []
# or
# additional_hostnames: []
# can have their intended affect. 'override_config' affects only behavior of the
# config.*.yaml file it exists in.
# Many ddev commands can be extended to run tasks before or after the
# ddev command is executed, for example "post-start", "post-import-db",
# "pre-composer", "post-composer"
# See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more
# information on the commands that can be extended and the tasks you can define
# for them. Example:
#hooks:

7
.gitignore vendored
View File

@ -10,10 +10,9 @@ home.html
robots.txt
#ignore local config
/config/local.config.php
/config/addon.config.php
/config/local.ini.php
/config/addon.ini.php
!/config/local-sample.config.php
/config/*.config.php
/config/*.ini.php
#ignore documentation, it should be newly built
/doc/api

View File

@ -9,7 +9,7 @@ Our mission is to free friends, family and colleagues from data-harvesting corpo
Friendica connects you effortlessly to a federated communications network of several thousand servers, with more than half a million user registrations. You can directly connect to anyone on [Friendica]( https://friendi.ca), [Mastodon](https://joinmastodon.org/), [Diaspora](https://diasporafoundation.org/), [GnuSocial](https://gnu.io/social/), [Pleroma](https://pleroma.social/), or [Hubzilla](https://hubzilla.org/), regardless where each user profile is hosted.
With Friendica, you can also fully interact with anyone on Twitter, post on Facebook and receive any content on Tumblr, Wordpress or RSS. Friendica allows you to integrate most things on the web via a range of addons such as ITTT, Buffer; you will be able to easily control your own data as you decide.
With Friendica, you can also fully interact with anyone on Twitter and receive any content from Tumblr, Wordpress or RSS. Friendica allows you to integrate most things on the web via a range of addons such as ITTT, Buffer; you will be able to easily control your own data as you decide.
Join today and [get your Friendica profile!](https://dir.friendica.social/servers 'Join Friendica today!')

View File

@ -1,12 +0,0 @@
<?php
// Addon configuration
// Copy this configuration file to addon.config.php and edit it if you want to configure addons, see below example for the twitter addon
return [
'twitter' => [
'consumerkey' => '1234567890',
'consumersecret' => 'ABCDEFGHIJKLMONPQRSTUVWXYZ',
],
];

View File

@ -790,10 +790,6 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('post_local', $datarray);
Hook::callAll('post_local_end', $datarray);
### mod/editpost.php
Hook::callAll('jot_tool', $jotplugins);
### src/Render/FriendicaSmartyEngine.php
Hook::callAll("template_vars", $arr);
@ -855,6 +851,10 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('lockview_content', $item);
### src/Module/Post/Edit.php
Hook::callAll('jot_tool', $jotplugins);
### src/Module/Settings/Delegation.php
Hook::callAll('authenticate', $addon_auth);

View File

@ -37,8 +37,8 @@ The `config` directory holds key configuration files and can have different conf
All of them have to end with `.config.php` and must not include `-sample` in their name.
Some examples of common known configuration files:
- `local.config.php` holds the current node custom configuration.
- `addon.config.php` is optional and holds the custom configuration for specific addons.
- `local.config.php` holds the base node custom configuration.
- Any other file in this folder is meant for additional configuration (e.g. for addons).
Addons can define their own default configuration values in `addon/[addon]/config/[addon].config.php` which is loaded when the addon is activated.
@ -59,7 +59,7 @@ Currently, the following configurations are included:
The legacy `.htconfig.php` configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.
The migration is pretty straightforward:
If you had any addon-specific configuration in your `.htconfig.php`, just copy `config/addon-sample.config.php` to `config/addon.config.php` and move your configuration values.
If you had any addon-specific configuration in your `.htconfig.php`, copy `config/local-sample.config.php` to `config/addon.config.php` and move your configuration values.
Afterwards, copy `config/local-sample.config.php` to `config/local.config.php`, move the remaining configuration values to it according to the following conversion chart, then rename your `.htconfig.php` to check your node is working as expected before deleting it.
<style>
@ -206,7 +206,7 @@ $lang = "value";
The legacy `config/local.ini.php` configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.
The migration is pretty straightforward:
If you had any addon-specific configuration in your `config/addon.ini.php`, just copy `config/addon-sample.config.php` to `config/addon.config.php` and move your configuration values.
If you had any addon-specific configuration in your `config/addon.ini.php`, copy `config/local-sample.config.php` to `config/addon.config.php` and move your configuration values.
Afterwards, copy `config/local-sample.config.php` to `config/local.config.php`, move the remaining configuration values to it according to the following conversion chart, then rename your `config/local.ini.php` file to check your node is working as expected before deleting it.
<table class="config">
@ -278,16 +278,16 @@ key[] = value3
### Database Settings
The configuration variables database.hostname, database.username, database.password, database.database and database.charset are holding your credentials for the database connection.
If you need to specify a port to access the database, you can do so by appending ":portnumber" to the database.hostname variable.
The configuration variables `database.hostname` (or `database.socket`), `database.username`, `database.password`, `database.database` and optionally `database.charset` are holding your credentials for the database connection.
If you need to specify a port to access the database, you can do so by appending ":portnumber" to the `database.hostname` variable.
'database' => [
'hostname' => 'your.mysqlhost.com:123456',
]
If all of the following environment variables are set, Friendica will use them instead of the previously configured variables for the db:
If all the following environment variables are set, Friendica will use them instead of the previously configured variables for the db:
MYSQL_HOST
MYSQL_HOST or MYSQL_SOCKET
MYSQL_PORT
MYSQL_USERNAME
MYSQL_PASSWORD
@ -316,7 +316,7 @@ Enabling the admin panel for an account, and thus making the account holder admi
Where you have to match the email address used for the account with the one you enter to the `config/local.config.php` file.
If more then one account should be able to access the admin panel, separate the email addresses with a comma.
If more than one account should be able to access the admin panel, separate the email addresses with a comma.
'config' => [
'admin_email' => 'someone@example.com,someoneelse@example.com',

View File

@ -50,7 +50,7 @@ We recommend to talk to the admin(s) of the affected friendica server. (Admins,
### How can I upload images, files, links, videos and sound files to posts?
You can upload images from your computer using the [editor](help/Text_editor).
An overview of all uploaded images is listed at *yourpage.com/photos/profilename*.
An overview of all uploaded images is listed at *yourpage.com/profile/profilename/photos*.
On that page, you can also upload images directly and choose if your contacts will receive a message about this upload.
Generally, you can attach any kind of file to a post.

View File

@ -309,10 +309,6 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('post_local', $datarray);
Hook::callAll('post_local_end', $datarray);
### mod/editpost.php
Hook::callAll('jot_tool', $jotplugins);
### src/Network/FKOAuth1.php
Hook::callAll('logged_in', $a->user);
@ -422,6 +418,10 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('lockview_content', $item);
### src/Module/Post/Edit.php
Hook::callAll('jot_tool', $jotplugins);
### src/Worker/Directory.php
Hook::callAll('globaldir_update', $arr);

View File

@ -69,7 +69,7 @@ Andere erlauben nur kostenpflichtige Zertifikate als eigenes Angebot bzw. von an
### Wie kann ich Bilder, Dateien, Links, Video und Audio in Beiträge einfügen?
Bilder können direkt im [Beitragseditor](help/Text_editor) vom Computer hochgeladen werden.
Eine Übersicht aller Bilder, die auf Deinem Server liegen, findest Du unter <i>deineSeite.de/photos/profilname</i>.
Eine Übersicht aller Bilder, die auf Deinem Server liegen, findest Du unter <i>deineSeite.de/profile/profilname/photos</i>.
Dort kannst Du auch direkt Bilder hochladen und festlegen, ob Deine Kontakte eine Nachricht über das neue Bild bekommen.
Alle Arten von Dateien können grundsätzlich als Anhang in Friendica hochgeladen werden.

View File

@ -1,166 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\Feature;
use Friendica\Core\Hook;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Util\Crypto;
function editpost_content(App $a)
{
$o = '';
if (!DI::userSession()->getLocalUserId()) {
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
return;
}
$post_id = ((DI::args()->getArgc() > 1) ? intval(DI::args()->getArgv()[1]) : 0);
if (!$post_id) {
DI::sysmsg()->addNotice(DI::l10n()->t('Item not found'));
return;
}
$fields = ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'body', 'title', 'uri-id', 'wall', 'post-type', 'guid'];
$item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), $fields, ['id' => $post_id, 'uid' => DI::userSession()->getLocalUserId()]);
if (!DBA::isResult($item)) {
DI::sysmsg()->addNotice(DI::l10n()->t('Item not found'));
return;
}
$user = User::getById(DI::userSession()->getLocalUserId());
$geotag = '';
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate("section_title.tpl"), [
'$title' => DI::l10n()->t('Edit post')
]);
$tpl = Renderer::getMarkupTemplate('jot-header.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$ispublic' => '&nbsp;', // DI::l10n()->t('Visible to <strong>everybody</strong>'),
'$geotag' => $geotag,
'$nickname' => $a->getLoggedInUserNickname(),
'$is_mobile' => DI::mode()->isMobile(),
]);
if (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) {
$lockstate = 'lock';
} else {
$lockstate = 'unlock';
}
$jotplugins = '';
$jotnets = '';
Hook::callAll('jot_tool', $jotplugins);
$tpl = Renderer::getMarkupTemplate('jot.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$is_edit' => true,
'$return_path' => '/display/' . $item['guid'],
'$action' => 'item',
'$share' => DI::l10n()->t('Save'),
'$loading' => DI::l10n()->t('Loading...'),
'$upload' => DI::l10n()->t('Upload photo'),
'$shortupload' => DI::l10n()->t('upload photo'),
'$attach' => DI::l10n()->t('Attach file'),
'$shortattach' => DI::l10n()->t('attach file'),
'$weblink' => DI::l10n()->t('Insert web link'),
'$shortweblink' => DI::l10n()->t('web link'),
'$video' => DI::l10n()->t('Insert video link'),
'$shortvideo' => DI::l10n()->t('video link'),
'$audio' => DI::l10n()->t('Insert audio link'),
'$shortaudio' => DI::l10n()->t('audio link'),
'$setloc' => DI::l10n()->t('Set your location'),
'$shortsetloc' => DI::l10n()->t('set location'),
'$noloc' => DI::l10n()->t('Clear browser location'),
'$shortnoloc' => DI::l10n()->t('clear location'),
'$wait' => DI::l10n()->t('Please wait'),
'$permset' => DI::l10n()->t('Permission settings'),
'$wall' => $item['wall'],
'$posttype' => $item['post-type'],
'$content' => undo_post_tagging($item['body']),
'$post_id' => $post_id,
'$defloc' => $user['default-location'],
'$visitor' => 'none',
'$pvisit' => 'none',
'$emailcc' => DI::l10n()->t('CC: email addresses'),
'$public' => DI::l10n()->t('Public post'),
'$jotnets' => $jotnets,
'$title' => $item['title'],
'$placeholdertitle' => DI::l10n()->t('Set title'),
'$category' => Post\Category::getCSVByURIId($item['uri-id'], DI::userSession()->getLocalUserId(), Post\Category::CATEGORY),
'$placeholdercategory' => (Feature::isEnabled(DI::userSession()->getLocalUserId(),'categories') ? DI::l10n()->t("Categories \x28comma-separated list\x29") : ''),
'$emtitle' => DI::l10n()->t('Example: bob@example.com, mary@example.com'),
'$lockstate' => $lockstate,
'$acl' => '', // populate_acl((($group) ? $group_acl : $a->user)),
'$bang' => ($lockstate === 'lock' ? '!' : ''),
'$profile_uid' => $_SESSION['uid'],
'$preview' => DI::l10n()->t('Preview'),
'$jotplugins' => $jotplugins,
'$cancel' => DI::l10n()->t('Cancel'),
'$rand_num' => Crypto::randomDigits(12),
// Formatting button labels
'$edbold' => DI::l10n()->t('Bold'),
'$editalic' => DI::l10n()->t('Italic'),
'$eduline' => DI::l10n()->t('Underline'),
'$edquote' => DI::l10n()->t('Quote'),
'$edcode' => DI::l10n()->t('Code'),
'$edurl' => DI::l10n()->t('Link'),
'$edattach' => DI::l10n()->t('Link or Media'),
//jot nav tab (used in some themes)
'$message' => DI::l10n()->t('Message'),
'$browser' => DI::l10n()->t('Browser'),
'$shortpermset' => DI::l10n()->t('Permissions'),
'$compose_link_title' => DI::l10n()->t('Open Compose page'),
]);
return $o;
}
function undo_post_tagging($s) {
$matches = null;
$cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism', $s, $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
if (in_array($mtch[1], ['!', '@'])) {
$contact = Contact::getByURL($mtch[2], false, ['addr']);
$mtch[3] = empty($contact['addr']) ? $mtch[2] : $contact['addr'];
}
$s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
}
}
return $s;
}

View File

@ -284,8 +284,7 @@ function photos_post(App $a)
DI::baseUrl()->redirect('photos/' . DI::args()->getArgv()[1] . '/image/' . DI::args()->getArgv()[3]);
}
DI::baseUrl()->redirect('photos/' . DI::args()->getArgv()[1]);
return; // NOTREACHED
DI::baseUrl()->redirect('profile/' . DI::args()->getArgv()[1] . '/photos');
}
}
@ -778,7 +777,6 @@ function photos_post(App $a)
function photos_content(App $a)
{
// URLs:
// photos/name
// photos/name/upload
// photos/name/upload/xxxxx (xxxxx is album name)
// photos/name/album/xxxxx
@ -905,7 +903,7 @@ function photos_content(App $a)
$uploader = '';
$ret = ['post_url' => 'photos/' . $user['nickname'],
$ret = ['post_url' => 'profile/' . $user['nickname'] . '/photos',
'addon_text' => $uploader,
'default_upload' => true];
@ -1522,68 +1520,4 @@ function photos_content(App $a)
return $o;
}
// Default - show recent photos with upload link (if applicable)
//$o = '';
$total = 0;
$r = DBA::toArray(DBA::p("SELECT `resource-id`, max(`scale`) AS `scale` FROM `photo` WHERE `uid` = ? AND `photo-type` = ?
$sql_extra GROUP BY `resource-id`",
$user['uid'],
Photo::DEFAULT,
));
if (DBA::isResult($r)) {
$total = count($r);
}
$pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 20);
$r = DBA::toArray(DBA::p("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`,
ANY_VALUE(`type`) AS `type`, ANY_VALUE(`album`) AS `album`, max(`scale`) AS `scale`,
ANY_VALUE(`created`) AS `created` FROM `photo`
WHERE `uid` = ? AND `photo-type` = ?
$sql_extra GROUP BY `resource-id` ORDER BY `created` DESC LIMIT ? , ?",
$user['uid'],
Photo::DEFAULT,
$pager->getStart(),
$pager->getItemsPerPage()
));
$photos = [];
if (DBA::isResult($r)) {
// "Twist" is only used for the duepunto theme with style "slackr"
$twist = false;
foreach ($r as $rr) {
$twist = !$twist;
$ext = $phototypes[$rr['type']];
$alt_e = $rr['filename'];
$name_e = $rr['album'];
$photos[] = [
'id' => $rr['id'],
'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2,4),
'link' => 'photos/' . $user['nickname'] . '/image/' . $rr['resource-id'],
'title' => DI::l10n()->t('View Photo'),
'src' => 'photo/' . $rr['resource-id'] . '-' . ((($rr['scale']) == 6) ? 4 : $rr['scale']) . '.' . $ext,
'alt' => $alt_e,
'album' => [
'link' => 'photos/' . $user['nickname'] . '/album/' . bin2hex($rr['album']),
'name' => $name_e,
'alt' => DI::l10n()->t('View Album'),
],
];
}
}
$tpl = Renderer::getMarkupTemplate('photos_recent.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$title' => DI::l10n()->t('Recent Photos'),
'$can_post' => $can_post,
'$upload' => [DI::l10n()->t('Upload New Photos'), 'photos/' . $user['nickname'] . '/upload'],
'$photos' => $photos,
'$paginate' => $pager->renderFull($total),
]);
return $o;
}

View File

@ -51,8 +51,6 @@ server {
listen 443 ssl;
server_name friendica.example.net;
ssl on;
#Traditional SSL
ssl_certificate /etc/nginx/ssl/friendica.example.net.chain.pem;
ssl_certificate_key /etc/nginx/ssl/example.net.key;

View File

@ -115,76 +115,40 @@ class Page implements ArrayAccess
}
}
// ArrayAccess interface
/**
* Whether a offset exists
*
* @link https://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
*
* @return boolean true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
* @since 5.0.0
* @inheritDoc
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset): bool
{
return isset($this->page[$offset]);
}
/**
* Offset to retrieve
*
* @link https://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
*
* @return mixed Can return all value types.
* @since 5.0.0
* @inheritDoc
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->page[$offset] ?? null;
}
/**
* Offset to set
*
* @link https://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
*
* @return void
* @since 5.0.0
* @inheritDoc
*/
public function offsetSet($offset, $value)
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value): void
{
$this->page[$offset] = $value;
}
/**
* Offset to unset
*
* @link https://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset <p>
* The offset to unset.
* </p>
*
* @return void
* @since 5.0.0
* @inheritDoc
*/
public function offsetUnset($offset)
#[\ReturnTypeWillChange]
public function offsetUnset($offset): void
{
if (isset($this->page[$offset])) {
unset($this->page[$offset]);
@ -310,7 +274,7 @@ class Page implements ArrayAccess
}
return $pageURL;
}
/**
* Initializes Page->page['footer'].
*

View File

@ -526,7 +526,7 @@ class Conversation
$live_update_div = '<div id="live-search"></div>' . "\r\n";
}
$page_dropping = $this->session->getLocalUserId() && $this->session->getLocalUserId() == $uid;
$page_dropping = $this->session->getLocalUserId() && $this->session->getLocalUserId() == $uid && $mode != 'search';
if (!$update) {
$_SESSION['return_path'] = $this->args->getQueryString();

View File

@ -361,7 +361,7 @@ class Item
if ($sparkle) {
$status_link = $profile_link . '/status';
$photos_link = str_replace('/profile/', '/photos/', $profile_link);
$photos_link = $profile_link . '/photos';
$profile_link = $profile_link . '/profile';
}
@ -729,7 +729,7 @@ class Item
'message_id' => $shared['uri'],
'comment' => $item['body'],
'shared' => $shared['body'],
];
];
}
}
@ -737,7 +737,7 @@ class Item
}
/**
* Add a link to a shared post at the end of the post
* Add a link to a shared post at the end of the post
*
* @param string $body
* @param integer $quote_uri_id

View File

@ -192,7 +192,7 @@ class Nav
// user menu
$nav['usermenu'][] = ['profile/' . $a->getLoggedInUserNickname(), DI::l10n()->t('Status'), '', DI::l10n()->t('Your posts and conversations')];
$nav['usermenu'][] = ['profile/' . $a->getLoggedInUserNickname() . '/profile', DI::l10n()->t('Profile'), '', DI::l10n()->t('Your profile page')];
$nav['usermenu'][] = ['photos/' . $a->getLoggedInUserNickname(), DI::l10n()->t('Photos'), '', DI::l10n()->t('Your photos')];
$nav['usermenu'][] = ['profile/' . $a->getLoggedInUserNickname() . '/photos', DI::l10n()->t('Photos'), '', DI::l10n()->t('Your photos')];
$nav['usermenu'][] = ['profile/' . $a->getLoggedInUserNickname() . '/media', DI::l10n()->t('Media'), '', DI::l10n()->t('Your postings with media')];
$nav['usermenu'][] = ['calendar/', DI::l10n()->t('Calendar'), '', DI::l10n()->t('Your calendar')];
$nav['usermenu'][] = ['notes/', DI::l10n()->t('Personal notes'), '', DI::l10n()->t('Your personal notes')];
@ -244,12 +244,8 @@ class Nav
}
$gdirpath = 'directory';
if (strlen(DI::config()->get('system', 'singleuser'))) {
$gdir = DI::config()->get('system', 'directory');
if (strlen($gdir)) {
$gdirpath = Profile::zrl($gdir, true);
}
if (DI::config()->get('system', 'singleuser') && DI::config()->get('system', 'directory')) {
$gdirpath = Profile::zrl(DI::config()->get('system', 'directory'), true);
}
if ((DI::userSession()->getLocalUserId() || DI::config()->get('system', 'community_page_style') != Community::DISABLED_VISITOR) &&

View File

@ -43,12 +43,12 @@ use Psr\Log\NullLogger;
*/
class Database
{
const PDO = 'pdo';
const PDO = 'pdo';
const MYSQLI = 'mysqli';
const INSERT_DEFAULT = 0;
const INSERT_UPDATE = 1;
const INSERT_IGNORE = 2;
const INSERT_UPDATE = 1;
const INSERT_IGNORE = 2;
protected $connected = false;
@ -64,18 +64,18 @@ class Database
* @var LoggerInterface
*/
protected $logger;
protected $server_info = '';
protected $server_info = '';
/** @var PDO|mysqli */
protected $connection;
protected $driver = '';
protected $pdo_emulate_prepares = false;
private $error = '';
private $errorno = 0;
private $affected_rows = 0;
private $error = '';
private $errorno = 0;
private $affected_rows = 0;
protected $in_transaction = false;
protected $in_retrial = false;
protected $testmode = false;
private $relation = [];
protected $in_retrial = false;
protected $testmode = false;
private $relation = [];
/** @var DbaDefinition */
protected $dbaDefinition;
/** @var ViewDefinition */
@ -112,23 +112,22 @@ class Database
$port = 0;
$serveraddr = trim($this->configCache->get('database', 'hostname'));
$serverdata = explode(':', $serveraddr);
$server = $serverdata[0];
$host = trim($serverdata[0]);
if (count($serverdata) > 1) {
$port = trim($serverdata[1]);
}
if (!empty(trim($this->configCache->get('database', 'port')))) {
$port = trim($this->configCache->get('database', 'port'));
if (trim($this->configCache->get('database', 'port') ?? 0)) {
$port = trim($this->configCache->get('database', 'port') ?? 0);
}
$server = trim($server);
$user = trim($this->configCache->get('database', 'username'));
$pass = trim($this->configCache->get('database', 'password'));
$db = trim($this->configCache->get('database', 'database'));
$charset = trim($this->configCache->get('database', 'charset'));
$socket = trim($this->configCache->get('database', 'socket'));
$user = trim($this->configCache->get('database', 'username'));
$pass = trim($this->configCache->get('database', 'password'));
$database = trim($this->configCache->get('database', 'database'));
$charset = trim($this->configCache->get('database', 'charset'));
$socket = trim($this->configCache->get('database', 'socket'));
if (!(strlen($server) && strlen($user))) {
if (!$host && !$socket || !$user) {
return false;
}
@ -138,19 +137,20 @@ class Database
if (!$this->configCache->get('database', 'disable_pdo') && class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
$this->driver = self::PDO;
$connect = "mysql:host=" . $server . ";dbname=" . $db;
if ($port > 0) {
$connect .= ";port=" . $port;
if ($socket) {
$connect = 'mysql:unix_socket=' . $socket;
} else {
$connect = 'mysql:host=' . $host;
if ($port > 0) {
$connect .= ';port=' . $port;
}
}
if ($charset) {
$connect .= ";charset=" . $charset;
$connect .= ';charset=' . $charset;
}
if ($socket) {
$connect .= ";$unix_socket=" . $socket;
}
$connect .= ';dbname=' . $database;
try {
$this->connection = @new PDO($connect, $user, $pass, [PDO::ATTR_PERSISTENT => $persistent]);
@ -165,10 +165,12 @@ class Database
if (!$this->connected && class_exists('\mysqli')) {
$this->driver = self::MYSQLI;
if ($port > 0) {
$this->connection = @new mysqli($server, $user, $pass, $db, $port);
if ($socket) {
$this->connection = @new mysqli(null, $user, $pass, $database, null, $socket);
} elseif ($port > 0) {
$this->connection = @new mysqli($host, $user, $pass, $database, $port);
} else {
$this->connection = @new mysqli($server, $user, $pass, $db);
$this->connection = @new mysqli($host, $user, $pass, $database);
}
if (!mysqli_connect_errno()) {
@ -177,11 +179,6 @@ class Database
if ($charset) {
$this->connection->set_charset($charset);
}
if ($socket) {
$this->connection->set_socket($socket);
}
}
}
@ -198,6 +195,7 @@ class Database
{
$this->testmode = $test;
}
/**
* Sets the logger for DBA
*
@ -222,6 +220,7 @@ class Database
{
$this->profiler = $profiler;
}
/**
* Disconnects the current database connection
*/
@ -338,12 +337,12 @@ class Database
}
$watchlist = explode(',', $this->configCache->get('system', 'db_log_index_watch'));
$denylist = explode(',', $this->configCache->get('system', 'db_log_index_denylist'));
$denylist = explode(',', $this->configCache->get('system', 'db_log_index_denylist'));
while ($row = $this->fetch($r)) {
if ((intval($this->configCache->get('system', 'db_loglimit_index')) > 0)) {
$log = (in_array($row['key'], $watchlist) &&
($row['rows'] >= intval($this->configCache->get('system', 'db_loglimit_index'))));
($row['rows'] >= intval($this->configCache->get('system', 'db_loglimit_index'))));
} else {
$log = false;
}
@ -358,11 +357,15 @@ class Database
if ($log) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
@file_put_contents($this->configCache->get('system', 'db_log_index'), DateTimeFormat::utcNow() . "\t" .
$row['key'] . "\t" . $row['rows'] . "\t" . $row['Extra'] . "\t" .
basename($backtrace[1]["file"]) . "\t" .
$backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" .
substr($query, 0, 4000) . "\n", FILE_APPEND);
@file_put_contents(
$this->configCache->get('system', 'db_log_index'),
DateTimeFormat::utcNow() . "\t" .
$row['key'] . "\t" . $row['rows'] . "\t" . $row['Extra'] . "\t" .
basename($backtrace[1]["file"]) . "\t" .
$backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" .
substr($query, 0, 4000) . "\n",
FILE_APPEND
);
}
}
}
@ -449,7 +452,7 @@ class Database
{
$server_info = $this->serverInfo();
if (version_compare($server_info, '5.7.5', '<') ||
(stripos($server_info, 'MariaDB') !== false)) {
(stripos($server_info, 'MariaDB') !== false)) {
$sql = str_ireplace('ANY_VALUE(', 'MIN(', $sql);
}
return $sql;
@ -647,7 +650,7 @@ class Database
} elseif (is_string($args[$param])) {
$param_types .= 's';
} elseif (is_object($args[$param]) && method_exists($args[$param], '__toString')) {
$param_types .= 's';
$param_types .= 's';
$args[$param] = (string)$args[$param];
} else {
$param_types .= 'b';
@ -743,10 +746,14 @@ class Database
$duration = round($duration, 3);
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
@file_put_contents($this->configCache->get('system', 'db_log'), DateTimeFormat::utcNow() . "\t" . $duration . "\t" .
basename($backtrace[1]["file"]) . "\t" .
$backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" .
substr($this->replaceParameters($sql, $args), 0, 4000) . "\n", FILE_APPEND);
@file_put_contents(
$this->configCache->get('system', 'db_log'),
DateTimeFormat::utcNow() . "\t" . $duration . "\t" .
basename($backtrace[1]["file"]) . "\t" .
$backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" .
substr($this->replaceParameters($sql, $args), 0, 4000) . "\n",
FILE_APPEND
);
}
}
return $retval;
@ -1365,7 +1372,7 @@ class Database
. $condition_string;
// Combines the updated fields parameter values with the condition parameter values
$params = array_merge(array_values($fields), $condition);
$params = array_merge(array_values($fields), $condition);
return $this->e($sql, $params);
}
@ -1415,8 +1422,8 @@ class Database
/**
* Escape fields, adding special treatment for "group by" handling
*
* @param array $fields
* @param array $options
* @param array $fields
* @param array $options
* @return array Escaped fields
*/
private function escapeFields(array $fields, array $options): array
@ -1438,8 +1445,7 @@ class Database
}
}
array_walk($fields, function(&$value, $key) use ($options)
{
array_walk($fields, function (&$value, $key) use ($options) {
$field = $value;
$value = DBA::quoteIdentifier($field);
@ -1487,7 +1493,7 @@ class Database
}
if (count($fields) > 0) {
$fields = $this->escapeFields($fields, $params);
$fields = $this->escapeFields($fields, $params);
$select_string = implode(', ', $fields);
} else {
$select_string = '*';
@ -1827,16 +1833,16 @@ class Database
/**
* Replaces a string in the provided fields of the provided table
*
* @param string $table Table name
* @param array $fields List of field names in the provided table
* @param string $search String to search for
* @param string $table Table name
* @param array $fields List of field names in the provided table
* @param string $search String to search for
* @param string $replace String to replace with
* @return void
* @throws \Exception
*/
public function replaceInTableFields(string $table, array $fields, string $search, string $replace)
{
$search = $this->escape($search);
$search = $this->escape($search);
$replace = $this->escape($replace);
$upd = [];

View File

@ -383,7 +383,7 @@ class APContact
// kroeg:blocks, updated
// When the photo is too large, try to shorten it by removing parts
if (strlen($apcontact['photo']) > 255) {
if (strlen($apcontact['photo'] ?? '') > 255) {
$parts = parse_url($apcontact['photo']);
unset($parts['fragment']);
$apcontact['photo'] = (string)Uri::fromParts($parts);
@ -574,7 +574,7 @@ class APContact
*
* @param array $apcontact
*
* @return bool
* @return bool
*/
public static function isRelay(array $apcontact): bool
{

View File

@ -1158,7 +1158,7 @@ class Contact
if ($sparkle) {
$status_link = $profile_link . '/status';
$photos_link = str_replace('/profile/', '/photos/', $profile_link);
$photos_link = $profile_link . '/photos';
$profile_link = $profile_link . '/profile';
}

View File

@ -210,7 +210,7 @@ class Item
$fields['raw-body'] = BBCode::removeSharedData($fields['raw-body']);
}
}
Post\Media::insertFromAttachmentData($item['uri-id'], $fields['body']);
$content_fields = ['raw-body' => trim($fields['raw-body'] ?? $fields['body'])];
@ -337,7 +337,7 @@ class Item
* generate a resource-id and therefore aren't intimately linked to the item.
*/
/// @TODO: this should first check if photo is used elsewhere
if (strlen($item['resource-id'])) {
if ($item['resource-id']) {
Photo::delete(['resource-id' => $item['resource-id'], 'uid' => $item['uid']]);
}
@ -2714,7 +2714,7 @@ class Item
}
$condition = ['vid' => $vids, 'deleted' => false, 'gravity' => self::GRAVITY_ACTIVITY,
'author-id' => $author_id, 'uid' => $item['uid'], 'thr-parent-id' => $uri_id];
'author-id' => $author_id, 'uid' => $uid, 'thr-parent-id' => $uri_id];
$like_item = Post::selectFirst(['id', 'guid', 'verb'], $condition);
if (DBA::isResult($like_item)) {

View File

@ -160,7 +160,7 @@ class ParsedLogIterator implements \Iterator
* @see Iterator::next()
* @return void
*/
public function next()
public function next(): void
{
$parsed = $this->read();
@ -177,7 +177,7 @@ class ParsedLogIterator implements \Iterator
* @see Iterator::rewind()
* @return void
*/
public function rewind()
public function rewind(): void
{
$this->value = null;
$this->reader->rewind();
@ -200,9 +200,9 @@ class ParsedLogIterator implements \Iterator
* Return current iterator value
*
* @see Iterator::current()
* @return ?ParsedLogLing
* @return ?ParsedLogLine
*/
public function current()
public function current(): ?ParsedLogLine
{
return $this->value;
}

View File

@ -80,19 +80,17 @@ class Nodeinfo
$config = DI::config();
$usage = new stdClass();
$usage->users = [];
$usage->users = new \stdClass;
if (!empty($config->get('system', 'nodeinfo'))) {
$usage->users = [
'total' => intval($config->get('nodeinfo', 'total_users')),
'activeHalfyear' => intval($config->get('nodeinfo', 'active_users_halfyear')),
'activeMonth' => intval($config->get('nodeinfo', 'active_users_monthly'))
];
$usage->users->total = intval($config->get('nodeinfo', 'total_users'));
$usage->users->activeHalfyear = intval($config->get('nodeinfo', 'active_users_halfyear'));
$usage->users->activeMonth = intval($config->get('nodeinfo', 'active_users_monthly'));
$usage->localPosts = intval($config->get('nodeinfo', 'local_posts'));
$usage->localComments = intval($config->get('nodeinfo', 'local_comments'));
if ($version2) {
$usage->users['activeWeek'] = intval($config->get('nodeinfo', 'active_users_weekly'));
$usage->users->activeWeek = intval($config->get('nodeinfo', 'active_users_weekly'));
}
}

View File

@ -1130,8 +1130,8 @@ class Photo
$picture['height'] = $photo['height'];
$picture['type'] = $photo['type'];
$picture['albumpage'] = DI::baseUrl() . '/photos/' . $user['nickname'] . '/image/' . $resource_id;
$picture['picture'] = DI::baseUrl() . '/photo/{$resource_id}-0.' . $image->getExt();
$picture['preview'] = DI::baseUrl() . '/photo/{$resource_id}-{$smallest}.' . $image->getExt();
$picture['picture'] = DI::baseUrl() . '/photo/' . $resource_id . '-0.' . $image->getExt();
$picture['preview'] = DI::baseUrl() . '/photo/' . $resource_id . '-' . $smallest . '.' . $image->getExt();
Logger::info('upload done', ['picture' => $picture]);
return $picture;
@ -1272,4 +1272,3 @@ class Photo
return $resource_id;
}
}

View File

@ -279,7 +279,7 @@ class Media
if (!empty($contact['gsid'])) {
$gserver = DBA::selectFirst('gserver', ['url', 'site_name'], ['id' => $contact['gsid']]);
}
$media['type'] = self::ACTIVITY;
$media['media-uri-id'] = $item['uri-id'];
$media['height'] = null;
@ -687,7 +687,7 @@ class Media
$previews[] = $medium['preview'];
}
$type = explode('/', current(explode(';', $medium['mimetype'])));
$type = explode('/', explode(';', $medium['mimetype'])[0]);
if (count($type) < 2) {
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $medium]);
$filetype = 'unkn';

View File

@ -23,6 +23,7 @@ namespace Friendica\Model;
use Friendica\Content\Pager;
use Friendica\Database\DBA;
use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
@ -113,21 +114,27 @@ class Register
}
/**
* Creates a register record for approval and returns the success of the database insert
* Creates a register record for approval
* Checks for the existence of the provided user id
*
* @param integer $uid The ID of the user needing approval
* @param string $language The registration language
* @param string $note An additional message from the user
* @return boolean
* @throws \Exception
* @param integer $uid The ID of the user needing approval
* @param string $language The registration language
* @param string $note An additional message from the user
* @return void
* @throws \OutOfBoundsException
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException
*/
public static function createForApproval(int $uid, string $language, string $note = ''): bool
public static function createForApproval(int $uid, string $language, string $note = ''): void
{
$hash = Strings::getRandomHex();
if (!$uid) {
throw new \OutOfBoundsException("User ID can't be empty");
}
if (!User::exists($uid)) {
return false;
throw new HTTPException\NotFoundException("User ID doesn't exist");
}
$fields = [
@ -139,7 +146,9 @@ class Register
'note' => $note
];
return DBA::insert('register', $fields);
if (!DBA::insert('register', $fields)) {
throw new HTTPException\InternalServerErrorException('Unable to insert a `register` record');
}
}
/**

View File

@ -65,7 +65,7 @@ class Seen extends BaseApi
DI::notify()->save($Notify);
if ($Notify->otype === Notification\ObjectType::ITEM) {
$item = Post::selectFirstForUser($uid, [], ['id' => $Notify->iid, 'uid' => $uid]);
$item = Post::selectFirstForUser($uid, [], ['id' => $Notify->itemId, 'uid' => $uid]);
if (DBA::isResult($item)) {
// we found the item, return it to the user
$ret = [DI::twitterStatus()->createFromUriId($item['uri-id'], $item['uid'], $include_entities)->toArray()];

View File

@ -62,7 +62,7 @@ class BaseProfile extends BaseModule
],
[
'label' => DI::l10n()->t('Photos'),
'url' => DI::baseUrl() . '/photos/' . $nickname,
'url' => $baseProfileUrl . '/photos',
'sel' => $current == 'photos' ? 'active' : '',
'title' => DI::l10n()->t('Photo Albums'),
'id' => 'photo-tab',
@ -78,7 +78,7 @@ class BaseProfile extends BaseModule
],
];
// the calendar link for the full featured events calendar
// the calendar link for the full-featured events calendar
if ($is_owner && $a->getThemeInfoValue('events_in_profile')) {
$tabs[] = [
'label' => DI::l10n()->t('Calendar'),

View File

@ -88,7 +88,9 @@ class Follow extends BaseModule
}
$uid = $this->session->getLocalUserId();
$url = Probe::cleanURI(trim($request['url'] ?? ''));
// uri is used by the /authorize_interaction Mastodon route
$url = Probe::cleanURI(trim($request['uri'] ?? $request['url'] ?? ''));
// Issue 6874: Allow remote following from Peertube
if (strpos($url, 'acct:') === 0) {

View File

@ -44,23 +44,15 @@ class Hovercard extends BaseModule
throw new HTTPException\ForbiddenException();
}
// If a contact is connected the url is internally changed to 'contact/redir/CID'. We need the pure url to search for
// the contact. So we strip out the contact id from the internal url and look in the contact table for
// the real url (nurl)
if (strpos($contact_url, 'contact/redir/') === 0) {
$cid = intval(substr($contact_url, 6));
} elseif (strpos($contact_url, 'contact/') === 0) {
$cid = intval(substr($contact_url, 8));
}
if (!empty($cid)) {
$remote_contact = Contact::selectFirst(['nurl'], ['id' => $cid]);
/* Possible formats for relative URLs that need to be converted to the absolute contact URL:
* - contact/redir/123456
* - contact/123456/conversations
*/
if (strpos($contact_url, 'contact/') === 0 && preg_match('/(\d+)/', $contact_url, $matches)) {
$remote_contact = Contact::selectFirst(['nurl'], ['id' => $matches[1]]);
$contact_url = $remote_contact['nurl'] ?? '';
}
$contact = [];
// if it's the url containing https it should be converted to http
if (!$contact_url) {
throw new HTTPException\BadRequestException();
}

View File

@ -46,7 +46,7 @@ class Home extends BaseModule
DI::baseUrl()->redirect('network');
}
if (strlen($config->get('system', 'singleuser'))) {
if ($config->get('system', 'singleuser')) {
DI::baseUrl()->redirect('/profile/' . $config->get('system', 'singleuser'));
}

View File

@ -136,7 +136,7 @@ class Display extends BaseModule
}
if ($item['gravity'] != Item::GRAVITY_PARENT) {
$parent = Post::selectFirstForUser($itemUid, $fields, [
$parent = Post::selectFirst($fields, [
'uid' => [0, $itemUid],
'uri-id' => $item['parent-uri-id']
], ['order' => ['uid' => true]]);
@ -249,7 +249,15 @@ class Display extends BaseModule
$item = Post::selectFirstForUser($pageUid, $fields, $condition);
if (empty($item)) {
throw new HTTPException\NotFoundException($this->t('The requested item doesn\'t exist or has been deleted.'));
$this->page['aside'] = '';
throw new HTTPException\NotFoundException($this->t('Unfortunately, the requested conversation isn\'t available to you.</p>
<p>Possible reasons include:</p>
<ul>
<li>The top-level post isn\'t visible.</li>
<li>The top-level post was deleted.</li>
<li>The node has blocked the top-level author or the author of the shared post.</li>
<li>You have ignored or blocked the top-level author or the author of the shared post.</li>
</ul><p>'));
}
$item['uri-id'] = $item['parent-uri-id'];

227
src/Module/Post/Edit.php Normal file
View File

@ -0,0 +1,227 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\Post;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Content\Feature;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Model\Contact;
use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Module\Response;
use Friendica\Navigation\SystemMessages;
use Friendica\Network\HTTPException;
use Friendica\Util\Crypto;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Controller to edit a post
*/
class Edit extends BaseModule
{
/** @var IHandleUserSessions */
protected $session;
/** @var SystemMessages */
protected $sysMessages;
/** @var App\Page */
protected $page;
/** @var App\Mode */
protected $mode;
/** @var App */
protected $app;
/** @var bool */
protected $isModal = false;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, SystemMessages $sysMessages, App\Page $page, App\Mode $mode, App $app, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->session = $session;
$this->sysMessages = $sysMessages;
$this->page = $page;
$this->mode = $mode;
$this->app = $app;
}
protected function content(array $request = []): string
{
$this->isModal = $request['mode'] ?? '' === 'none';
if (!$this->session->getLocalUserId()) {
$this->errorExit($this->t('Permission denied.'), HTTPException\UnauthorizedException::class);
}
$postId = $this->parameters['post_id'] ?? 0;
if (empty($postId)) {
$this->errorExit($this->t('Post not found.'), HTTPException\BadRequestException::class);
}
$fields = [
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'body', 'title', 'uri-id', 'wall', 'post-type', 'guid'
];
$item = Post::selectFirstForUser($this->session->getLocalUserId(), $fields, [
'id' => $postId,
'uid' => $this->session->getLocalUserId(),
]);
if (empty($item)) {
$this->errorExit($this->t('Post not found.'), HTTPException\BadRequestException::class);
}
$user = User::getById($this->session->getLocalUserId());
$output = Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [
'$title' => $this->t('Edit post'),
]);
$this->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('jot-header.tpl'), [
'$ispublic' => '&nbsp;',
'$geotag' => '',
'$nickname' => $this->app->getLoggedInUserNickname(),
'$is_mobile' => $this->mode->isMobile(),
]);
if (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) {
$lockstate = 'lock';
} else {
$lockstate = 'unlock';
}
$jotplugins = '';
Hook::callAll('jot_tool', $jotplugins);
$output .= Renderer::replaceMacros(Renderer::getMarkupTemplate('jot.tpl'), [
'$is_edit' => true,
'$return_path' => '/display/' . $item['guid'],
'$action' => 'item',
'$share' => $this->t('Save'),
'$loading' => $this->t('Loading...'),
'$upload' => $this->t('Upload photo'),
'$shortupload' => $this->t('upload photo'),
'$attach' => $this->t('Attach file'),
'$shortattach' => $this->t('attach file'),
'$weblink' => $this->t('Insert web link'),
'$shortweblink' => $this->t('web link'),
'$video' => $this->t('Insert video link'),
'$shortvideo' => $this->t('video link'),
'$audio' => $this->t('Insert audio link'),
'$shortaudio' => $this->t('audio link'),
'$setloc' => $this->t('Set your location'),
'$shortsetloc' => $this->t('set location'),
'$noloc' => $this->t('Clear browser location'),
'$shortnoloc' => $this->t('clear location'),
'$wait' => $this->t('Please wait'),
'$permset' => $this->t('Permission settings'),
'$wall' => $item['wall'],
'$posttype' => $item['post-type'],
'$content' => $this->undoPostTagging($item['body']),
'$post_id' => $postId,
'$defloc' => $user['default-location'],
'$visitor' => 'none',
'$pvisit' => 'none',
'$emailcc' => $this->t('CC: email addresses'),
'$public' => $this->t('Public post'),
'$title' => $item['title'],
'$placeholdertitle' => $this->t('Set title'),
'$category' => Post\Category::getCSVByURIId($item['uri-id'], $this->session->getLocalUserId(), Post\Category::CATEGORY),
'$placeholdercategory' => (Feature::isEnabled($this->session->getLocalUserId(), 'categories') ? $this->t("Categories \x28comma-separated list\x29") : ''),
'$emtitle' => $this->t('Example: bob@example.com, mary@example.com'),
'$lockstate' => $lockstate,
'$acl' => '',
'$bang' => ($lockstate === 'lock' ? '!' : ''),
'$profile_uid' => $this->session->getLocalUserId(),
'$preview' => $this->t('Preview'),
'$jotplugins' => $jotplugins,
'$cancel' => $this->t('Cancel'),
'$rand_num' => Crypto::randomDigits(12),
// Formatting button labels
'$edbold' => $this->t('Bold'),
'$editalic' => $this->t('Italic'),
'$eduline' => $this->t('Underline'),
'$edquote' => $this->t('Quote'),
'$edcode' => $this->t('Code'),
'$edurl' => $this->t('Link'),
'$edattach' => $this->t('Link or Media'),
//jot nav tab (used in some themes)
'$message' => $this->t('Message'),
'$browser' => $this->t('Browser'),
'$shortpermset' => $this->t('Permissions'),
'$compose_link_title' => $this->t('Open Compose page'),
]);
return $output;
}
/**
* Removes Tags from the item-body
*
* @param string $body The item body
*
* @return string the new item body without tagging
*/
protected function undoPostTagging(string $body)
{
$matches = null;
$content = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism', $body, $matches, PREG_SET_ORDER);
if ($content) {
foreach ($matches as $match) {
if (in_array($match[1], ['!', '@'])) {
$contact = Contact::getByURL($match[2], false, ['addr']);
$match[3] = empty($contact['addr']) ? $match[2] : $contact['addr'];
}
$body = str_replace($match[0], $match[1] . $match[3], $body);
}
}
return $body;
}
/**
* Exists the current Module because of an error
*
* @param string $message The error message
* @param string $exceptionClass In case it's a modal, throw an exception instead of an redirect
*
* @return void
*/
protected function errorExit(string $message, string $exceptionClass)
{
if ($this->isModal) {
throw new $exceptionClass($message);
} else {
$this->sysMessages->addNotice($message);
$this->baseUrl->redirect();
}
}
}

View File

@ -0,0 +1,196 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\Profile\Photos;
use Friendica\App;
use Friendica\Content\Pager;
use Friendica\Content\Widget;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Database\Database;
use Friendica\Model\Contact;
use Friendica\Model\Photo;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Module\Response;
use Friendica\Network\HTTPException;
use Friendica\Security\Security;
use Friendica\Util\Images;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
class Index extends \Friendica\Module\BaseProfile
{
/** @var IHandleUserSessions */
private $session;
/** @var App\Page */
private $page;
/** @var IManageConfigValues */
private $config;
/** @var App */
private $app;
/** @var Database */
private $database;
public function __construct(Database $database, App $app, IManageConfigValues $config, App\Page $page, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->session = $session;
$this->page = $page;
$this->config = $config;
$this->app = $app;
$this->database = $database;
}
protected function content(array $request = []): string
{
parent::content($request);
if ($this->config->get('system', 'block_public') && !$this->session->isAuthenticated()) {
throw new HttpException\ForbiddenException($this->t('Public access denied.'));
}
$owner = User::getOwnerDataByNick($this->parameters['nickname']);
if (!isset($owner['account_removed']) || $owner['account_removed']) {
throw new HTTPException\NotFoundException($this->t('User not found.'));
}
$owner_uid = $owner['uid'];
$is_owner = $this->session->getLocalUserId() && ($this->session->getLocalUserId() == $owner_uid);
$remote_contact = false;
if ($this->session->getRemoteContactID($owner_uid)) {
$contact_id = $this->session->getRemoteContactID($owner_uid);
$contact = Contact::getContactForUser($contact_id, $owner_uid, ['blocked', 'pending']);
$remote_contact = $contact && !$contact['blocked'] && !$contact['pending'];
}
if ($owner['hidewall'] && !$is_owner && !$remote_contact) {
throw new HttpException\ForbiddenException($this->t('Access to this item is restricted.'));
}
$this->session->set('photo_return', $this->args->getCommand());
$sql_extra = Security::getPermissionsSQLByUserId($owner_uid);
$photo = $this->database->toArray($this->database->p(
"SELECT COUNT(DISTINCT `resource-id`) AS `count`
FROM `photo`
WHERE `uid` = ?
AND `photo-type` = ?
$sql_extra",
$owner['uid'],
Photo::DEFAULT,
));
$total = $photo[0]['count'];
$pager = new Pager($this->l10n, $this->args->getQueryString(), 20);
$photos = $this->database->toArray($this->database->p(
"SELECT
`resource-id`,
ANY_VALUE(`id`) AS `id`,
ANY_VALUE(`filename`) AS `filename`,
ANY_VALUE(`type`) AS `type`,
ANY_VALUE(`album`) AS `album`,
max(`scale`) AS `scale`,
ANY_VALUE(`created`) AS `created`
FROM `photo`
WHERE `uid` = ?
AND `photo-type` = ?
$sql_extra
GROUP BY `resource-id`
ORDER BY `created` DESC
LIMIT ? , ?",
$owner['uid'],
Photo::DEFAULT,
$pager->getStart(),
$pager->getItemsPerPage()
));
$phototypes = Images::supportedTypes();
$photos = array_map(function ($photo) use ($owner, $phototypes) {
return [
'id' => $photo['id'],
'link' => 'photos/' . $owner['nickname'] . '/image/' . $photo['resource-id'],
'title' => $this->t('View Photo'),
'src' => 'photo/' . $photo['resource-id'] . '-' . ((($photo['scale']) == 6) ? 4 : $photo['scale']) . '.' . $phototypes[$photo['type']],
'alt' => $photo['filename'],
'album' => [
'link' => 'photos/' . $owner['nickname'] . '/album/' . bin2hex($photo['album']),
'name' => $photo['album'],
'alt' => $this->t('View Album'),
],
];
}, $photos);
$tpl = Renderer::getMarkupTemplate('photos_head.tpl');
$this->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$ispublic' => $this->t('everybody')
]);
if ($albums = Photo::getAlbums($owner['uid'])) {
$albums = array_map(function ($album) use ($owner) {
return [
'text' => $album['album'],
'total' => $album['total'],
'url' => 'photos/' . $owner['nickname'] . '/album/' . bin2hex($album['album']),
'urlencode' => urlencode($album['album']),
'bin2hex' => bin2hex($album['album'])
];
}, $albums);
$photo_albums_widget = Renderer::replaceMacros(Renderer::getMarkupTemplate('photo_albums.tpl'), [
'$nick' => $owner['nickname'],
'$title' => $this->t('Photo Albums'),
'$recent' => $this->t('Recent Photos'),
'$albums' => $albums,
'$upload' => [$this->t('Upload New Photos'), 'photos/' . $owner['nickname'] . '/upload'],
'$can_post' => $this->session->getLocalUserId() && $owner['uid'] == $this->session->getLocalUserId(),
]);
}
$this->page['aside'] .= Widget\VCard::getHTML($owner);
if (!empty($photo_albums_widget)) {
$this->page['aside'] .= $photo_albums_widget;
}
$o = self::getTabsHTML($this->app, 'photos', $is_owner, $owner['nickname'], Profile::getByUID($owner['uid'])['hide-friends'] ?? false);
$tpl = Renderer::getMarkupTemplate('photos_recent.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$title' => $this->t('Recent Photos'),
'$can_post' => $is_owner || $remote_contact && $owner['page-flags'] == User::PAGE_FLAGS_COMMUNITY,
'$upload' => [$this->t('Upload New Photos'), 'photos/' . $owner['nickname'] . '/upload'],
'$photos' => $photos,
'$paginate' => $pager->renderFull($total),
]);
return $o;
}
}

View File

@ -353,6 +353,7 @@ class Register extends BaseModule
}
} elseif (intval(DI::config()->get('config', 'register_policy')) === self::APPROVE) {
if (!User::getAdminEmailList()) {
$this->logger->critical('Registration policy is set to APPROVE but no admin email address has been set in config.admin_email');
DI::sysmsg()->addNotice(DI::l10n()->t('Your registration can not be processed.'));
DI::baseUrl()->redirect();
}
@ -362,10 +363,17 @@ class Register extends BaseModule
DI::sysmsg()->addNotice(DI::l10n()->t('You have to leave a request note for the admin.')
. DI::l10n()->t('Your registration can not be processed.'));
DI::baseUrl()->redirect('register/');
$this->baseUrl->redirect('register');
}
Model\Register::createForApproval($user['uid'], DI::config()->get('system', 'language'), $_POST['permonlybox']);
try {
Model\Register::createForApproval($user['uid'], DI::config()->get('system', 'language'), $_POST['permonlybox']);
} catch (\Throwable $e) {
$this->logger->error('Unable to create a `register` record.', ['user' => $user]);
DI::sysmsg()->addNotice(DI::l10n()->t('An internal error occured.')
. DI::l10n()->t('Your registration can not be processed.'));
$this->baseUrl->redirect('register');
}
// invite system
if ($using_invites && $invite_id) {

View File

@ -230,7 +230,7 @@ class Index extends BaseSettings
'$banner' => DI::l10n()->t('Edit Profile Details'),
'$submit' => DI::l10n()->t('Submit'),
'$profpic' => DI::l10n()->t('Change Profile Photo'),
'$profpiclink' => '/photos/' . $profile['nickname'],
'$profpiclink' => '/profile/' . $profile['nickname'] . '/photos',
'$viewprof' => DI::l10n()->t('View Profile'),
'$lbl_personal_section' => DI::l10n()->t('Personal'),

View File

@ -132,7 +132,7 @@ class Index extends BaseSettings
DI::l10n()->t('or'),
($newuser) ?
'<a href="' . DI::baseUrl() . '">' . DI::l10n()->t('skip this step') . '</a>'
: '<a href="' . DI::baseUrl() . '/photos/' . DI::app()->getLoggedInUserNickname() . '">'
: '<a href="' . DI::baseUrl() . '/profile/' . DI::app()->getLoggedInUserNickname() . '/photos">'
. DI::l10n()->t('select a photo from your photo albums') . '</a>'
),
]);

View File

@ -25,6 +25,7 @@ use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\DI;
use Friendica\Module\Response;
use Friendica\Security\TwoFactor\Model\AppSpecificPassword;
@ -45,9 +46,9 @@ class AppSpecific extends BaseSettings
/** @var IManagePersonalConfigValues */
protected $pConfig;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManagePersonalConfigValues $pConfig, array $server, array $parameters = [])
public function __construct(IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->pConfig = $pConfig;

View File

@ -25,6 +25,7 @@ use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\DI;
use Friendica\Module\Response;
use Friendica\Security\TwoFactor\Model\RecoveryCode;
@ -43,9 +44,9 @@ class Recovery extends BaseSettings
/** @var IManagePersonalConfigValues */
protected $pConfig;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManagePersonalConfigValues $pConfig, array $server, array $parameters = [])
public function __construct(IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->pConfig = $pConfig;
@ -97,7 +98,7 @@ class Recovery extends BaseSettings
$recoveryCodes = RecoveryCode::getListForUser(DI::userSession()->getLocalUserId());
$verified = $this->pConfig->get(DI::userSession()->getLocalUserId(), '2fa', 'verified');
return Renderer::replaceMacros(Renderer::getMarkupTemplate('settings/twofactor/recovery.tpl'), [
'$form_security_token' => self::getFormSecurityToken('settings_2fa_recovery'),
'$password_security_token' => self::getFormSecurityToken('settings_2fa_password'),

View File

@ -25,6 +25,7 @@ use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\DI;
use Friendica\Module\BaseSettings;
use Friendica\Module\Response;
@ -45,9 +46,9 @@ class Trusted extends BaseSettings
/** @var TwoFactor\Repository\TrustedBrowser */
protected $trustedBrowserRepo;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManagePersonalConfigValues $pConfig, TwoFactor\Repository\TrustedBrowser $trustedBrowserRepo, array $server, array $parameters = [])
public function __construct(IManagePersonalConfigValues $pConfig, TwoFactor\Repository\TrustedBrowser $trustedBrowserRepo, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->pConfig = $pConfig;
$this->trustedBrowserRepo = $trustedBrowserRepo;

View File

@ -29,6 +29,7 @@ use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\DI;
use Friendica\Module\BaseSettings;
use Friendica\Module\Response;
@ -47,9 +48,9 @@ class Verify extends BaseSettings
/** @var IManagePersonalConfigValues */
protected $pConfig;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManagePersonalConfigValues $pConfig, array $server, array $parameters = [])
public function __construct(IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->pConfig = $pConfig;

View File

@ -254,7 +254,7 @@ class HttpClient implements ICanSendHttpRequests
$urlResult = $this->resolver->resolveURL($url);
if ($urlResult->didErrorOccur()) {
throw new TransferException($urlResult->getErrorMessageString(), $urlResult->getHTTPStatusCode());
throw new TransferException($urlResult->getErrorMessageString(), $urlResult->getHTTPStatusCode() ?? 0);
}
return $urlResult->getURL();

View File

@ -44,6 +44,8 @@ class Attachment extends BaseDataTransferObject
protected $text_url;
/** @var string */
protected $description;
/** @var array */
protected $meta;
/**
* Creates an attachment
@ -60,6 +62,21 @@ class Attachment extends BaseDataTransferObject
$this->remote_url = $remote;
$this->text_url = $this->remote_url ?? $this->url;
$this->description = $attachment['description'];
if ($type === 'image') {
if ((int) $attachment['width'] > 0 && (int) $attachment['height'] > 0) {
$this->meta['original']['width'] = (int) $attachment['width'];
$this->meta['original']['height'] = (int) $attachment['height'];
$this->meta['original']['size'] = (int) $attachment['width'] . 'x' . (int) $attachment['height'];
$this->meta['original']['aspect'] = (float) ((int) $attachment['width'] / (int) $attachment['height']);
}
if ((int) $attachment['preview-width'] > 0 && (int) $attachment['preview-height'] > 0) {
$this->meta['small']['width'] = (int) $attachment['preview-width'];
$this->meta['small']['height'] = (int) $attachment['preview-height'];
$this->meta['small']['size'] = (int) $attachment['preview-width'] . 'x' . (int) $attachment['preview-height'];
$this->meta['small']['aspect'] = (float) ((int) $attachment['preview-width'] / (int) $attachment['preview-height']);
}
}
}
/**

View File

@ -80,7 +80,7 @@ interface IEmail extends JsonSerializable
*
* @return string
*/
function getMessage(bool $plain = false);
function getMessage(bool $plain = false): string;
/**
* Gets the additional mail header array

View File

@ -110,12 +110,12 @@ class Email implements IEmail
/**
* {@inheritDoc}
*/
public function getMessage(bool $plain = false)
public function getMessage(bool $plain = false): string
{
if ($plain) {
return $this->msgText;
} else {
return $this->msgHtml;
return $this->msgHtml ?? '';
}
}

View File

@ -220,7 +220,7 @@ class Post
if ($item['event-id'] != 0) {
$edpost = ['calendar/event/edit/' . $item['event-id'], DI::l10n()->t('Edit')];
} else {
$edpost = ['editpost/' . $item['id'], DI::l10n()->t('Edit')];
$edpost = [sprintf('post/%s/edit', $item['id']), DI::l10n()->t('Edit')];
}
}
$dropping = in_array($item['uid'], [0, DI::userSession()->getLocalUserId()]);

View File

@ -3201,7 +3201,7 @@ class Diaspora
*/
public static function getReshareDetails(array $item): array
{
$reshared = DI::contentItem()->getSharedPost($item, ['network', 'author-addr']);
$reshared = DI::contentItem()->getSharedPost($item, ['guid', 'network', 'author-addr']);
if (empty($reshared)) {
return [];
}
@ -3213,7 +3213,7 @@ class Diaspora
return [
'root_handle' => strtolower($reshared['post']['author-addr']),
'root_guid' => $reshared['guid']
'root_guid' => $reshared['post']['guid'],
];
}
@ -3638,7 +3638,7 @@ class Diaspora
Logger::info('Got relayable data ' . $type . ' for item ' . $item['guid'] . ' (' . $item['id'] . ')');
$msg = json_decode($item['signed_text'], true);
$msg = json_decode($item['signed_text'] ?? '', true);
$message = [];
if (is_array($msg)) {

View File

@ -81,7 +81,7 @@ final class FriendicaSmartyEngine extends TemplateEngine
// "middleware": inject variables into templates
$arr = [
'template' => basename($this->smarty->filename),
'template' => basename($this->smarty->filename ?? ''),
'vars' => $vars
];
Hook::callAll('template_vars', $arr);

View File

@ -336,7 +336,7 @@ class Notifier
foreach ($items as $item) {
$recipients[] = $item['contact-id'];
// pull out additional tagged people to notify (if public message)
if ($public_message && strlen($item['inform'])) {
if ($public_message && $item['inform']) {
$people = explode(',',$item['inform']);
foreach ($people as $person) {
if (substr($person,0,4) === 'cid:') {

View File

@ -38,12 +38,12 @@ return [
'port' => null,
// socket (String)
// Socket of the database server.
// Can be used instead of adding a socket location to the hostname
'socket' => '',
// Socket of the database server.
// Can be used instead of adding a socket location to the hostname
'socket' => '',
// user (String)
// Database user name. Please don't use "root".
// Database username. Please don't use "root".
'username' => '',
// pass (String)
@ -64,7 +64,7 @@ return [
'pdo_emulate_prepares' => true,
// disable_pdo (Boolean)
// PDO is used by default (if available). Otherwise MySQLi will be used.
// PDO is used by default (if available). Otherwise, MySQLi will be used.
'disable_pdo' => false,
// persistent (Boolean)
@ -145,7 +145,7 @@ return [
'block_local_dir' => false,
// blocked_tags (String)
// Comma separated list of hash tags that shouldn't be displayed in the trending tags
// Comma separated list of hashtags that shouldn't be displayed in the trending tags
'blocked_tags' => '',
// community_no_sharer (Boolean)
@ -153,7 +153,7 @@ return [
'community_no_sharer' => false,
// contact_update_limit (Integer)
// How much contacts should be checked at a time?
// How many contacts should be checked at a time?
'contact_update_limit' => 100,
// cron_interval (Integer)
@ -259,7 +259,7 @@ return [
'disable_implicit_mentions' => false,
// disable_url_validation (Boolean)
// Disables the DNS lookup of an URL.
// Disables the DNS lookup of a URL.
'disable_url_validation' => false,
// disable_password_exposed (Boolean)
@ -283,7 +283,7 @@ return [
'dlogip' => '',
// expire-notify-priority (integer)
// Priority for the expirary notification
// Priority for the expirary notification
'expire-notify-priority' => Friendica\Core\Worker::PRIORITY_LOW,
// fetch_by_worker (Boolean)
@ -300,11 +300,11 @@ return [
// groupedit_image_limit (Integer)
// Number of contacts at which the group editor should switch from display the profile pictures of the contacts to only display the names.
// This can alternatively be set on a per account basis in the pconfig table.
// This can alternatively be set on a per-account basis in the pconfig table.
'groupedit_image_limit' => 400,
// gserver_update_limit (Integer)
// How much servers should be checked at a time?
// How many servers should be checked at a time?
'gserver_update_limit' => 100,
// hsts (Boolean)
@ -330,7 +330,7 @@ return [
'ipv4_resolve' => false,
// invitation_only (Boolean)
// If set true registration is only possible after a current member of the node has send an invitation.
// If set true registration is only possible after a current member of the node has sent an invitation.
'invitation_only' => false,
// itemspage_network (Integer)
@ -343,7 +343,7 @@ return [
'itemspage_network_mobile' => 20,
// jpeg_quality (Integer)
//
//
// Lower numbers save space at cost of image detail
// where n is between 1 and 100, and with very poor results below about 50
'jpeg_quality' => 100,
@ -415,13 +415,13 @@ return [
// max_image_length (Integer)
// An alternate way of limiting picture upload sizes.
// Specify the maximum pixel length that pictures are allowed to be (for non-square pictures, it will apply to the longest side).
// Specify the maximum pixel length that pictures are allowed to be (for non-square pictures, it will apply to the longest side).
// Pictures longer than this length will be resized to be this length (on the longest side, the other side will be scaled appropriately).
// If you don't want to set a maximum length, set to -1.
'max_image_length' => -1,
// max_likers (Integer)
// Maximum number of "people who like (or don't like) this" that we will list by name
// Maximum number of "people who like (or don't like) this" that we will list by name
'max_likers' => 75,
// max_processes_backend (Integer)
@ -471,7 +471,7 @@ return [
'no_oembed' => false,
// no_redirect_list (Array)
// List of domains where HTTP redirects should be ignored.
// List of domains where HTTP redirects should be ignored.
'no_redirect_list' => [],
// no_smilies (Boolean)
@ -483,7 +483,7 @@ return [
'paranoia' => false,
// permit_crawling (Boolean)
// Restricts the search for not logged in users to one search per minute.
// Restricts the search for not logged-in users to one search per minute.
'permit_crawling' => false,
// pidfile (Path)
@ -491,7 +491,7 @@ return [
'pidfile' => '',
// png_quality (Integer)
// Sets the ImageMagick compression level for PNG images. Values ranges from 0 (uncompressed) to 9 (most compressed).
// Sets the ImageMagick compression level for PNG images. Values range from 0 (uncompressed) to 9 (most compressed).
'png_quality' => 8,
// profiler (Boolean)
@ -568,11 +568,11 @@ return [
'set_creation_date' => false,
// show_global_community_hint (Boolean)
// When the global community page is enabled, use this option to display a hint above the stream, that this is a collection of all public top-level postings that arrive on your node.
// When the global community page is enabled, use this option to display a hint above the stream, that this is a collection of all public top-level postings that arrive at your node.
'show_global_community_hint' => false,
// show_received (Boolean)
// Show the receive data along with the post creation date
// Show the received date along with the post creation date
'show_received' => true,
// show_received_seconds (Integer)
@ -609,13 +609,13 @@ return [
// username_min_length (Integer)
// The minimum character length a username can be.
// This length is check once the username has been trimmed and multiple spaces have been collapsed into one.
// This length is checked once the username has been trimmed and multiple spaces have been collapsed into one.
// Minimum for this config value is 1. Maximum is 64 as the resulting profile URL mustn't be longer than 255 chars.
'username_min_length' => 3,
// username_max_length (Integer)
// The maximum character length a username can be.
// This length is check once the username has been trimmed and multiple spaces have been collapsed into one.
// This length is checked once the username has been trimmed and multiple spaces have been collapsed into one.
// Minimum for this config value is 1. Maximum is 64 as the resulting profile URL mustn't be longer than 255 chars.
'username_max_length' => 48,
@ -738,8 +738,7 @@ return [
'config_dir' => 'view/smarty3',
// use_sub_dirs (Boolean)
// By default the template cache is stored in several sub directories.
//
// By default the template cache is stored in several subdirectories.
'use_sub_dirs' => true,
],
];

View File

@ -36,6 +36,7 @@ $profileRoutes = [
'/contacts/common' => [Module\Profile\Common::class, [R::GET]],
'/contacts[/{type}]' => [Module\Profile\Contacts::class, [R::GET]],
'/media' => [Module\Profile\Media::class, [R::GET]],
'/photos' => [Module\Profile\Photos\Index::class, [R::GET ]],
'/photos/upload' => [Module\Profile\Photos\Upload::class, [ R::POST]],
'/profile' => [Module\Profile\Profile::class, [R::GET]],
'/remote_follow' => [Module\Profile\RemoteFollow::class, [R::GET, R::POST]],
@ -342,6 +343,10 @@ return [
'/acctlink' => [Module\Acctlink::class, [R::GET]],
'/apps' => [Module\Apps::class, [R::GET]],
'/attach/{item:\d+}' => [Module\Attach::class, [R::GET]],
// Mastodon route used by Fedifind to follow people who set their Webfinger address in their Twitter bio
'/authorize_interaction' => [Module\Contact\Follow::class, [R::GET, R::POST]],
'/babel' => [Module\Debug\Babel::class, [R::GET, R::POST]],
'/debug/ap' => [Module\Debug\ActivityPubConversion::class, [R::GET, R::POST]],
@ -549,9 +554,14 @@ return [
'/{type}/{customsize:\d+}/{nickname_ext}' => [Module\Photo::class, [R::GET]],
],
// Kept for backwards-compatibility
// @TODO remove by version 2023.12
'/photos/{nickname}' => [Module\Profile\Photos\Index::class, [R::GET]],
'/ping' => [Module\Notifications\Ping::class, [R::GET]],
'/post' => [
'/{post_id}/edit' => [Module\Post\Edit::class, [R::GET ]],
'/{post_id}/share' => [Module\Post\Share::class, [R::GET ]],
'/{item_id}/tag/add' => [Module\Post\Tag\Add::class, [ R::POST]],
'/{item_id}/tag/remove[/{tag_name}]' => [Module\Post\Tag\Remove::class, [R::GET, R::POST]],

View File

@ -35,6 +35,11 @@ a.btn, a.btn:hover {
text-decoration: overline;
}
.icon {
width: 48px;
height: 48px;
}
/* List of social Networks */
img.connector, img.connector-disabled {
height: 40px;

View File

@ -590,7 +590,7 @@ function liveUpdate(src) {
var orgHeight = $("section").height();
var udargs = ((netargs.length) ? netargs : '');
var udargs = ((netargs.length) ? '/' + netargs : '');
var update_url = 'update_' + src + udargs + '&p=' + profile_uid + '&force=' + (force ? 1 : 0) + '&item=' + update_item;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
<h1>{{$l10n.header}}</h1>
{{include file="contact_template.tpl" no_contacts_checkbox=True}}
{{include file="contact/entry.tpl" no_contacts_checkbox=True}}
{{include file="confirm.tpl"}}
<div class="clear"></div>
<div class="clear"></div>

View File

@ -22,7 +22,7 @@
<div id="directory-search-end"></div>
{{foreach $contacts as $contact}}
{{include file="contact_template.tpl"}}
{{include file="contact/entry.tpl"}}
{{/foreach}}
<div class="directory-end"></div>

View File

@ -1,7 +1,7 @@
<div id="exception" class="generic-page-wrapper">
<img class="hare" src="images/friendica-404_svg_flexy-o-hare.png"/>
<h1>{{$title}}</h1>
<p>{{$message}}</p>
<p>{{$message nofilter}}</p>
{{if $thrown}}
<pre>{{$thrown}}
{{$stack_trace}}

View File

@ -2,12 +2,8 @@
<h1>{{$header}}</h1>
{{foreach $contacts as $contact}}
{{include file="contact_template.tpl"}}
{{include file="contact/entry.tpl"}}
{{/foreach}}
<div id="contact-edit-end"></div>
{{$paginate nofilter}}

View File

@ -2,14 +2,14 @@
<h3>{{$title}}</h3>
<ul role="menubar" class="sidebar-photos-albums-ul">
<li role="menuitem" class="sidebar-photos-albums-li">
<a href="{{$baseurl}}/photos/{{$nick}}" class="sidebar-photos-albums-element" title="{{$title}}">{{$recent}}</a>
<a href="profile/{{$nick}}/photos" class="sidebar-photos-albums-element" title="{{$title}}">{{$recent}}</a>
</li>
{{if $albums}}
{{foreach $albums as $al}}
{{if $al.text}}
<li role="menuitem" class="sidebar-photos-albums-li">
<a href="{{$baseurl}}/photos/{{$nick}}/album/{{$al.bin2hex}}" class="sidebar-photos-albums-element">
<a href="photos/{{$nick}}/album/{{$al.bin2hex}}" class="sidebar-photos-albums-element">
<span class="badge pull-right">{{$al.total}}</span>{{$al.text}}
</a>
</li>

View File

@ -3,7 +3,7 @@
<div id="photos-usage-message">{{$usage}}</div>
<form action="photos/{{$nickname}}" enctype="multipart/form-data" method="post" name="photos-upload-form" id="photos-upload-form">
<form action="profile/{{$nickname}}/photos" enctype="multipart/form-data" method="post" name="photos-upload-form" id="photos-upload-form">
<div id="photos-upload-new-wrapper">
<div id="photos-upload-newalbum-div">
<label id="photos-upload-newalbum-text" for="photos-upload-newalbum">{{$newalbum}}</label>
@ -47,4 +47,3 @@
<div class="photos-upload-end"></div>
</form>

View File

@ -10,7 +10,7 @@
{{if $contacts}}
<div id="viewcontact_wrapper-{{$id}}">
{{foreach $contacts as $contact}}
{{include file="contact_template.tpl"}}
{{include file="contact/entry.tpl"}}
{{/foreach}}
</div>
{{else}}

View File

@ -92,7 +92,7 @@
{{if !$additional}}
<h3>{{$importh}}</h3>
<div id ="import-profile">
<a href="uimport">{{$importt}}</a>
<a href="user/import">{{$importt}}</a>
</div>
{{/if}}
</form>

View File

@ -132,16 +132,16 @@ class Color {
$var_1 = 2 * $L - $var_2;
$r = round(255 * self::_huetorgb( $var_1, $var_2, $H + (1/3) ));
$g = round(255 * self::_huetorgb( $var_1, $var_2, $H ));
$b = round(255 * self::_huetorgb( $var_1, $var_2, $H - (1/3) ));
$r = 255 * self::_huetorgb( $var_1, $var_2, $H + (1/3) );
$g = 255 * self::_huetorgb( $var_1, $var_2, $H );
$b = 255 * self::_huetorgb( $var_1, $var_2, $H - (1/3) );
}
// Convert to hex
$r = dechex($r);
$g = dechex($g);
$b = dechex($b);
$r = dechex(round($r));
$g = dechex(round($g));
$b = dechex(round($b));
// Make sure we get 2 digits for decimals
$r = (strlen("".$r)===1) ? "0".$r:$r;

View File

@ -0,0 +1,30 @@
{{$live_update nofilter}}
{{foreach $threads as $thread}}
<div id="tread-wrapper-{{$thread.id}}" class="tread-wrapper panel toplevel_item">
{{foreach $thread.items as $item}}
{{if $item.comment_firstcollapsed}}
<div class="hide-comments-outer">
<span id="hide-comments-total-{{$thread.id}}" class="hide-comments-total">{{$thread.num_comments}}</span>
<span id="hide-comments-{{$thread.id}}" class="hide-comments fakelink" onclick="showHideComments({{$thread.id}});">{{$thread.hide_text}}</span>
</div>
<div id="collapsed-comments-{{$thread.id}}" class="collapsed-comments" style="display: none;">
{{/if}}
{{if $item.comment_lastcollapsed}}</div>{{/if}}
{{include file="{{$item.template}}"}}
{{/foreach}}
</div>
{{/foreach}}
{{if !$update}}
<div id="conversation-end"></div>
{{if $dropping}}
<div id="item-delete-selected" class="fakelink" onclick="deleteCheckedItems();">
<div id="item-delete-selected-icon" class="icon drophide" title="{{$dropping}}"
onmouseover="imgbright(this);" onmouseout="imgdull(this);"></div>
<div id="item-delete-selected-desc">{{$dropping}}</div>
</div>
<div id="item-delete-selected-end"></div>
{{/if}}
{{/if}}

View File

@ -2,7 +2,7 @@
<div class="pull-left">
<h3>{{$title}}</h3>
</div>
<div class="pull-right">
{{if $can_post}}
<div class="photos-upload-link">
@ -15,14 +15,14 @@
<ul role="menubar" class="sidebar-photos-albums-ul clear">
<li role="menuitem" class="sidebar-photos-albums-li">
<a href="{{$baseurl}}/photos/{{$nick}}" class="sidebar-photos-albums-element" title="{{$title}}">{{$recent}}</a>
<a href="profile/{{$nick}}/photos" class="sidebar-photos-albums-element" title="{{$title}}">{{$recent}}</a>
</li>
{{if $albums}}
{{foreach $albums as $al}}
{{if $al.text}}
<li role="menuitem" class="sidebar-photos-albums-li">
<a href="{{$baseurl}}/photos/{{$nick}}/album/{{$al.bin2hex}}" class="sidebar-photos-albums-element">
<a href="photos/{{$nick}}/album/{{$al.bin2hex}}" class="sidebar-photos-albums-element">
<span class="badge pull-right">{{$al.total}}</span>{{$al.text}}
</a>
</li>

View File

@ -4,7 +4,7 @@
<div id="photos-usage-message">{{$usage}}</div>
<form action="photos/{{$nickname}}" enctype="multipart/form-data" method="post" name="photos-upload-form" id="photos-upload-form">
<form action="profile/{{$nickname}}/photos" enctype="multipart/form-data" method="post" name="photos-upload-form" id="photos-upload-form">
<div id="photos-upload-div" class="form-group">
<label id="photos-upload-text" for="photos-upload-newalbum">{{$newalbum}}</label>
@ -40,7 +40,7 @@
<div class="photos-upload-wrapper">
<div id="photos-upload-perms" class="btn-group pull-right">
<button class="btn btn-default" data-toggle="modal" data-target="#aclModal" onclick="return false;">
<i id="jot-perms-icon" class="fa {{$lockstate}}"></i>
<i id="jot-perms-icon" class="fa {{$lockstate}}"></i>
</button>
{{$default_upload_submit nofilter}}

View File

@ -92,7 +92,7 @@
{{if !$additional}}
<h3>{{$importh}}</h3>
<div id ="import-profile">
<a href="uimport">{{$importt}}</a>
<a href="user/import">{{$importt}}</a>
</div>
{{/if}}
</form>

View File

@ -1,302 +1,299 @@
<!-- TODO => Unknow block -->
<div class="wall-item-decor" style="display:none;">
{{if $item.star}}<span class="icon s22 star {{$item.isstarred}}" id="starred-{{$item.id}}" title="{{$item.star.starred}}">{{$item.star.starred}}</span>{{/if}}
{{if $item.lock}}<span class="navicon lock fakelink" onclick="lockview(event, 'item', {{$item.id}});" title="{{$item.lock}}"></span><span class="fa fa-lock" aria-hidden="true"></span>{{/if}}
</div>
<!-- ./TODO => Unknow block -->
<div id="item-{{$item.guid}}" class="item-{{$item.id}} wall-item-container panel-body{{$item.indent}} {{$item.shiny}} {{$item.previewing}}">
<div class="media">
{{* Put additional actions in a top-right dropdown menu *}}
{{* The avatar picture and the photo-menu *}}
<div class="dropdown pull-left"><!-- Dropdown -->
<div class="hidden-sm hidden-xs contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}}">
<a href="{{$item.profile_url}}" class="userinfo click-card u-url" id="wall-item-photo-menu-{{$item.id}}">
<div class="contact-photo-image-wrapper">
<img src="{{$item.thumb}}" class="contact-photo media-object {{$item.sparkle}}" id="wall-item-photo-{{$item.id}}" alt="{{$item.name}}" />
</div>
</a>
</div>
<div class="hidden-lg hidden-md contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}}">
<a href="{{$item.profile_url}}" class="userinfo click-card u-url" id="wall-item-photo-menu-xs-{{$item.id}}">
<div class="contact-photo-image-wrapper">
<img src="{{$item.thumb}}" class="contact-photo-xs media-object {{$item.sparkle}}" id="wall-item-photo-xs-{{$item.id}}" alt="{{$item.name}}" />
</div>
</a>
</div>
</div><!-- ./Dropdown -->
<div class="panel item-{{$item.id}}" id="item-{{$item.guid}}">
<span class="commented" style="display: none;">{{$item.commented}}</span>
<span class="received" style="display: none;">{{$item.received}}</span>
<span class="created" style="display: none;">{{$item.created_date}}</span>
<span class="uriid" style="display: none;">{{$item.uriid}}</span>
<div class="wall-item-container panel-body{{$item.indent}} {{$item.shiny}} {{$item.previewing}}">
<div class="media">
{{* Put additional actions in a top-right dropdown menu *}}
{{* contact info header*}}
<div role="heading" class="contact-info hidden-sm hidden-xs media-body"><!-- <= For computer -->
<div class="preferences">
{{if $item.network_icon != ""}}
<span class="wall-item-network"><i class="fa fa-{{$item.network_icon}}" title="{{$item.network_name}}" aria-hidden="true"></i></span>
{{else}}
<span class="wall-item-network" title="{{$item.app}}">{{$item.network_name}}</span>
{{/if}}
{{if $item.plink}} {{*link to the original source of the item *}}
<a href="{{$item.plink.href}}" class="plink u-url" aria-label="{{$item.plink.title}}" title="{{$item.plink.title}}">
<i class="fa fa-external-link"></i>
{{if $item.network_icon != ""}}
<span class="wall-item-network"><i class="fa fa-{{$item.network_icon}}" title="{{$item.network_name}}" aria-hidden="true"></i></span>
{{else}}
<span class="wall-item-network" title="{{$item.app}}">{{$item.network_name}}</span>
{{/if}}
{{if $item.plink}} {{*link to the original source of the item *}}
<a href="{{$item.plink.href}}" class="plink u-url" aria-label="{{$item.plink.title}}" title="{{$item.plink.title}}">
<i class="fa fa-external-link"></i>
</a>
{{/if}}
</div>
<h4 class="media-heading">
<a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card">
<span class="wall-item-name {{$item.sparkle}}">{{$item.name}}</span>
</a>
{{if $item.owner_url}}
{{$item.via}}
<a href="{{$item.owner_url}}" target="redir" title="{{$item.olinktitle}}" class="wall-item-name-link userinfo hover-card">
<span class="wall-item-name {{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}">{{$item.owner_name}}</span>
</a>
{{/if}}
</div>
{{* The avatar picture and the photo-menu *}}
<div class="dropdown pull-left"><!-- Dropdown -->
<div class="hidden-sm hidden-xs contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}}">
<a href="{{$item.profile_url}}" class="userinfo click-card u-url" id="wall-item-photo-menu-{{$item.id}}">
<div class="contact-photo-image-wrapper">
<img src="{{$item.thumb}}" class="contact-photo media-object {{$item.sparkle}}" id="wall-item-photo-{{$item.id}}" alt="{{$item.name}}" />
</div>
</a>
</div>
<div class="hidden-lg hidden-md contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}}">
<a href="{{$item.profile_url}}" class="userinfo click-card u-url" id="wall-item-photo-menu-xs-{{$item.id}}">
<div class="contact-photo-image-wrapper">
<img src="{{$item.thumb}}" class="contact-photo-xs media-object {{$item.sparkle}}" id="wall-item-photo-xs-{{$item.id}}" alt="{{$item.name}}" />
</div>
</a>
</div>
</div><!-- ./Dropdown -->
{{* contact info header*}}
<div role="heading" class="contact-info hidden-sm hidden-xs media-body"><!-- <= For computer -->
<h4 class="media-heading">
<a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card">
<span class="wall-item-name {{$item.sparkle}}">{{$item.name}}</span>
</a>
{{if $item.owner_url}}
{{$item.via}}
<a href="{{$item.owner_url}}" target="redir" title="{{$item.olinktitle}}" class="wall-item-name-link userinfo hover-card">
<span class="wall-item-name {{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}">{{$item.owner_name}}</span>
</a>
{{/if}}
{{if $item.lock}}
<span class="navicon lock fakelink" onClick="lockview(event, 'item', {{$item.id}});" title="{{$item.lock}}">
&nbsp;<small><i class="fa fa-lock" aria-hidden="true"></i></small>
</span>
{{elseif $item.connector}}
<span class="fa fa-lock" title="{{$item.connector}}"></span>
{{/if}}
<div class="additional-info text-muted">
<div id="wall-item-ago-{{$item.id}}" class="wall-item-ago">
<small>
<a href="{{$item.plink.orig}}">
<time class="time" title="{{$item.localtime}}" data-toggle="tooltip" datetime="{{$item.utc}}">{{$item.ago}}</time>
</a>
{{if $item.pinned}}
&bull; <i class="fa fa-thumb-tack" aria-hidden="true" title="{{$item.pinned}}"></i>
<span class="sr-only">{{$item.pinned}}</span>
{{/if}}
</small>
</div>
{{if $item.location_html}}
<div id="wall-item-location-{{$item.id}}" class="wall-item-location">
<small><span class="location">({{$item.location_html nofilter}})</span></small>
</div>
{{/if}}
{{if $item.lock}}
<span class="navicon lock fakelink" onClick="lockview(event, 'item', {{$item.id}});" title="{{$item.lock}}">
&nbsp;<small><i class="fa fa-lock" aria-hidden="true"></i></small>
</span>
{{elseif $item.connector}}
<span class="fa fa-lock" title="{{$item.connector}}"></span>
{{/if}}
<div class="additional-info text-muted">
<div id="wall-item-ago-{{$item.id}}" class="wall-item-ago">
<small>
<a href="{{$item.plink.orig}}">
<time class="time" title="{{$item.localtime}}" data-toggle="tooltip" datetime="{{$item.utc}}">{{$item.ago}}</time>
</a>
{{if $item.pinned}}
&bull; <i class="fa fa-thumb-tack" aria-hidden="true" title="{{$item.pinned}}"></i>
<span class="sr-only">{{$item.pinned}}</span>
{{/if}}
</small>
</div>
{{* @todo $item.created have to be inserted *}}
</h4>
{{if $item.location_html}}
<div id="wall-item-location-{{$item.id}}" class="wall-item-location">
<small><span class="location">({{$item.location_html nofilter}})</span></small>
</div>
{{/if}}
</div>
{{* @todo $item.created have to be inserted *}}
</h4>
</div>
{{* contact info header for smartphones *}}
<div role="heading" class="contact-info-xs hidden-lg hidden-md">
<div class="preferences">
{{if $item.network_icon != ""}}
<span class="wall-item-network"><i class="fa fa-{{$item.network_icon}}" title="{{$item.network_name}}" aria-hidden="true"></i></span>
{{else}}
<span class="wall-item-network" title="{{$item.app}}">{{$item.network_name}}</span>
{{/if}}
{{if $item.plink}} {{*link to the original source of the item *}}
<a href="{{$item.plink.href}}" class="plink u-url" aria-label="{{$item.plink.title}}" title="{{$item.plink.title}}">
<i class="fa fa-external-link"></i>
</a>
{{/if}}
</div>
<h5 class="media-heading">
<a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card"><span>{{$item.name}}</span></a>
<p class="text-muted"><small>
<span class="wall-item-ago">{{$item.ago}}</span> {{if $item.location_html}}&nbsp;&mdash;&nbsp;({{$item.location_html nofilter}}){{/if}}</small>
</p>
</h5>
</div>
{{* contact info header for smartphones *}}
<div role="heading " class="contact-info-xs hidden-lg hidden-md">
<h5 class="media-heading">
<a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card"><span>{{$item.name}}</span></a>
<p class="text-muted"><small>
<span class="wall-item-ago">{{$item.ago}}</span> {{if $item.location_html}}&nbsp;&mdash;&nbsp;({{$item.location_html nofilter}}){{/if}}</small>
</p>
</h5>
</div>
<div class="clearfix"></div>
<div class="clearfix"></div>
<hr />
<hr />
{{* item content *}}
<div class="wall-item-content {{$item.type}}" id="wall-item-content-{{$item.id}}">
{{if $item.title}}
<span class="wall-item-title" id="wall-item-title-{{$item.id}}"><h4 class="media-heading" dir="auto"><a href="{{$item.plink.href}}" class="{{$item.sparkle}}">{{$item.title}}</a></h4><br /></span>
{{/if}}
<div class="wall-item-body" id="wall-item-body-{{$item.id}}" dir="auto">{{$item.body_html nofilter}}</div>
</div>
<!-- TODO -->
<div class="wall-item-bottom">
<div class="wall-item-links"></div>
<div class="wall-item-tags">
{{if !$item.suppress_tags}}
{{foreach $item.hashtags as $tag}}
<span class="tag label btn-info sm">{{$tag nofilter}} <i class="fa fa-bolt" aria-hidden="true"></i></span>
{{/foreach}}
{{foreach $item.mentions as $tag}}
<span class="mention label btn-warning sm">{{$tag nofilter}} <i class="fa fa-user" aria-hidden="true"></i></span>
{{/foreach}}
{{* item content *}}
<div class="wall-item-content {{$item.type}}" id="wall-item-content-{{$item.id}}">
{{if $item.title}}
<span class="wall-item-title" id="wall-item-title-{{$item.id}}"><h4 class="media-heading" dir="auto"><a href="{{$item.plink.href}}" class="{{$item.sparkle}}">{{$item.title}}</a></h4><br /></span>
{{/if}}
{{foreach $item.folders as $cat}}
<span class="folder label btn-danger sm">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}
<div class="wall-item-body" id="wall-item-body-{{$item.id}}" dir="auto">{{$item.body_html nofilter}}</div>
</div>
{{foreach $item.categories as $cat}}
<span class="category label btn-success sm">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}
</div>
{{if $item.edited}}<div class="itemedited text-muted">{{$item.edited['label']}} (<span title="{{$item.edited['date']}}">{{$item.edited['relative']}}</span>)</div>{{/if}}
<!-- TODO -->
<div class="wall-item-bottom">
<div class="wall-item-links"></div>
<div class="wall-item-tags">
{{if !$item.suppress_tags}}
{{foreach $item.hashtags as $tag}}
<span class="tag label btn-info sm">{{$tag nofilter}} <i class="fa fa-bolt" aria-hidden="true"></i></span>
{{/foreach}}
{{foreach $item.mentions as $tag}}
<span class="mention label btn-warning sm">{{$tag nofilter}} <i class="fa fa-user" aria-hidden="true"></i></span>
{{/foreach}}
{{/if}}
{{foreach $item.folders as $cat}}
<span class="folder label btn-danger sm">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}
{{foreach $item.categories as $cat}}
<span class="category label btn-success sm">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}
</div>
<!-- ./TODO -->
{{if $item.edited}}<div class="itemedited text-muted">{{$item.edited['label']}} (<span title="{{$item.edited['date']}}">{{$item.edited['relative']}}</span>)</div>{{/if}}
</div>
<!-- ./TODO -->
<p class="wall-item-actions">
{{* Action buttons to interact with the item (like: like, dislike, share and so on *}}
<span class="wall-item-actions-left">
<!--comment this out to try something different {{if $item.threaded}}{{if $item.comment_html}}
<div id="button-reply" class="pull-left">
<button type="button" class="btn-link" id="comment-{{$item.id}}" onclick="openClose('item-comments-{{$item.id}}'); commentExpand({{$item.id}});"><i class="fa fa-reply" title="{{$item.switchcomment}}"></i> </span>
</div>
{{/if}}{{/if}}-->
<p class="wall-item-actions">
{{* Action buttons to interact with the item (like: like, dislike, share and so on *}}
<span class="wall-item-actions-left">
<!--comment this out to try something different {{if $item.threaded}}{{if $item.comment_html}}
<div id="button-reply" class="pull-left">
<button type="button" class="btn-link" id="comment-{{$item.id}}" onclick="openClose('item-comments-{{$item.id}}'); commentExpand({{$item.id}});"><i class="fa fa-reply" title="{{$item.switchcomment}}"></i> </span>
</div>
{{/if}}{{/if}}-->
{{if $item.threaded}}{{/if}}
{{if $item.threaded}}{{/if}}
{{* Buttons for like and dislike *}}
{{if $item.vote}}
{{if $item.vote.like}}
<button type="button" class="btn btn-defaultbutton-likes{{if $item.responses.like.self}} active" aria-pressed="true{{/if}}" id="like-{{$item.id}}" title="{{$item.vote.like.0}}" onclick="doActivityItemAction({{$item.id}}, 'like'{{if $item.responses.like.self}}, true{{/if}});">{{$item.vote.like.0}}</button>
{{/if}}
{{if $item.vote.like AND $item.vote.dislike}}
<span role="presentation" class="separator">•</span>
{{/if}}
{{if $item.vote.dislike}}
<button type="button" class="btn btn-defaultbutton-likes{{if $item.responses.like.self}} active" aria-pressed="true{{/if}}" id="dislike-{{$item.id}}" title="{{$item.vote.dislike.0}}" onclick="doActivityItemAction({{$item.id}}, 'dislike'{{if $item.responses.dislike.self}}, true{{/if}});">{{$item.vote.dislike.0}}</button>
{{/if}}
{{if ($item.vote.like OR $item.vote.dislike) AND $item.comment_html}}
<span role="presentation" class="separator">•</span>
{{/if}}
{{* Buttons for like and dislike *}}
{{if $item.vote}}
{{if $item.vote.like}}
<button type="button" class="btn btn-defaultbutton-likes{{if $item.responses.like.self}} active" aria-pressed="true{{/if}}" id="like-{{$item.id}}" title="{{$item.vote.like.0}}" onclick="doActivityItemAction({{$item.id}}, 'like'{{if $item.responses.like.self}}, true{{/if}});">{{$item.vote.like.0}}</button>
{{/if}}
{{if $item.vote.like AND $item.vote.dislike}}
<span role="presentation" class="separator">•</span>
{{/if}}
{{* Button to open the comment text field *}}
{{if $item.comment_html}}
<button type="button" class="btn btn-default" id="comment-{{$item.id}}" title="{{$item.switchcomment}}" onclick="openClose('item-comments-{{$item.id}}'); commentExpand({{$item.id}});">{{$item.switchcomment}}</button>
{{if $item.vote.dislike}}
<button type="button" class="btn btn-defaultbutton-likes{{if $item.responses.like.self}} active" aria-pressed="true{{/if}}" id="dislike-{{$item.id}}" title="{{$item.vote.dislike.0}}" onclick="doActivityItemAction({{$item.id}}, 'dislike'{{if $item.responses.dislike.self}}, true{{/if}});">{{$item.vote.dislike.0}}</button>
{{/if}}
{{* Button for sharing the item *}}
{{if $item.vote}}
{{if $item.vote.share}}
{{if $item.vote.like OR $item.vote.dislike OR $item.comment_html}}
<span role="presentation" class="separator">•</span>
{{/if}}
<button type="button" class="btn btn-default" id="share-{{$item.id}}" title="{{$item.vote.share.0}}" onclick="jotShare({{$item.id}});"><i class="fa fa-retweet" aria-hidden="true"></i>&nbsp;{{$item.vote.share.0}}</button>
{{/if}}
{{if ($item.vote.like OR $item.vote.dislike) AND $item.comment_html}}
<span role="presentation" class="separator">•</span>
{{/if}}
{{/if}}
{{* Put additional actions in a dropdown menu *}}
{{if $item.menu && ($item.edpost || $item.tagger || $item.filer || $item.pin || $item.star || $item.follow_thread || $item.ignore || $item.drop.dropping || $item.browsershare)}}
<span role="presentation" class="separator"></span>
<span class="more-links btn-group{{if $item.thread_level> 1}} dropup{{/if}}">
<button type="button" class="btn-link dropdown-toggle" data-toggle="dropdown" id="dropdownMenuOptions-{{$item.id}}" aria-haspopup="true" aria-expanded="false" title="{{$item.menu}}"><i class="fa fa-ellipsis-h" aria-hidden="true"></i>&nbsp;{{$item.menu}}</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="dropdownMenuOptions-{{$item.id}}">
{{if $item.edpost}} {{* edit the posting *}}
<li role="menuitem">
<a href="javascript:editpost('{{$item.edpost.0}}?mode=none');" title="{{$item.edpost.1}}" class="btn-link navicon pencil"><i class="fa fa-pencil" aria-hidden="true"></i> {{$item.edpost.1}}</a>
</li>
{{* Button to open the comment text field *}}
{{if $item.comment_html}}
<button type="button" class="btn btn-default" id="comment-{{$item.id}}" title="{{$item.switchcomment}}" onclick="openClose('item-comments-{{$item.id}}'); commentExpand({{$item.id}});">{{$item.switchcomment}}</button>
{{/if}}
{{* Button for sharing the item *}}
{{if $item.vote}}
{{if $item.vote.share}}
{{if $item.vote.like OR $item.vote.dislike OR $item.comment_html}}
<span role="presentation" class="separator">•</span>
{{/if}}
<button type="button" class="btn btn-default" id="share-{{$item.id}}" title="{{$item.vote.share.0}}" onclick="jotShare({{$item.id}});"><i class="fa fa-retweet" aria-hidden="true"></i>&nbsp;{{$item.vote.share.0}}</button>
{{/if}}
{{/if}}
{{if $item.tagger}} {{* tag the post *}}
<li role="menuitem">
<a id="tagger-{{$item.id}}" href="javascript:itemTag({{$item.id}});" class="btn-link {{$item.tagger.class}}" title="{{$item.tagger.add}}"><i class="fa fa-tag" aria-hidden="true"></i> {{$item.tagger.add}}</a>
</li>
{{/if}}
{{if $item.filer}}
<li role="menuitem">
<a id="filer-{{$item.id}}" href="javascript:itemFiler({{$item.id}});" class="btn-link filer-item filer-icon" title="{{$item.filer}}"><i class="fa fa-folder" aria-hidden="true"></i>&nbsp;{{$item.filer}}</a>
</li>
{{/if}}
{{if $item.pin}}
<li role="menuitem">
<a id="pin-{{$item.id}}" href="javascript:doPin({{$item.id}});" class="btn-link {{$item.pin.classdo}}" title="{{$item.pin.do}}"><i class="fa fa-circle-o" aria-hidden="true"></i>&nbsp;{{$item.pin.do}}</a>
<a id="unpin-{{$item.id}}" href="javascript:doPin({{$item.id}});" class="btn-link {{$item.pin.classundo}}" title="{{$item.pin.undo}}"><i class="fa fa-dot-circle-o" aria-hidden="true"></i>&nbsp;{{$item.pin.undo}}</a>
</li>
{{/if}}
{{if $item.star}}
<li role="menuitem">
<a id="star-{{$item.id}}" href="javascript:doStar({{$item.id}});" class="btn-link {{$item.star.classdo}}" title="{{$item.star.do}}"><i class="fa fa-star-o" aria-hidden="true"></i>&nbsp;{{$item.star.do}}</a>
<a id="unstar-{{$item.id}}" href="javascript:doStar({{$item.id}});" class="btn-link {{$item.star.classundo}}" title="{{$item.star.undo}}"><i class="fa fa-star" aria-hidden="true"></i>&nbsp;{{$item.star.undo}}</a>
</li>
{{/if}}
{{if $item.follow_thread}}
<li role="menuitem">
<a id="follow_thread-{{$item.id}}" href="javascript:{{$item.follow_thread.action}}" class="btn-link" title="{{$item.follow_thread.title}}"><i class="fa fa-plus" aria-hidden="true"></i>&nbsp;{{$item.follow_thread.title}}</a>
</li>
{{/if}}
{{if $item.language}}
{{* Put additional actions in a dropdown menu *}}
{{if $item.menu && ($item.edpost || $item.tagger || $item.filer || $item.pin || $item.star || $item.follow_thread || $item.ignore || $item.drop.dropping || $item.browsershare)}}
<span role="presentation" class="separator"></span>
<span class="more-links btn-group{{if $item.thread_level> 1}} dropup{{/if}}">
<button type="button" class="btn-link dropdown-toggle" data-toggle="dropdown" id="dropdownMenuOptions-{{$item.id}}" aria-haspopup="true" aria-expanded="false" title="{{$item.menu}}"><i class="fa fa-ellipsis-h" aria-hidden="true"></i>&nbsp;{{$item.menu}}</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="dropdownMenuOptions-{{$item.id}}">
{{if $item.edpost}} {{* edit the posting *}}
<li role="menuitem">
<a id="language-{{$item.id}}" href="javascript:alert('{{$item.language.1}}');" class="btn-link filer-item language-icon" title="{{$item.language.0}}"><i class="fa fa-language" aria-hidden="true"></i>&nbsp;{{$item.language.0}}</a>
<a href="javascript:editpost('{{$item.edpost.0}}?mode=none');" title="{{$item.edpost.1}}" class="btn-link navicon pencil"><i class="fa fa-pencil" aria-hidden="true"></i> {{$item.edpost.1}}</a>
</li>
{{/if}}
{{if $item.tagger}} {{* tag the post *}}
<li role="menuitem">
<a id="tagger-{{$item.id}}" href="javascript:itemTag({{$item.id}});" class="btn-link {{$item.tagger.class}}" title="{{$item.tagger.add}}"><i class="fa fa-tag" aria-hidden="true"></i> {{$item.tagger.add}}</a>
</li>
{{/if}}
{{if $item.filer}}
<li role="menuitem">
<a id="filer-{{$item.id}}" href="javascript:itemFiler({{$item.id}});" class="btn-link filer-item filer-icon" title="{{$item.filer}}"><i class="fa fa-folder" aria-hidden="true"></i>&nbsp;{{$item.filer}}</a>
</li>
{{/if}}
{{if $item.pin}}
<li role="menuitem">
<a id="pin-{{$item.id}}" href="javascript:doPin({{$item.id}});" class="btn-link {{$item.pin.classdo}}" title="{{$item.pin.do}}"><i class="fa fa-circle-o" aria-hidden="true"></i>&nbsp;{{$item.pin.do}}</a>
<a id="unpin-{{$item.id}}" href="javascript:doPin({{$item.id}});" class="btn-link {{$item.pin.classundo}}" title="{{$item.pin.undo}}"><i class="fa fa-dot-circle-o" aria-hidden="true"></i>&nbsp;{{$item.pin.undo}}</a>
</li>
{{/if}}
{{if $item.star}}
<li role="menuitem">
<a id="star-{{$item.id}}" href="javascript:doStar({{$item.id}});" class="btn-link {{$item.star.classdo}}" title="{{$item.star.do}}"><i class="fa fa-star-o" aria-hidden="true"></i>&nbsp;{{$item.star.do}}</a>
<a id="unstar-{{$item.id}}" href="javascript:doStar({{$item.id}});" class="btn-link {{$item.star.classundo}}" title="{{$item.star.undo}}"><i class="fa fa-star" aria-hidden="true"></i>&nbsp;{{$item.star.undo}}</a>
</li>
{{/if}}
{{if $item.follow_thread}}
<li role="menuitem">
<a id="follow_thread-{{$item.id}}" href="javascript:{{$item.follow_thread.action}}" class="btn-link" title="{{$item.follow_thread.title}}"><i class="fa fa-plus" aria-hidden="true"></i>&nbsp;{{$item.follow_thread.title}}</a>
</li>
{{/if}}
{{if $item.language}}
<li role="menuitem">
<a id="language-{{$item.id}}" href="javascript:alert('{{$item.language.1}}');" class="btn-link filer-item language-icon" title="{{$item.language.0}}"><i class="fa fa-language" aria-hidden="true"></i>&nbsp;{{$item.language.0}}</a>
</li>
{{/if}}
{{if $item.browsershare}}
<li role="menuitem" class="button-browser-share">
<a id="browser-share-{{$item.id}}" href="javascript:navigator.share({url: '{{$item.plink.orig}}'});" class="btn-link button-browser-share" title="{{$item.browsershare.1}}"><i class="fa fa-share-alt" aria-hidden="true"></i>&nbsp;{{$item.browsershare.0}}</a>
</li>
{{/if}}
{{if ($item.edpost || $item.tagger || $item.filer || $item.pin || $item.star || $item.follow_thread) && ($item.ignore || $item.drop.dropping)}}
<li role="separator" class="divider"></li>
{{/if}}
{{if $item.ignore}}
<li role="menuitem">
<a id="ignore-{{$item.id}}" href="javascript:doIgnoreThread({{$item.id}});" class="btn-link {{$item.ignore.classdo}}" title="{{$item.ignore.do}}"><i class="fa fa-eye-slash" aria-hidden="true"></i> {{$item.ignore.do}}</a>
</li>
<li role="menuitem">
<a id="unignore-{{$item.id}}" href="javascript:doIgnoreThread({{$item.id}});" class="btn-link {{$item.ignore.classundo}}" title="{{$item.ignore.undo}}"><i class="fa fa-eye" aria-hidden="true"></i> {{$item.ignore.undo}}</a>
</li>
{{/if}}
{{if $item.drop && $item.drop.dropping}}
<li role="menuitem">
<a class="btn-link navicon delete" href="javascript:dropItem('item/drop/{{$item.id}}/{{$item.return}}', 'item-{{$item.guid}}');" title="{{$item.drop.delete}}"><i class="fa fa-trash" aria-hidden="true"></i> {{$item.drop.delete}}</a>
</li>
{{/if}}
{{if $item.browsershare}}
<li role="menuitem" class="button-browser-share">
<a id="browser-share-{{$item.id}}" href="javascript:navigator.share({url: '{{$item.plink.orig}}'});" class="btn-link button-browser-share" title="{{$item.browsershare.1}}"><i class="fa fa-share-alt" aria-hidden="true"></i>&nbsp;{{$item.browsershare.0}}</a>
</li>
{{/if}}
{{if ($item.edpost || $item.tagger || $item.filer || $item.pin || $item.star || $item.follow_thread) && ($item.ignore || $item.drop.dropping)}}
<li role="separator" class="divider"></li>
{{/if}}
{{if $item.ignore}}
<li role="menuitem">
<a id="ignore-{{$item.id}}" href="javascript:doIgnoreThread({{$item.id}});" class="btn-link {{$item.ignore.classdo}}" title="{{$item.ignore.do}}"><i class="fa fa-eye-slash" aria-hidden="true"></i> {{$item.ignore.do}}</a>
</li>
<li role="menuitem">
<a id="unignore-{{$item.id}}" href="javascript:doIgnoreThread({{$item.id}});" class="btn-link {{$item.ignore.classundo}}" title="{{$item.ignore.undo}}"><i class="fa fa-eye" aria-hidden="true"></i> {{$item.ignore.undo}}</a>
</li>
{{/if}}
{{if $item.drop && $item.drop.dropping}}
<li role="menuitem">
<a class="btn-link navicon delete" href="javascript:dropItem('item/drop/{{$item.id}}/{{$item.return}}', 'item-{{$item.guid}}');" title="{{$item.drop.delete}}"><i class="fa fa-trash" aria-hidden="true"></i> {{$item.drop.delete}}</a>
</li>
{{/if}}
</ul>
<img id="like-rotator-{{$item.id}}" class="like-rotator" src="images/rotator.gif" alt="{{$item.wait}}" title="{{$item.wait}}" style="display: none;" />
</span>
{{else}}
</ul>
<img id="like-rotator-{{$item.id}}" class="like-rotator" src="images/rotator.gif" alt="{{$item.wait}}" title="{{$item.wait}}" style="display: none;" />
{{/if}}
</span>
{{else}}
<img id="like-rotator-{{$item.id}}" class="like-rotator" src="images/rotator.gif" alt="{{$item.wait}}" title="{{$item.wait}}" style="display: none;" />
{{/if}}
</span>
<span class="wall-item-actions-right">
{{* Event attendance buttons *}}
{{if $item.isevent}}
<span class="vote-event">
<button type="button" class="btn btn-defaultbutton-event{{if $item.responses.attendyes.self}} active" aria-pressed="true{{/if}}" id="attendyes-{{$item.id}}" title="{{$item.attend.0}}" onclick="doActivityItemAction({{$item.id}}, 'attendyes'{{if $item.responses.attendyes.self}}, true{{/if}});"><i class="fa fa-check" aria-hidden="true"><span class="sr-only">{{$item.attend.0}}</span></i></button>
<button type="button" class="btn btn-defaultbutton-event{{if $item.responses.attendno.self}} active" aria-pressed="true{{/if}}" id="attendno-{{$item.id}}" title="{{$item.attend.1}}" onclick="doActivityItemAction({{$item.id}}, 'attendno'{{if $item.responses.attendno.self}}, true{{/if}});"><i class="fa fa-times" aria-hidden="true"><span class="sr-only">{{$item.attend.1}}</span></i></button>
<button type="button" class="btn btn-defaultbutton-event{{if $item.responses.attendmaybe.self}} active" aria-pressed="true{{/if}}" id="attendmaybe-{{$item.id}}" title="{{$item.attend.2}}" onclick="doActivityItemAction({{$item.id}}, 'attendmaybe'{{if $item.responses.attendmaybe.self}}, true{{/if}});"><i class="fa fa-question" aria-hidden="true"><span class="sr-only">{{$item.attend.2}}</span></i></button>
</span>
{{/if}}
<span class="pull-right checkbox">
{{if $item.drop && $item.drop.pagedrop}}
<input type="checkbox" title="{{$item.drop.select}}" name="itemselected[]" id="checkbox-{{$item.id}}" class="item-select" value="{{$item.id}}" />
<label for="checkbox-{{$item.id}}"></label>
{{/if}}
</span>
<span class="wall-item-actions-right">
{{* Event attendance buttons *}}
{{if $item.isevent}}
<span class="vote-event">
<button type="button" class="btn btn-defaultbutton-event{{if $item.responses.attendyes.self}} active" aria-pressed="true{{/if}}" id="attendyes-{{$item.id}}" title="{{$item.attend.0}}" onclick="doActivityItemAction({{$item.id}}, 'attendyes'{{if $item.responses.attendyes.self}}, true{{/if}});"><i class="fa fa-check" aria-hidden="true"><span class="sr-only">{{$item.attend.0}}</span></i></button>
<button type="button" class="btn btn-defaultbutton-event{{if $item.responses.attendno.self}} active" aria-pressed="true{{/if}}" id="attendno-{{$item.id}}" title="{{$item.attend.1}}" onclick="doActivityItemAction({{$item.id}}, 'attendno'{{if $item.responses.attendno.self}}, true{{/if}});"><i class="fa fa-times" aria-hidden="true"><span class="sr-only">{{$item.attend.1}}</span></i></button>
<button type="button" class="btn btn-defaultbutton-event{{if $item.responses.attendmaybe.self}} active" aria-pressed="true{{/if}}" id="attendmaybe-{{$item.id}}" title="{{$item.attend.2}}" onclick="doActivityItemAction({{$item.id}}, 'attendmaybe'{{if $item.responses.attendmaybe.self}}, true{{/if}});"><i class="fa fa-question" aria-hidden="true"><span class="sr-only">{{$item.attend.2}}</span></i></button>
</span>
</p><!--./wall-item-actions-->
{{* Display likes, dislike and attendance stats *}}
{{if $item.responses}}
<div class="wall-item-responses">
{{foreach $item.responses as $verb=>$response}}
<div class="wall-item-{{$verb}}" id="wall-item-{{$verb}}-{{$item.id}}">{{$response.output nofilter}}</div>
{{/foreach}}
</div>
{{/if}}
<div class="wall-item-conv" id="wall-item-conv-{{$item.id}}" dir="auto">
{{if $item.conv}}
<a href="{{$item.conv.href}}" id="context-{{$item.id}}" title="{{$item.conv.title}}">{{$item.conv.title}}</a>
<span class="pull-right checkbox">
{{if $item.drop && $item.drop.pagedrop}}
<input type="checkbox" title="{{$item.drop.select}}" name="itemselected[]" id="checkbox-{{$item.id}}" class="item-select" value="{{$item.id}}" />
<label for="checkbox-{{$item.id}}"></label>
{{/if}}
</div>
</div><!--./media>-->
</div><!-- ./panel-body -->
</div><!--./panel-->
</span>
</span>
</p><!--./wall-item-actions-->
{{* Display likes, dislike and attendance stats *}}
{{if $item.responses}}
<div class="wall-item-responses">
{{foreach $item.responses as $verb=>$response}}
<div class="wall-item-{{$verb}}" id="wall-item-{{$verb}}-{{$item.id}}">{{$response.output nofilter}}</div>
{{/foreach}}
</div>
{{/if}}
<div class="wall-item-conv" id="wall-item-conv-{{$item.id}}" dir="auto">
{{if $item.conv}}
<a href="{{$item.conv.href}}" id="context-{{$item.id}}" title="{{$item.conv.title}}">{{$item.conv.title}}</a>
{{/if}}
</div>
</div><!--./media>-->
</div><!-- ./panel-body -->

View File

@ -179,7 +179,7 @@ function frio_contact_photo_menu(App $a, &$args)
// Add to pm link a new key with the value 'modal'.
// Later we can make conditions in the corresponding templates (e.g.
// contact_template.tpl)
// contact/entry.tpl)
if (strpos($pmlink, 'message/new/' . $cid) !== false) {
$args['menu']['pm'][3] = 'modal';
}
@ -236,6 +236,9 @@ function frio_remote_nav(App $a, array &$nav_info)
// user menu
$nav_info['nav']['usermenu'][] = [$server_url . '/profile/' . $remoteUser['nick'], DI::l10n()->t('Status'), '', DI::l10n()->t('Your posts and conversations')];
$nav_info['nav']['usermenu'][] = [$server_url . '/profile/' . $remoteUser['nick'] . '/profile', DI::l10n()->t('Profile'), '', DI::l10n()->t('Your profile page')];
// Kept for backwards-compatibility reasons, the remote server may not have updated to version 2022.12 yet
// @TODO Switch with the new routes by version 2023.12
//$nav_info['nav']['usermenu'][] = [$server_url . '/profile/' . $remoteUser['nick'] . '/photos', DI::l10n()->t('Photos'), '', DI::l10n()->t('Your photos')];
$nav_info['nav']['usermenu'][] = [$server_url . '/photos/' . $remoteUser['nick'], DI::l10n()->t('Photos'), '', DI::l10n()->t('Your photos')];
$nav_info['nav']['usermenu'][] = [$server_url . '/profile/' . $remoteUser['nick'] . '/media', DI::l10n()->t('Media'), '', DI::l10n()->t('Your postings with media')];
$nav_info['nav']['usermenu'][] = [$server_url . '/calendar/', DI::l10n()->t('Calendar'), '', DI::l10n()->t('Your calendar')];

View File

@ -70,7 +70,6 @@
<div style="display: none;">
<div id="profile-jot-acl-wrapper" style="width:auto;height:auto;overflow:auto;">
{{$acl nofilter}}
{{$jotnets nofilter}}
{{if $scheduled_at}}{{$scheduled_at nofilter}}{{/if}}
{{if $created_at}}{{$created_at nofilter}}{{/if}}
</div>