2018-03-24 14:38:05 -04:00
< ? php
/**
* @ file src / Core / Acl . php
*/
namespace Friendica\Core ;
2019-11-28 12:42:12 -05:00
use Friendica\App\Page ;
2018-07-20 08:19:26 -04:00
use Friendica\Database\DBA ;
2019-12-15 16:34:11 -05:00
use Friendica\DI ;
2018-03-24 14:38:05 -04:00
use Friendica\Model\Contact ;
2019-11-28 12:33:00 -05:00
use Friendica\Model\Group ;
2018-03-24 14:38:05 -04:00
/**
* Handle ACL management and display
*
2018-09-15 19:28:38 -04:00
* @ author Hypolite Petovan < hypolite @ mrpetovan . com >
2018-03-24 14:38:05 -04:00
*/
2019-12-15 17:28:01 -05:00
class ACL
2018-03-24 14:38:05 -04:00
{
/**
* Returns a select input tag with all the contact of the local user
*
2019-01-06 16:06:53 -05:00
* @ param string $selname Name attribute of the select input tag
* @ param string $selclass Class attribute of the select input tag
* @ param array $options Available options :
* - size : length of the select box
* - mutual_friends : Only used for the hook
* - single : Only used for the hook
* - exclude : Only used for the hook
* @ param array $preselected Contact ID that should be already selected
2018-03-24 14:38:05 -04:00
* @ return string
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-03-24 14:38:05 -04:00
*/
public static function getSuggestContactSelectHTML ( $selname , $selclass , array $options = [], array $preselected = [])
{
2019-12-15 16:34:11 -05:00
$a = DI :: app ();
2018-03-24 14:38:05 -04:00
$networks = null ;
2019-10-16 08:35:14 -04:00
$size = ( $options [ 'size' ] ? ? 0 ) ? : 4 ;
2018-03-24 14:38:05 -04:00
$mutual = ! empty ( $options [ 'mutual_friends' ]);
$single = ! empty ( $options [ 'single' ]) && empty ( $options [ 'multiple' ]);
2019-10-16 08:35:14 -04:00
$exclude = $options [ 'exclude' ] ? ? false ;
2018-03-24 14:38:05 -04:00
2019-10-16 08:35:14 -04:00
switch (( $options [ 'networks' ] ? ? '' ) ? : Protocol :: PHANTOM ) {
2018-03-24 14:38:05 -04:00
case 'DFRN_ONLY' :
2018-08-11 16:40:44 -04:00
$networks = [ Protocol :: DFRN ];
2018-03-24 14:38:05 -04:00
break ;
2018-08-11 16:40:44 -04:00
2018-03-24 14:38:05 -04:00
case 'PRIVATE' :
2018-10-05 23:17:44 -04:00
$networks = [ Protocol :: ACTIVITYPUB , Protocol :: DFRN , Protocol :: MAIL , Protocol :: DIASPORA ];
2018-03-24 14:38:05 -04:00
break ;
2018-08-11 16:40:44 -04:00
2018-03-24 14:38:05 -04:00
case 'TWO_WAY' :
if ( ! empty ( $a -> user [ 'prvnets' ])) {
2018-10-05 23:17:44 -04:00
$networks = [ Protocol :: ACTIVITYPUB , Protocol :: DFRN , Protocol :: MAIL , Protocol :: DIASPORA ];
2018-03-24 14:38:05 -04:00
} else {
2018-10-05 23:17:44 -04:00
$networks = [ Protocol :: ACTIVITYPUB , Protocol :: DFRN , Protocol :: MAIL , Protocol :: DIASPORA , Protocol :: OSTATUS ];
2018-03-24 14:38:05 -04:00
}
break ;
2018-08-11 16:40:44 -04:00
2018-03-24 14:38:05 -04:00
default : /// @TODO Maybe log this call?
break ;
}
$x = [ 'options' => $options , 'size' => $size , 'single' => $single , 'mutual' => $mutual , 'exclude' => $exclude , 'networks' => $networks ];
2018-12-26 01:06:24 -05:00
Hook :: callAll ( 'contact_select_options' , $x );
2018-03-24 14:38:05 -04:00
$o = '' ;
$sql_extra = '' ;
if ( ! empty ( $x [ 'mutual' ])) {
2018-07-24 22:53:46 -04:00
$sql_extra .= sprintf ( " AND `rel` = %d " , intval ( Contact :: FRIEND ));
2018-03-24 14:38:05 -04:00
}
if ( ! empty ( $x [ 'exclude' ])) {
$sql_extra .= sprintf ( " AND `id` != %d " , intval ( $x [ 'exclude' ]));
}
if ( ! empty ( $x [ 'networks' ])) {
/// @TODO rewrite to foreach()
array_walk ( $x [ 'networks' ], function ( & $value ) {
2018-07-21 09:10:13 -04:00
$value = " ' " . DBA :: escape ( $value ) . " ' " ;
2018-03-24 14:38:05 -04:00
});
$str_nets = implode ( ',' , $x [ 'networks' ]);
$sql_extra .= " AND `network` IN ( $str_nets ) " ;
}
$tabindex = ( ! empty ( $options [ 'tabindex' ]) ? 'tabindex="' . $options [ " tabindex " ] . '"' : '' );
if ( ! empty ( $x [ 'single' ])) {
$o .= " <select name= \" $selname\ " id = \ " $selclass\ " class = \ " $selclass\ " size = \ " " . $x [ 'size' ] . " \" $tabindex > \r \n " ;
} else {
$o .= " <select name= \" { $selname } [] \" id= \" $selclass\ " class = \ " $selclass\ " multiple = \ " multiple \" size= \" " . $x [ 'size' ] . " $\ " $tabindex > \r\n " ;
}
2018-07-20 08:19:26 -04:00
$stmt = DBA :: p ( " SELECT `id`, `name`, `url`, `network` FROM `contact`
2019-01-09 08:23:11 -05:00
WHERE `uid` = ? AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND NOT `deleted` AND `notify` != ''
2018-03-24 14:38:05 -04:00
$sql_extra
ORDER BY `name` ASC " , intval(local_user())
);
2018-07-20 22:03:40 -04:00
$contacts = DBA :: toArray ( $stmt );
2018-03-24 14:38:05 -04:00
$arr = [ 'contact' => $contacts , 'entry' => $o ];
// e.g. 'network_pre_contact_deny', 'profile_pre_contact_allow'
2018-12-26 01:06:24 -05:00
Hook :: callAll ( $a -> module . '_pre_' . $selname , $arr );
2018-03-24 14:38:05 -04:00
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $contacts )) {
2018-03-24 14:38:05 -04:00
foreach ( $contacts as $contact ) {
if ( in_array ( $contact [ 'id' ], $preselected )) {
$selected = ' selected="selected" ' ;
} else {
$selected = '' ;
}
$trimmed = mb_substr ( $contact [ 'name' ], 0 , 20 );
$o .= " <option value= \" { $contact [ 'id' ] } \" $selected title= \" { $contact [ 'name' ] } | { $contact [ 'url' ] } \" > $trimmed </option> \r \n " ;
}
}
$o .= '</select>' . PHP_EOL ;
2018-12-26 01:06:24 -05:00
Hook :: callAll ( $a -> module . '_post_' . $selname , $o );
2018-03-24 14:38:05 -04:00
return $o ;
}
/**
* Returns a select input tag with all the contact of the local user
*
* @ param string $selname Name attribute of the select input tag
* @ param string $selclass Class attribute of the select input tag
* @ param array $preselected Contact IDs that should be already selected
* @ param int $size Length of the select box
* @ param int $tabindex Select input tag tabindex attribute
* @ return string
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-03-24 14:38:05 -04:00
*/
public static function getMessageContactSelectHTML ( $selname , $selclass , array $preselected = [], $size = 4 , $tabindex = null )
{
2019-12-15 16:34:11 -05:00
$a = DI :: app ();
2018-03-24 14:38:05 -04:00
$o = '' ;
// When used for private messages, we limit correspondence to mutual DFRN/Friendica friends and the selector
// to one recipient. By default our selector allows multiple selects amongst all contacts.
2018-07-24 22:53:46 -04:00
$sql_extra = sprintf ( " AND `rel` = %d " , intval ( Contact :: FRIEND ));
2018-08-11 16:40:44 -04:00
$sql_extra .= sprintf ( " AND `network` IN ('%s' , '%s') " , Protocol :: DFRN , Protocol :: DIASPORA );
2018-03-24 14:38:05 -04:00
$tabindex_attr = ! empty ( $tabindex ) ? ' tabindex="' . intval ( $tabindex ) . '"' : '' ;
$hidepreselected = '' ;
if ( $preselected ) {
$sql_extra .= " AND `id` IN ( " . implode ( " , " , $preselected ) . " ) " ;
$hidepreselected = ' style="display: none;"' ;
}
$o .= " <select name= \" $selname\ " id = \ " $selclass\ " class = \ " $selclass\ " size = \ " $size\ " $tabindex_attr $hidepreselected > \r\n " ;
2018-07-20 08:19:26 -04:00
$stmt = DBA :: p ( " SELECT `id`, `name`, `url`, `network` FROM `contact`
2019-01-09 08:23:11 -05:00
WHERE `uid` = ? AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND NOT `deleted` AND `notify` != ''
2018-03-24 14:38:05 -04:00
$sql_extra
ORDER BY `name` ASC " , intval(local_user())
);
2018-07-20 22:03:40 -04:00
$contacts = DBA :: toArray ( $stmt );
2018-03-24 14:38:05 -04:00
$arr = [ 'contact' => $contacts , 'entry' => $o ];
// e.g. 'network_pre_contact_deny', 'profile_pre_contact_allow'
2018-12-26 01:06:24 -05:00
Hook :: callAll ( $a -> module . '_pre_' . $selname , $arr );
2018-03-24 14:38:05 -04:00
$receiverlist = [];
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $contacts )) {
2018-03-24 14:38:05 -04:00
foreach ( $contacts as $contact ) {
if ( in_array ( $contact [ 'id' ], $preselected )) {
$selected = ' selected="selected"' ;
} else {
$selected = '' ;
}
$trimmed = Protocol :: formatMention ( $contact [ 'url' ], $contact [ 'name' ]);
$receiverlist [] = $trimmed ;
$o .= " <option value= \" { $contact [ 'id' ] } \" $selected title= \" { $contact [ 'name' ] } | { $contact [ 'url' ] } \" > $trimmed </option> \r \n " ;
}
}
$o .= '</select>' . PHP_EOL ;
if ( $preselected ) {
$o .= implode ( ', ' , $receiverlist );
}
2018-12-26 01:06:24 -05:00
Hook :: callAll ( $a -> module . '_post_' . $selname , $o );
2018-03-24 14:38:05 -04:00
return $o ;
}
2018-03-24 15:40:35 -04:00
private static function fixACL ( & $item )
{
2018-03-24 14:38:05 -04:00
$item = intval ( str_replace ([ '<' , '>' ], [ '' , '' ], $item ));
}
/**
* Return the default permission of the provided user array
*
* @ param array $user
* @ return array Hash of contact id lists
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-03-24 14:38:05 -04:00
*/
public static function getDefaultUserPermissions ( array $user = null )
{
$matches = [];
$acl_regex = '/<([0-9]+)>/i' ;
2019-10-16 08:35:14 -04:00
preg_match_all ( $acl_regex , $user [ 'allow_cid' ] ? ? '' , $matches );
2018-03-24 14:38:05 -04:00
$allow_cid = $matches [ 1 ];
2019-10-16 08:35:14 -04:00
preg_match_all ( $acl_regex , $user [ 'allow_gid' ] ? ? '' , $matches );
2018-03-24 14:38:05 -04:00
$allow_gid = $matches [ 1 ];
2019-10-16 08:35:14 -04:00
preg_match_all ( $acl_regex , $user [ 'deny_cid' ] ? ? '' , $matches );
2018-03-24 14:38:05 -04:00
$deny_cid = $matches [ 1 ];
2019-10-16 08:35:14 -04:00
preg_match_all ( $acl_regex , $user [ 'deny_gid' ] ? ? '' , $matches );
2018-03-24 14:38:05 -04:00
$deny_gid = $matches [ 1 ];
// Reformats the ACL data so that it is accepted by the JS frontend
2018-03-24 15:40:35 -04:00
array_walk ( $allow_cid , 'self::fixACL' );
array_walk ( $allow_gid , 'self::fixACL' );
array_walk ( $deny_cid , 'self::fixACL' );
array_walk ( $deny_gid , 'self::fixACL' );
2018-03-24 14:38:05 -04:00
Contact :: pruneUnavailable ( $allow_cid );
return [
'allow_cid' => $allow_cid ,
'allow_gid' => $allow_gid ,
'deny_cid' => $deny_cid ,
'deny_gid' => $deny_gid ,
];
}
2019-11-28 12:33:00 -05:00
/**
* Returns the ACL list of contacts for a given user id
*
* @ param int $user_id
* @ return array
* @ throws \Exception
*/
public static function getContactListByUserId ( int $user_id )
{
2019-12-06 02:25:21 -05:00
$fields = [ 'id' , 'name' , 'addr' , 'micro' ];
$params = [ 'order' => [ 'name' ]];
$acl_contacts = Contact :: selectToArray ( $fields ,
[ 'uid' => $user_id , 'self' => false , 'blocked' => false , 'archive' => false , 'deleted' => false ,
'pending' => false , 'rel' => [ Contact :: FOLLOWER , Contact :: FRIEND ]], $params
2019-11-28 12:33:00 -05:00
);
2019-12-06 02:25:21 -05:00
$acl_forums = Contact :: selectToArray ( $fields ,
[ 'uid' => $user_id , 'self' => false , 'blocked' => false , 'archive' => false , 'deleted' => false ,
'pending' => false , 'contact-type' => Contact :: TYPE_COMMUNITY ], $params
);
$acl_contacts = array_merge ( $acl_forums , $acl_contacts );
2019-11-28 12:33:00 -05:00
array_walk ( $acl_contacts , function ( & $value ) {
$value [ 'type' ] = 'contact' ;
});
return $acl_contacts ;
}
/**
* Returns the ACL list of groups ( including meta - groups ) for a given user id
*
* @ param int $user_id
* @ return array
*/
public static function getGroupListByUserId ( int $user_id )
{
$acl_groups = [
[
'id' => Group :: FOLLOWERS ,
'name' => L10n :: t ( 'Followers' ),
'addr' => '' ,
'micro' => 'images/twopeople.png' ,
'type' => 'group' ,
],
[
'id' => Group :: MUTUALS ,
'name' => L10n :: t ( 'Mutuals' ),
'addr' => '' ,
'micro' => 'images/twopeople.png' ,
'type' => 'group' ,
]
];
foreach ( Group :: getByUserId ( $user_id ) as $group ) {
$acl_groups [] = [
'id' => $group [ 'id' ],
'name' => $group [ 'name' ],
'addr' => '' ,
'micro' => 'images/twopeople.png' ,
'type' => 'group' ,
];
}
return $acl_groups ;
}
2018-03-24 14:38:05 -04:00
/**
* Return the full jot ACL selector HTML
*
2019-11-28 12:42:12 -05:00
* @ param Page $page
2018-08-14 18:43:27 -04:00
* @ param array $user User array
2019-11-28 12:42:12 -05:00
* @ param bool $for_federation
* @ param array $default_permissions Static defaults permission array :
* [
* 'allow_cid' => [],
* 'allow_gid' => [],
* 'deny_cid' => [],
* 'deny_gid' => [],
* 'hidewall' => true / false
* ]
2018-03-24 14:38:05 -04:00
* @ return string
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-03-24 14:38:05 -04:00
*/
2019-11-28 12:42:12 -05:00
public static function getFullSelectorHTML ( Page $page , array $user = null , bool $for_federation = false , array $default_permissions = [])
2018-03-24 14:38:05 -04:00
{
2019-12-13 13:29:06 -05:00
if ( empty ( $user [ 'uid' ])) {
return '' ;
}
2019-11-28 12:42:12 -05:00
$page -> registerFooterScript ( Theme :: getPathForFile ( 'asset/typeahead.js/dist/typeahead.bundle.js' ));
$page -> registerFooterScript ( Theme :: getPathForFile ( 'js/friendica-tagsinput/friendica-tagsinput.js' ));
$page -> registerStylesheet ( Theme :: getPathForFile ( 'js/friendica-tagsinput/friendica-tagsinput.css' ));
$page -> registerStylesheet ( Theme :: getPathForFile ( 'js/friendica-tagsinput/friendica-tagsinput-typeahead.css' ));
2018-08-14 18:43:27 -04:00
// Defaults user permissions
if ( empty ( $default_permissions )) {
$default_permissions = self :: getDefaultUserPermissions ( $user );
2018-07-20 14:07:54 -04:00
}
2019-12-03 06:59:51 -05:00
$default_permissions = [
'allow_cid' => $default_permissions [ 'allow_cid' ] ? ? [],
'allow_gid' => $default_permissions [ 'allow_gid' ] ? ? [],
'deny_cid' => $default_permissions [ 'deny_cid' ] ? ? [],
'deny_gid' => $default_permissions [ 'deny_gid' ] ? ? [],
'hidewall' => $default_permissions [ 'hidewall' ] ? ? false ,
];
2019-11-28 12:42:12 -05:00
if ( count ( $default_permissions [ 'allow_cid' ])
+ count ( $default_permissions [ 'allow_gid' ])
+ count ( $default_permissions [ 'deny_cid' ])
+ count ( $default_permissions [ 'deny_gid' ])) {
$visibility = 'custom' ;
} else {
$visibility = 'public' ;
// Default permission display for custom panel
$default_permissions [ 'allow_gid' ] = [ Group :: FOLLOWERS ];
}
2019-03-24 22:40:50 -04:00
$jotnets_fields = [];
2019-11-28 12:42:12 -05:00
if ( $for_federation ) {
2018-03-24 14:38:05 -04:00
$mail_enabled = false ;
$pubmail_enabled = false ;
2019-03-24 22:40:50 -04:00
if ( function_exists ( 'imap_open' ) && ! Config :: get ( 'system' , 'imap_disabled' )) {
2019-11-29 15:55:52 -05:00
$mailacct = DBA :: selectFirst ( 'mailacct' , [ 'pubmail' ], [ '`uid` = ? AND `server` != ""' , $user [ 'uid' ]]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $mailacct )) {
2018-03-24 14:38:05 -04:00
$mail_enabled = true ;
$pubmail_enabled = ! empty ( $mailacct [ 'pubmail' ]);
}
}
2019-12-05 20:05:47 -05:00
if ( ! $default_permissions [ 'hidewall' ]) {
2018-03-24 14:38:05 -04:00
if ( $mail_enabled ) {
2019-03-24 22:40:50 -04:00
$jotnets_fields [] = [
'type' => 'checkbox' ,
'field' => [
'pubmail_enable' ,
L10n :: t ( 'Post to Email' ),
$pubmail_enabled
]
];
2018-03-24 14:38:05 -04:00
}
2019-03-24 22:40:50 -04:00
Hook :: callAll ( 'jot_networks' , $jotnets_fields );
2018-03-24 14:38:05 -04:00
}
}
2019-03-24 22:40:50 -04:00
2019-11-28 12:42:12 -05:00
$acl_contacts = self :: getContactListByUserId ( $user [ 'uid' ]);
$acl_groups = self :: getGroupListByUserId ( $user [ 'uid' ]);
$acl_list = array_merge ( $acl_groups , $acl_contacts );
2018-10-31 10:44:06 -04:00
$tpl = Renderer :: getMarkupTemplate ( 'acl_selector.tpl' );
2018-10-31 10:35:50 -04:00
$o = Renderer :: replaceMacros ( $tpl , [
2019-11-28 12:42:12 -05:00
'$public_title' => L10n :: t ( 'Public' ),
'$public_desc' => L10n :: t ( 'This content will be shown to all your followers and can be seen in the community pages and by anyone with its link.' ),
'$custom_title' => L10n :: t ( 'Limited/Private' ),
'$custom_desc' => L10n :: t ( 'This content will be shown only to the people in the first box, to the exception of the people mentioned in the second box. It won\'t appear anywhere public.' ),
'$allow_label' => L10n :: t ( 'Show to:' ),
'$deny_label' => L10n :: t ( 'Except to:' ),
'$emailcc' => L10n :: t ( 'CC: email addresses' ),
'$emtitle' => L10n :: t ( 'Example: bob@example.com, mary@example.com' ),
2019-03-24 22:40:50 -04:00
'$jotnets_summary' => L10n :: t ( 'Connectors' ),
'$jotnets_disabled_label' => L10n :: t ( 'Connectors disabled, since "%s" is enabled.' , L10n :: t ( 'Hide your profile details from unknown viewers?' )),
2019-11-28 12:42:12 -05:00
'$visibility' => $visibility ,
'$acl_contacts' => $acl_contacts ,
'$acl_groups' => $acl_groups ,
'$acl_list' => $acl_list ,
'$contact_allow' => implode ( ',' , $default_permissions [ 'allow_cid' ]),
'$group_allow' => implode ( ',' , $default_permissions [ 'allow_gid' ]),
'$contact_deny' => implode ( ',' , $default_permissions [ 'deny_cid' ]),
'$group_deny' => implode ( ',' , $default_permissions [ 'deny_gid' ]),
'$for_federation' => $for_federation ,
'$jotnets_fields' => $jotnets_fields ,
2019-12-03 06:59:51 -05:00
'$user_hidewall' => $default_permissions [ 'hidewall' ],
2018-03-24 14:38:05 -04:00
]);
return $o ;
}
}