diff options
Diffstat (limited to 'Zotlabs/Lib')
-rw-r--r-- | Zotlabs/Lib/AConfig.php | 25 | ||||
-rw-r--r-- | Zotlabs/Lib/AbConfig.php | 73 | ||||
-rw-r--r-- | Zotlabs/Lib/Apps.php | 708 | ||||
-rw-r--r-- | Zotlabs/Lib/Chatroom.php | 267 | ||||
-rw-r--r-- | Zotlabs/Lib/Config.php | 166 | ||||
-rw-r--r-- | Zotlabs/Lib/Enotify.php | 685 | ||||
-rw-r--r-- | Zotlabs/Lib/IConfig.php | 165 | ||||
-rw-r--r-- | Zotlabs/Lib/PConfig.php | 189 | ||||
-rw-r--r-- | Zotlabs/Lib/ProtoDriver.php | 19 | ||||
-rw-r--r-- | Zotlabs/Lib/System.php | 58 | ||||
-rw-r--r-- | Zotlabs/Lib/ThreadItem.php | 780 | ||||
-rw-r--r-- | Zotlabs/Lib/ThreadStream.php | 220 | ||||
-rw-r--r-- | Zotlabs/Lib/XConfig.php | 160 | ||||
-rw-r--r-- | Zotlabs/Lib/ZotDriver.php | 30 |
14 files changed, 3545 insertions, 0 deletions
diff --git a/Zotlabs/Lib/AConfig.php b/Zotlabs/Lib/AConfig.php new file mode 100644 index 000000000..24ec97dfa --- /dev/null +++ b/Zotlabs/Lib/AConfig.php @@ -0,0 +1,25 @@ +<?php + +namespace Zotlabs\Lib; + +// account configuration storage is built on top of the under-utilised xconfig + +class AConfig { + + static public function Load($account_id) { + return XConfig::Load('a_' . $account_id); + } + + static public function Get($account_id,$family,$key) { + return XConfig::Get('a_' . $account_id,$family,$key); + } + + static public function Set($account_id,$family,$key,$value) { + return XConfig::Get('a_' . $account_id,$family,$key,$value); + } + + static public function Delete($account_id,$family,$key) { + return XConfig::Delete('a_' . $account_id,$family,$key); + } + +} diff --git a/Zotlabs/Lib/AbConfig.php b/Zotlabs/Lib/AbConfig.php new file mode 100644 index 000000000..f2d6522b9 --- /dev/null +++ b/Zotlabs/Lib/AbConfig.php @@ -0,0 +1,73 @@ +<?php + +namespace Zotlabs\Lib; + + +class AbConfig { + + static public function Load($chash,$xhash) { + $r = q("select * from abconfig where chan = '%s' and xchan = '%s'", + dbesc($chash), + dbesc($xhash) + ); + return $r; + } + + + static public function Get($chash,$xhash,$family,$key) { + $r = q("select * from abconfig where chan = '%s' and xchan = '%s' and cat = '%s' and k = '%s' limit 1", + dbesc($chash), + dbesc($xhash), + dbesc($family), + dbesc($key) + ); + if($r) { + return ((preg_match('|^a:[0-9]+:{.*}$|s', $r[0]['v'])) ? unserialize($r[0]['v']) : $r[0]['v']); + } + return false; + } + + + static public function Set($chash,$xhash,$family,$key,$value) { + + $dbvalue = ((is_array($value)) ? serialize($value) : $value); + $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + + if(self::Get($chash,$xhash,$family,$key) === false) { + $r = q("insert into abconfig ( chan, xchan, cat, k, v ) values ( '%s', '%s', '%s', '%s', '%s' ) ", + dbesc($chash), + dbesc($xhash), + dbesc($family), + dbesc($key), + dbesc($dbvalue) + ); + } + else { + $r = q("update abconfig set v = '%s' where chan = '%s' and xchan = '%s' and cat = '%s' and k = '%s' ", + dbesc($dbvalue), + dbesc($chash), + dbesc($xhash), + dbesc($family), + dbesc($key) + ); + } + + if($r) + return $value; + return false; + } + + + static public function Delete($chash,$xhash,$family,$key) { + + $r = q("delete from abconfig where chan = '%s' and xchan = '%s' and cat = '%s' and k = '%s' ", + dbesc($chash), + dbesc($xhash), + dbesc($family), + dbesc($key) + ); + + return $r; + } + +}
\ No newline at end of file diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php new file mode 100644 index 000000000..20556212a --- /dev/null +++ b/Zotlabs/Lib/Apps.php @@ -0,0 +1,708 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + +/** + * Apps + * + */ + +require_once('include/plugin.php'); +require_once('include/channel.php'); + + +class Apps { + + static public $installed_system_apps = null; + + static public function get_system_apps($translate = true) { + + $ret = array(); + if(is_dir('apps')) + $files = glob('apps/*.apd'); + else + $files = glob('app/*.apd'); + if($files) { + foreach($files as $f) { + $x = self::parse_app_description($f,$translate); + if($x) { + $ret[] = $x; + } + } + } + $files = glob('addon/*/*.apd'); + if($files) { + foreach($files as $f) { + $n = basename($f,'.apd'); + if(plugin_is_installed($n)) { + $x = self::parse_app_description($f,$translate); + if($x) { + $ret[] = $x; + } + } + } + } + + return $ret; + + } + + + static public function import_system_apps() { + if(! local_channel()) + return; + $apps = self::get_system_apps(false); + + + self::$installed_system_apps = q("select * from app where app_system = 1 and app_channel = %d", + intval(local_channel()) + ); + + if($apps) { + foreach($apps as $app) { + $id = self::check_install_system_app($app); + // $id will be boolean true or false to install an app, or an integer id to update an existing app + if($id === false) + continue; + if($id !== true) { + // if we already installed this app, but it changed, preserve any categories we created + $s = ''; + $r = q("select * from term where otype = %d and oid = d", + intval(TERM_OBJ_APP), + intval($id) + ); + if($r) { + foreach($r as $t) { + if($s) + $s .= ','; + $s .= $t['term']; + } + $app['categories'] = $s; + } + } + $app['uid'] = local_channel(); + $app['guid'] = hash('whirlpool',$app['name']); + $app['system'] = 1; + self::app_install(local_channel(),$app); + } + } + } + + /** + * Install the system app if no system apps have been installed, or if a new system app + * is discovered, or if the version of a system app changes. + */ + + static public function check_install_system_app($app) { + if((! is_array(self::$installed_system_apps)) || (! count(self::$installed_system_apps))) { + return true; + } + $notfound = true; + foreach(self::$installed_system_apps as $iapp) { + if($iapp['app_id'] == hash('whirlpool',$app['name'])) { + $notfound = false; + if($iapp['app_version'] != $app['version']) { + return intval($iapp['app_id']); + } + } + } + return $notfound; + } + + + static public function app_name_compare($a,$b) { + return strcmp($a['name'],$b['name']); + } + + + static public function parse_app_description($f,$translate = true) { + $ret = array(); + + $baseurl = z_root(); + $channel = \App::get_channel(); + $address = (($channel) ? $channel['channel_address'] : ''); + + //future expansion + + $observer = \App::get_observer(); + + + $lines = @file($f); + if($lines) { + foreach($lines as $x) { + if(preg_match('/^([a-zA-Z].*?):(.*?)$/ism',$x,$matches)) { + $ret[$matches[1]] = trim(str_replace(array('$baseurl','$nick'),array($baseurl,$address),$matches[2])); + } + } + } + + + if(! $ret['photo']) + $ret['photo'] = $baseurl . '/' . get_default_profile_photo(80); + + $ret['type'] = 'system'; + + foreach($ret as $k => $v) { + if(strpos($v,'http') === 0) + $ret[$k] = zid($v); + } + + if(array_key_exists('desc',$ret)) + $ret['desc'] = str_replace(array('\'','"'),array(''','&dquot;'),$ret['desc']); + + if(array_key_exists('target',$ret)) + $ret['target'] = str_replace(array('\'','"'),array(''','&dquot;'),$ret['target']); + + if(array_key_exists('version',$ret)) + $ret['version'] = str_replace(array('\'','"'),array(''','&dquot;'),$ret['version']); + + + if(array_key_exists('requires',$ret)) { + $requires = explode(',',$ret['requires']); + foreach($requires as $require) { + $require = trim(strtolower($require)); + switch($require) { + case 'nologin': + if(local_channel()) + unset($ret); + break; + case 'admin': + if(! is_site_admin()) + unset($ret); + break; + case 'local_channel': + if(! local_channel()) + unset($ret); + break; + case 'public_profile': + if(! is_public_profile()) + unset($ret); + break; + case 'observer': + if(! $observer) + unset($ret); + break; + default: + if(! (local_channel() && feature_enabled(local_channel(),$require))) + unset($ret); + break; + + } + } + } + if($ret) { + if($translate) + self::translate_system_apps($ret); + return $ret; + } + return false; + } + + + static public function translate_system_apps(&$arr) { + $apps = array( + 'Site Admin' => t('Site Admin'), + 'Bug Report' => t('Bug Report'), + 'View Bookmarks' => t('View Bookmarks'), + 'My Chatrooms' => t('My Chatrooms'), + 'Connections' => t('Connections'), + 'Firefox Share' => t('Firefox Share'), + 'Remote Diagnostics' => t('Remote Diagnostics'), + 'Suggest Channels' => t('Suggest Channels'), + 'Login' => t('Login'), + 'Channel Manager' => t('Channel Manager'), + 'Grid' => t('Grid'), + 'Settings' => t('Settings'), + 'Files' => t('Files'), + 'Webpages' => t('Webpages'), + 'Wiki' => t('Wiki'), + 'Channel Home' => t('Channel Home'), + 'View Profile' => t('View Profile'), + 'Photos' => t('Photos'), + 'Events' => t('Events'), + 'Directory' => t('Directory'), + 'Help' => t('Help'), + 'Mail' => t('Mail'), + 'Mood' => t('Mood'), + 'Poke' => t('Poke'), + 'Chat' => t('Chat'), + 'Search' => t('Search'), + 'Probe' => t('Probe'), + 'Suggest' => t('Suggest'), + 'Random Channel' => t('Random Channel'), + 'Invite' => t('Invite'), + 'Features' => t('Features'), + 'Language' => t('Language'), + 'Post' => t('Post'), + 'Profile Photo' => t('Profile Photo') + ); + + if(array_key_exists($arr['name'],$apps)) + $arr['name'] = $apps[$arr['name']]; + + } + + + // papp is a portable app + + static public function app_render($papp,$mode = 'view') { + + /** + * modes: + * view: normal mode for viewing an app via bbcode from a conversation or page + * provides install/update button if you're logged in locally + * list: normal mode for viewing an app on the app page + * no buttons are shown + * edit: viewing the app page in editing mode provides a delete button + */ + + $installed = false; + + if(! $papp) + return; + + if(! $papp['photo']) + $papp['photo'] = z_root() . '/' . get_default_profile_photo(80); + + self::translate_system_apps($papp); + + $papp['papp'] = self::papp_encode($papp); + + if(! strstr($papp['url'],'://')) + $papp['url'] = z_root() . ((strpos($papp['url'],'/') === 0) ? '' : '/') . $papp['url']; + + foreach($papp as $k => $v) { + if(strpos($v,'http') === 0 && $k != 'papp') + $papp[$k] = zid($v); + if($k === 'desc') + $papp['desc'] = str_replace(array('\'','"'),array(''','&dquot;'),$papp['desc']); + + if($k === 'requires') { + $requires = explode(',',$v); + foreach($requires as $require) { + $require = trim(strtolower($require)); + switch($require) { + case 'nologin': + if(local_channel()) + return ''; + break; + case 'admin': + if(! is_site_admin()) + return ''; + break; + case 'local_channel': + if(! local_channel()) + return ''; + break; + case 'public_profile': + if(! is_public_profile()) + return ''; + break; + case 'observer': + $observer = \App::get_observer(); + if(! $observer) + return ''; + break; + default: + if(! (local_channel() && feature_enabled(local_channel(),$require))) + return ''; + break; + + } + } + } + } + + $hosturl = ''; + + if(local_channel()) { + $installed = self::app_installed(local_channel(),$papp); + $hosturl = z_root() . '/'; + } + elseif(remote_channel()) { + $observer = \App::get_observer(); + if($observer && $observer['xchan_network'] === 'zot') { + // some folks might have xchan_url redirected offsite, use the connurl + $x = parse_url($observer['xchan_connurl']); + if($x) { + $hosturl = $x['scheme'] . '://' . $x['host'] . '/'; + } + } + } + + $install_action = (($installed) ? t('Update') : t('Install')); + + return replace_macros(get_markup_template('app.tpl'),array( + '$app' => $papp, + '$hosturl' => $hosturl, + '$purchase' => (($papp['page'] && (! $installed)) ? t('Purchase') : ''), + '$install' => (($hosturl && $mode == 'view') ? $install_action : ''), + '$edit' => ((local_channel() && $installed && $mode == 'edit') ? t('Edit') : ''), + '$delete' => ((local_channel() && $installed && $mode == 'edit') ? t('Delete') : '') + )); + } + + static public function app_install($uid,$app) { + $app['uid'] = $uid; + + if(self::app_installed($uid,$app)) + $x = self::app_update($app); + else + $x = self::app_store($app); + + if($x['success']) { + $r = q("select * from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($x['app_id']), + intval($uid) + ); + if($r) { + if(! $r[0]['app_system']) { + if($app['categories'] && (! $app['term'])) { + $r[0]['term'] = q("select * from term where otype = %d and oid = d", + intval(TERM_OBJ_APP), + intval($r[0]['id']) + ); + build_sync_packet($uid,array('app' => $r[0])); + } + } + } + return $x['app_id']; + } + return false; + } + + static public function app_destroy($uid,$app) { + + + if($uid && $app['guid']) { + + $x = q("select * from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($app['guid']), + intval($uid) + ); + if($x) { + $x[0]['app_deleted'] = 1; + q("delete from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($x[0]['id']) + ); + if($x[0]['app_system']) { + $r = q("update app set app_deleted = 1 where app_id = '%s' and app_channel = %d", + dbesc($app['guid']), + intval($uid) + ); + } + else { + $r = q("delete from app where app_id = '%s' and app_channel = %d", + dbesc($app['guid']), + intval($uid) + ); + + // we don't sync system apps - they may be completely different on the other system + build_sync_packet($uid,array('app' => $x)); + } + } + } + } + + + static public function app_installed($uid,$app) { + + $r = q("select id from app where app_id = '%s' and app_version = '%s' and app_channel = %d limit 1", + dbesc((array_key_exists('guid',$app)) ? $app['guid'] : ''), + dbesc((array_key_exists('version',$app)) ? $app['version'] : ''), + intval($uid) + ); + return(($r) ? true : false); + + } + + + static public function app_list($uid, $deleted = false, $cat = '') { + if($deleted) + $sql_extra = " and app_deleted = 1 "; + else + $sql_extra = " and app_deleted = 0 "; + + if($cat) { + $r = q("select oid from term where otype = %d and term = '%s'", + intval(TERM_OBJ_APP), + dbesc($cat) + ); + if(! $r) + return $r; + $sql_extra .= " and app.id in ( "; + $s = ''; + foreach($r as $rr) { + if($s) + $s .= ','; + $s .= intval($rr['oid']); + } + $sql_extra .= $s . ') '; + } + + $r = q("select * from app where app_channel = %d $sql_extra order by app_name asc", + intval($uid) + ); + if($r) { + for($x = 0; $x < count($r); $x ++) { + if(! $r[$x]['app_system']) + $r[$x]['type'] = 'personal'; + $r[$x]['term'] = q("select * from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($r[$x]['id']) + ); + } + } + return($r); + } + + + static public function app_decode($s) { + $x = base64_decode(str_replace(array('<br />',"\r","\n",' '),array('','','',''),$s)); + return json_decode($x,true); + } + + + static public function app_store($arr) { + + // logger('app_store: ' . print_r($arr,true)); + + $darray = array(); + $ret = array('success' => false); + + $darray['app_url'] = ((x($arr,'url')) ? $arr['url'] : ''); + $darray['app_channel'] = ((x($arr,'uid')) ? $arr['uid'] : 0); + + if((! $darray['app_url']) || (! $darray['app_channel'])) + return $ret; + + if($arr['photo'] && ! strstr($arr['photo'],z_root())) { + $x = import_xchan_photo($arr['photo'],get_observer_hash(),true); + $arr['photo'] = $x[1]; + } + + + $darray['app_id'] = ((x($arr,'guid')) ? $arr['guid'] : random_string(). '.' . \App::get_hostname()); + $darray['app_sig'] = ((x($arr,'sig')) ? $arr['sig'] : ''); + $darray['app_author'] = ((x($arr,'author')) ? $arr['author'] : get_observer_hash()); + $darray['app_name'] = ((x($arr,'name')) ? escape_tags($arr['name']) : t('Unknown')); + $darray['app_desc'] = ((x($arr,'desc')) ? escape_tags($arr['desc']) : ''); + $darray['app_photo'] = ((x($arr,'photo')) ? $arr['photo'] : z_root() . '/' . get_default_profile_photo(80)); + $darray['app_version'] = ((x($arr,'version')) ? escape_tags($arr['version']) : ''); + $darray['app_addr'] = ((x($arr,'addr')) ? escape_tags($arr['addr']) : ''); + $darray['app_price'] = ((x($arr,'price')) ? escape_tags($arr['price']) : ''); + $darray['app_page'] = ((x($arr,'page')) ? escape_tags($arr['page']) : ''); + $darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : ''); + $darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0); + $darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0); + + $created = datetime_convert(); + + $r = q("insert into app ( app_id, app_sig, app_author, app_name, app_desc, app_url, app_photo, app_version, app_channel, app_addr, app_price, app_page, app_requires, app_created, app_edited, app_system, app_deleted ) values ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, %d )", + dbesc($darray['app_id']), + dbesc($darray['app_sig']), + dbesc($darray['app_author']), + dbesc($darray['app_name']), + dbesc($darray['app_desc']), + dbesc($darray['app_url']), + dbesc($darray['app_photo']), + dbesc($darray['app_version']), + intval($darray['app_channel']), + dbesc($darray['app_addr']), + dbesc($darray['app_price']), + dbesc($darray['app_page']), + dbesc($darray['app_requires']), + dbesc($created), + dbesc($created), + intval($darray['app_system']), + intval($darray['app_deleted']) + ); + if($r) { + $ret['success'] = true; + $ret['app_id'] = $darray['app_id']; + } + if($arr['categories']) { + $x = q("select id from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($darray['app_id']), + intval($darray['app_channel']) + ); + $y = explode(',',$arr['categories']); + if($y) { + foreach($y as $t) { + $t = trim($t); + if($t) { + store_item_tag($darray['app_channel'],$x[0]['id'],TERM_OBJ_APP,TERM_CATEGORY,escape_tags($t),escape_tags(z_root() . '/apps/?f=&cat=' . escape_tags($t))); + } + } + } + } + + return $ret; + } + + + static public function app_update($arr) { + + $darray = array(); + $ret = array('success' => false); + + $darray['app_url'] = ((x($arr,'url')) ? $arr['url'] : ''); + $darray['app_channel'] = ((x($arr,'uid')) ? $arr['uid'] : 0); + $darray['app_id'] = ((x($arr,'guid')) ? $arr['guid'] : 0); + + if((! $darray['app_url']) || (! $darray['app_channel']) || (! $darray['app_id'])) + return $ret; + + if($arr['photo'] && ! strstr($arr['photo'],z_root())) { + $x = import_xchan_photo($arr['photo'],get_observer_hash(),true); + $arr['photo'] = $x[1]; + } + + $darray['app_sig'] = ((x($arr,'sig')) ? $arr['sig'] : ''); + $darray['app_author'] = ((x($arr,'author')) ? $arr['author'] : get_observer_hash()); + $darray['app_name'] = ((x($arr,'name')) ? escape_tags($arr['name']) : t('Unknown')); + $darray['app_desc'] = ((x($arr,'desc')) ? escape_tags($arr['desc']) : ''); + $darray['app_photo'] = ((x($arr,'photo')) ? $arr['photo'] : z_root() . '/' . get_default_profile_photo(80)); + $darray['app_version'] = ((x($arr,'version')) ? escape_tags($arr['version']) : ''); + $darray['app_addr'] = ((x($arr,'addr')) ? escape_tags($arr['addr']) : ''); + $darray['app_price'] = ((x($arr,'price')) ? escape_tags($arr['price']) : ''); + $darray['app_page'] = ((x($arr,'page')) ? escape_tags($arr['page']) : ''); + $darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : ''); + $darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0); + $darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0); + + $edited = datetime_convert(); + + $r = q("update app set app_sig = '%s', app_author = '%s', app_name = '%s', app_desc = '%s', app_url = '%s', app_photo = '%s', app_version = '%s', app_addr = '%s', app_price = '%s', app_page = '%s', app_requires = '%s', app_edited = '%s', app_system = %d, app_deleted = %d where app_id = '%s' and app_channel = %d", + dbesc($darray['app_sig']), + dbesc($darray['app_author']), + dbesc($darray['app_name']), + dbesc($darray['app_desc']), + dbesc($darray['app_url']), + dbesc($darray['app_photo']), + dbesc($darray['app_version']), + dbesc($darray['app_addr']), + dbesc($darray['app_price']), + dbesc($darray['app_page']), + dbesc($darray['app_requires']), + dbesc($edited), + intval($darray['app_system']), + intval($darray['app_deleted']), + dbesc($darray['app_id']), + intval($darray['app_channel']) + ); + if($r) { + $ret['success'] = true; + $ret['app_id'] = $darray['app_id']; + } + + $x = q("select id from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($darray['app_id']), + intval($darray['app_channel']) + ); + if($x) { + q("delete from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($x[0]['id']) + ); + if($arr['categories']) { + $y = explode(',',$arr['categories']); + if($y) { + foreach($y as $t) { + $t = trim($t); + if($t) { + store_item_tag($darray['app_channel'],$x[0]['id'],TERM_OBJ_APP,TERM_CATEGORY,escape_tags($t),escape_tags(z_root() . '/apps/?f=&cat=' . escape_tags($t))); + } + } + } + } + } + + return $ret; + + } + + + static public function app_encode($app,$embed = false) { + + $ret = array(); + + $ret['type'] = 'personal'; + + if($app['app_id']) + $ret['guid'] = $app['app_id']; + + if($app['app_id']) + $ret['guid'] = $app['app_id']; + + if($app['app_sig']) + $ret['sig'] = $app['app_sig']; + + if($app['app_author']) + $ret['author'] = $app['app_author']; + + if($app['app_name']) + $ret['name'] = $app['app_name']; + + if($app['app_desc']) + $ret['desc'] = $app['app_desc']; + + if($app['app_url']) + $ret['url'] = $app['app_url']; + + if($app['app_photo']) + $ret['photo'] = $app['app_photo']; + + if($app['app_version']) + $ret['version'] = $app['app_version']; + + if($app['app_addr']) + $ret['addr'] = $app['app_addr']; + + if($app['app_price']) + $ret['price'] = $app['app_price']; + + if($app['app_page']) + $ret['page'] = $app['app_page']; + + if($app['app_requires']) + $ret['requires'] = $app['app_requires']; + + if($app['app_system']) + $ret['system'] = $app['app_system']; + + if($app['app_deleted']) + $ret['deleted'] = $app['app_deleted']; + + if($app['term']) { + $s = ''; + foreach($app['term'] as $t) { + if($s) + $s .= ','; + $s .= $t['term']; + } + $ret['categories'] = $s; + } + + + if(! $embed) + return $ret; + + if(array_key_exists('categories',$ret)) + unset($ret['categories']); + + $j = json_encode($ret); + return '[app]' . chunk_split(base64_encode($j),72,"\n") . '[/app]'; + + } + + + static public function papp_encode($papp) { + return chunk_split(base64_encode(json_encode($papp)),72,"\n"); + + } + +} + + diff --git a/Zotlabs/Lib/Chatroom.php b/Zotlabs/Lib/Chatroom.php new file mode 100644 index 000000000..e1a9a10b3 --- /dev/null +++ b/Zotlabs/Lib/Chatroom.php @@ -0,0 +1,267 @@ +<?php +namespace Zotlabs\Lib; + +/** + * @brief Chat related functions. + */ + + + +class Chatroom { + /** + * @brief Creates a chatroom. + * + * @param array $channel + * @param array $arr + * @return An associative array containing: + * - success: A boolean + * - message: (optional) A string + */ + + static public function create($channel, $arr) { + + $ret = array('success' => false); + + $name = trim($arr['name']); + if(! $name) { + $ret['message'] = t('Missing room name'); + return $ret; + } + + $r = q("select cr_id from chatroom where cr_uid = %d and cr_name = '%s' limit 1", + intval($channel['channel_id']), + dbesc($name) + ); + if($r) { + $ret['message'] = t('Duplicate room name'); + return $ret; + } + + $r = q("select count(cr_id) as total from chatroom where cr_aid = %d", + intval($channel['channel_account_id']) + ); + if($r) + $limit = service_class_fetch($channel['channel_id'], 'chatrooms'); + + if(($r) && ($limit !== false) && ($r[0]['total'] >= $limit)) { + $ret['message'] = upgrade_message(); + return $ret; + } + + if(! array_key_exists('expire', $arr)) + $arr['expire'] = 120; // minutes, e.g. 2 hours + + $created = datetime_convert(); + + $x = q("insert into chatroom ( cr_aid, cr_uid, cr_name, cr_created, cr_edited, cr_expire, allow_cid, allow_gid, deny_cid, deny_gid ) + values ( %d, %d , '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s' ) ", + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc($name), + dbesc($created), + dbesc($created), + intval($arr['expire']), + dbesc($arr['allow_cid']), + dbesc($arr['allow_gid']), + dbesc($arr['deny_cid']), + dbesc($arr['deny_gid']) + ); + + if($x) + $ret['success'] = true; + + return $ret; + } + + + static public function destroy($channel,$arr) { + + $ret = array('success' => false); + + if(intval($arr['cr_id'])) + $sql_extra = " and cr_id = " . intval($arr['cr_id']) . " "; + elseif(trim($arr['cr_name'])) + $sql_extra = " and cr_name = '" . protect_sprintf(dbesc(trim($arr['cr_name']))) . "' "; + else { + $ret['message'] = t('Invalid room specifier.'); + return $ret; + } + + $r = q("select * from chatroom where cr_uid = %d $sql_extra limit 1", + intval($channel['channel_id']) + ); + if(! $r) { + $ret['message'] = t('Invalid room specifier.'); + return $ret; + } + + build_sync_packet($channel['channel_id'],array('chatroom' => $r)); + + q("delete from chatroom where cr_id = %d", + intval($r[0]['cr_id']) + ); + if($r[0]['cr_id']) { + q("delete from chatpresence where cp_room = %d", + intval($r[0]['cr_id']) + ); + q("delete from chat where chat_room = %d", + intval($r[0]['cr_id']) + ); + } + + $ret['success'] = true; + return $ret; + } + + + static public function enter($observer_xchan, $room_id, $status, $client) { + + if(! $room_id || ! $observer_xchan) + return; + + $r = q("select * from chatroom where cr_id = %d limit 1", + intval($room_id) + ); + if(! $r) { + notice( t('Room not found.') . EOL); + return false; + } + require_once('include/security.php'); + $sql_extra = permissions_sql($r[0]['cr_uid']); + + $x = q("select * from chatroom where cr_id = %d and cr_uid = %d $sql_extra limit 1", + intval($room_id), + intval($r[0]['cr_uid']) + ); + if(! $x) { + notice( t('Permission denied.') . EOL); + return false; + } + + $limit = service_class_fetch($r[0]['cr_uid'], 'chatters_inroom'); + if($limit !== false) { + $y = q("select count(*) as total from chatpresence where cp_room = %d", + intval($room_id) + ); + if($y && $y[0]['total'] > $limit) { + notice( t('Room is full') . EOL); + return false; + } + } + + if(intval($x[0]['cr_expire'])) { + $r = q("delete from chat where created < %s - INTERVAL %s and chat_room = %d", + db_utcnow(), + db_quoteinterval( intval($x[0]['cr_expire']) . ' MINUTE' ), + intval($x[0]['cr_id']) + ); + } + + $r = q("select * from chatpresence where cp_xchan = '%s' and cp_room = %d limit 1", + dbesc($observer_xchan), + intval($room_id) + ); + if($r) { + q("update chatpresence set cp_last = '%s' where cp_id = %d and cp_client = '%s'", + dbesc(datetime_convert()), + intval($r[0]['cp_id']), + dbesc($client) + ); + return true; + } + + $r = q("insert into chatpresence ( cp_room, cp_xchan, cp_last, cp_status, cp_client ) + values ( %d, '%s', '%s', '%s', '%s' )", + intval($room_id), + dbesc($observer_xchan), + dbesc(datetime_convert()), + dbesc($status), + dbesc($client) + ); + + return $r; + } + + + function leave($observer_xchan, $room_id, $client) { + if(! $room_id || ! $observer_xchan) + return; + + $r = q("select * from chatpresence where cp_xchan = '%s' and cp_room = %d and cp_client = '%s' limit 1", + dbesc($observer_xchan), + intval($room_id), + dbesc($client) + ); + if($r) { + q("delete from chatpresence where cp_id = %d", + intval($r[0]['cp_id']) + ); + } + + return true; + } + + + static public function roomlist($uid) { + require_once('include/security.php'); + $sql_extra = permissions_sql($uid); + + $r = q("select allow_cid, allow_gid, deny_cid, deny_gid, cr_name, cr_expire, cr_id, count(cp_id) as cr_inroom from chatroom left join chatpresence on cr_id = cp_room where cr_uid = %d $sql_extra group by cr_name, cr_id order by cr_name", + intval($uid) + ); + + return $r; + } + + static public function list_count($uid) { + require_once('include/security.php'); + $sql_extra = permissions_sql($uid); + + $r = q("select count(*) as total from chatroom where cr_uid = %d $sql_extra", + intval($uid) + ); + + return $r[0]['total']; + } + + /** + * create a chat message via API. + * It is the caller's responsibility to enter the room. + */ + + static public function message($uid, $room_id, $xchan, $text) { + + $ret = array('success' => false); + + if(! $text) + return; + + $sql_extra = permissions_sql($uid); + + $r = q("select * from chatroom where cr_uid = %d and cr_id = %d $sql_extra", + intval($uid), + intval($room_id) + ); + if(! $r) + return $ret; + + $arr = array( + 'chat_room' => $room_id, + 'chat_xchan' => $xchan, + 'chat_text' => $text + ); + + call_hooks('chat_message', $arr); + + $x = q("insert into chat ( chat_room, chat_xchan, created, chat_text ) + values( %d, '%s', '%s', '%s' )", + intval($room_id), + dbesc($xchan), + dbesc(datetime_convert()), + dbesc($arr['chat_text']) + ); + + $ret['success'] = true; + return $ret; + } +} diff --git a/Zotlabs/Lib/Config.php b/Zotlabs/Lib/Config.php new file mode 100644 index 000000000..d4ee1aeda --- /dev/null +++ b/Zotlabs/Lib/Config.php @@ -0,0 +1,166 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + + +class Config { + + /** + * @brief Loads the hub's configuration from database to a cached storage. + * + * Retrieve a category ($family) of config variables from database to a cached + * storage in the global App::$config[$family]. + * + * @param string $family + * The category of the configuration value + */ + + static public function Load($family) { + if(! array_key_exists($family, \App::$config)) + \App::$config[$family] = array(); + + if(! array_key_exists('config_loaded', \App::$config[$family])) { + $r = q("SELECT * FROM config WHERE cat = '%s'", dbesc($family)); + if($r !== false) { + if($r) { + foreach($r as $rr) { + $k = $rr['k']; + \App::$config[$family][$k] = $rr['v']; + } + } + \App::$config[$family]['config_loaded'] = true; + } + } + } + + /** + * @brief Sets a configuration value for the hub. + * + * Stores a config value ($value) in the category ($family) under the key ($key). + * + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to set + * @param mixed $value + * The value to store in the configuration + * @return mixed + * Return the set value, or false if the database update failed + */ + + static public function Set($family,$key,$value) { + // manage array value + $dbvalue = ((is_array($value)) ? serialize($value) : $value); + $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + + if(get_config($family, $key) === false || (! self::get_from_storage($family, $key))) { + $ret = q("INSERT INTO config ( cat, k, v ) VALUES ( '%s', '%s', '%s' ) ", + dbesc($family), + dbesc($key), + dbesc($dbvalue) + ); + if($ret) { + \App::$config[$family][$key] = $value; + $ret = $value; + } + return $ret; + } + + $ret = q("UPDATE config SET v = '%s' WHERE cat = '%s' AND k = '%s'", + dbesc($dbvalue), + dbesc($family), + dbesc($key) + ); + + if($ret) { + \App::$config[$family][$key] = $value; + $ret = $value; + } + return $ret; + + } + + /** + * @brief Get a particular config variable given the category name ($family) + * and a key. + * + * Get a particular config variable from the given category ($family) and the + * $key from a cached storage in App::$config[$family]. If a key is found in the + * DB but does not exist in local config cache, pull it into the cache so we + * do not have to hit the DB again for this item. + * + * Returns false if not set. + * + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to query + * @return mixed Return value or false on error or if not set + */ + + static public function Get($family,$key) { + if((! array_key_exists($family, \App::$config)) || (! array_key_exists('config_loaded', \App::$config[$family]))) + self::Load($family); + + if(array_key_exists('config_loaded', \App::$config[$family])) { + if(! array_key_exists($key, \App::$config[$family])) { + return false; + } + return ((! is_array(\App::$config[$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', \App::$config[$family][$key])) + ? unserialize(\App::$config[$family][$key]) + : \App::$config[$family][$key] + ); + } + + return false; + } + + /** + * @brief Deletes the given key from the hub's configuration database. + * + * Removes the configured value from the stored cache in App::$config[$family] + * and removes it from the database. + * + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to delete + * @return mixed + */ + + static public function Delete($family,$key) { + + $ret = false; + + if(array_key_exists($family, \App::$config) && array_key_exists($key, \App::$config[$family])) + unset(\App::$config[$family][$key]); + $ret = q("DELETE FROM config WHERE cat = '%s' AND k = '%s'", + dbesc($family), + dbesc($key) + ); + return $ret; + } + + + /** + * @brief Returns a value directly from the database configuration storage. + * + * This function queries directly the database and bypasses the chached storage + * from get_config($family, $key). + * + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to query + * @return mixed + */ + + static private function get_from_storage($family,$key) { + $ret = q("SELECT * FROM config WHERE cat = '%s' AND k = '%s' LIMIT 1", + dbesc($family), + dbesc($key) + ); + return $ret; + } + +} diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php new file mode 100644 index 000000000..56c717468 --- /dev/null +++ b/Zotlabs/Lib/Enotify.php @@ -0,0 +1,685 @@ +<?php + +namespace Zotlabs\Lib; + +/** + * @brief File with functions and a class for generating system and email notifications. + */ + + +class Enotify { + + /** + * @brief + * + * @param array $params an assoziative array with: + * * \e string \b from_xchan sender xchan hash + * * \e string \b to_xchan recipient xchan hash + * * \e array \b item an assoziative array + * * \e int \b type one of the NOTIFY_* constants from boot.php + * * \e string \b link + * * \e string \b parent_mid + * * \e string \b otype + * * \e string \b verb + * * \e string \b activity + */ + + + static public function submit($params) { + + logger('notification: entry', LOGGER_DEBUG); + + // throw a small amount of entropy into the system to breakup duplicates arriving at the same precise instant. + usleep(mt_rand(0, 10000)); + + if ($params['from_xchan']) { + $x = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($params['from_xchan']) + ); + } + if ($params['to_xchan']) { + $y = q("select channel.*, account.* from channel left join account on channel_account_id = account_id + where channel_hash = '%s' and channel_removed = 0 limit 1", + dbesc($params['to_xchan']) + ); + } + if ($x & $y) { + $sender = $x[0]; + $recip = $y[0]; + } else { + logger('notification: no sender or recipient.'); + logger('sender: ' . $params['from_xchan']); + logger('recip: ' . $params['to_xchan']); + return; + } + + // from here on everything is in the recipients language + + push_lang($recip['account_language']); // should probably have a channel language + + $banner = t('$Projectname Notification'); + $product = t('$projectname'); // PLATFORM_NAME; + $siteurl = z_root(); + $thanks = t('Thank You,'); + $sitename = get_config('system','sitename'); + $site_admin = sprintf( t('%s Administrator'), $sitename); + + $sender_name = $product; + $hostname = \App::get_hostname(); + if(strpos($hostname,':')) + $hostname = substr($hostname,0,strpos($hostname,':')); + + // Do not translate 'noreply' as it must be a legal 7-bit email address + $sender_email = 'noreply' . '@' . $hostname; + + $additional_mail_header = ""; + + if(array_key_exists('item', $params)) { + require_once('include/conversation.php'); + // if it's a normal item... + if (array_key_exists('verb', $params['item'])) { + // localize_item() alters the original item so make a copy first + $i = $params['item']; + logger('calling localize'); + localize_item($i); + $title = $i['title']; + $body = $i['body']; + $private = (($i['item_private']) || intval($i['item_obscured'])); + } + else { + $title = $params['item']['title']; + $body = $params['item']['body']; + } + } + else { + $title = $body = ''; + } + + + // e.g. "your post", "David's photo", etc. + $possess_desc = t('%s <!item_type!>'); + + if ($params['type'] == NOTIFY_MAIL) { + logger('notification: mail'); + $subject = sprintf( t('[Hubzilla:Notify] New mail received at %s'),$sitename); + + $preamble = sprintf( t('%1$s, %2$s sent you a new private message at %3$s.'),$recip['channel_name'], $sender['xchan_name'],$sitename); + $epreamble = sprintf( t('%1$s sent you %2$s.'),'[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', '[zrl=$itemlink]' . t('a private message') . '[/zrl]'); + $sitelink = t('Please visit %s to view and/or reply to your private messages.'); + $tsitelink = sprintf( $sitelink, $siteurl . '/mail/' . $params['item']['id'] ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/mail/' . $params['item']['id'] . '">' . $sitename . '</a>'); + $itemlink = $siteurl . '/mail/' . $params['item']['id']; + } + + if ($params['type'] == NOTIFY_COMMENT) { +// logger("notification: params = " . print_r($params, true), LOGGER_DEBUG); + + $itemlink = $params['link']; + + // ignore like/unlike activity on posts - they probably require a sepearate notification preference + + if (array_key_exists('item',$params) && (! visible_activity($params['item']))) + return; + + $parent_mid = $params['parent_mid']; + + // Check to see if there was already a notify for this post. + // If so don't create a second notification + + $p = null; + $p = q("select id from notify where link = '%s' and uid = %d limit 1", + dbesc($params['link']), + intval($recip['channel_id']) + ); + if ($p) { + logger('notification: comment already notified'); + pop_lang(); + return; + } + + + // if it's a post figure out who's post it is. + + $p = null; + + if($params['otype'] === 'item' && $parent_mid) { + $p = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($parent_mid), + intval($recip['channel_id']) + ); + } + + xchan_query($p); + + + $item_post_type = item_post_type($p[0]); +// $private = $p[0]['item_private']; + $parent_id = $p[0]['id']; + + $parent_item = $p[0]; + + //$possess_desc = str_replace('<!item_type!>',$possess_desc); + + // "a post" + $dest_str = sprintf(t('%1$s, %2$s commented on [zrl=%3$s]a %4$s[/zrl]'), + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $itemlink, + $item_post_type); + + // "George Bull's post" + if($p) + $dest_str = sprintf(t('%1$s, %2$s commented on [zrl=%3$s]%4$s\'s %5$s[/zrl]'), + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $itemlink, + $p[0]['author']['xchan_name'], + $item_post_type); + + // "your post" + if($p[0]['owner']['xchan_name'] == $p[0]['author']['xchan_name'] && intval($p[0]['item_wall'])) + $dest_str = sprintf(t('%1$s, %2$s commented on [zrl=%3$s]your %4$s[/zrl]'), + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $itemlink, + $item_post_type); + + // Some mail softwares relies on subject field for threading. + // So, we cannot have different subjects for notifications of the same thread. + // Before this we have the name of the replier on the subject rendering + // differents subjects for messages on the same thread. + + $subject = sprintf( t('[Hubzilla:Notify] Comment to conversation #%1$d by %2$s'), $parent_id, $sender['xchan_name']); + $preamble = sprintf( t('%1$s, %2$s commented on an item/conversation you have been following.'), $recip['channel_name'], $sender['xchan_name']); + $epreamble = $dest_str; + + $sitelink = t('Please visit %s to view and/or reply to the conversation.'); + $tsitelink = sprintf( $sitelink, $siteurl ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); + } + + if($params['type'] == NOTIFY_WALL) { + $subject = sprintf( t('[Hubzilla:Notify] %s posted to your profile wall') , $sender['xchan_name']); + + $preamble = sprintf( t('%1$s, %2$s posted to your profile wall at %3$s') , $recip['channel_name'], $sender['xchan_name'], $sitename); + + $epreamble = sprintf( t('%1$s, %2$s posted to [zrl=%3$s]your wall[/zrl]') , + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $params['link']); + + $sitelink = t('Please visit %s to view and/or reply to the conversation.'); + $tsitelink = sprintf( $sitelink, $siteurl ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); + $itemlink = $params['link']; + } + + if ($params['type'] == NOTIFY_TAGSELF) { + + $p = null; + $p = q("select id from notify where link = '%s' and uid = %d limit 1", + dbesc($params['link']), + intval($recip['channel_id']) + ); + if ($p) { + logger('enotify: tag: already notified about this post'); + pop_lang(); + return; + } + + $subject = sprintf( t('[Hubzilla:Notify] %s tagged you') , $sender['xchan_name']); + $preamble = sprintf( t('%1$s, %2$s tagged you at %3$s') , $recip['channel_name'], $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, %2$s [zrl=%3$s]tagged you[/zrl].') , + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $params['link']); + + $sitelink = t('Please visit %s to view and/or reply to the conversation.'); + $tsitelink = sprintf( $sitelink, $siteurl ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); + $itemlink = $params['link']; + } + + if ($params['type'] == NOTIFY_POKE) { + $subject = sprintf( t('[Hubzilla:Notify] %1$s poked you') , $sender['xchan_name']); + $preamble = sprintf( t('%1$s, %2$s poked you at %3$s') , $recip['channel_name'], $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, %2$s [zrl=%2$s]poked you[/zrl].') , + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $params['link']); + + $subject = str_replace('poked', t($params['activity']), $subject); + $preamble = str_replace('poked', t($params['activity']), $preamble); + $epreamble = str_replace('poked', t($params['activity']), $epreamble); + + $sitelink = t('Please visit %s to view and/or reply to the conversation.'); + $tsitelink = sprintf( $sitelink, $siteurl ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); + $itemlink = $params['link']; + } + + if ($params['type'] == NOTIFY_TAGSHARE) { + $subject = sprintf( t('[Hubzilla:Notify] %s tagged your post') , $sender['xchan_name']); + $preamble = sprintf( t('%1$s, %2$s tagged your post at %3$s') , $recip['channel_name'],$sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, %2$s tagged [zrl=%3$s]your post[/zrl]') , + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $itemlink); + + $sitelink = t('Please visit %s to view and/or reply to the conversation.'); + $tsitelink = sprintf( $sitelink, $siteurl ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); + $itemlink = $params['link']; + } + + if ($params['type'] == NOTIFY_INTRO) { + $subject = sprintf( t('[Hubzilla:Notify] Introduction received')); + $preamble = sprintf( t('%1$s, you\'ve received an new connection request from \'%2$s\' at %3$s'), $recip['channel_name'], $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, you\'ve received [zrl=%2$s]a new connection request[/zrl] from %3$s.'), + $recip['channel_name'], + $siteurl . '/connections/ifpending', + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]'); + $body = sprintf( t('You may visit their profile at %s'),$sender['xchan_url']); + + $sitelink = t('Please visit %s to approve or reject the connection request.'); + $tsitelink = sprintf( $sitelink, $siteurl . '/connections/ifpending'); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/connections/ifpending">' . $sitename . '</a>'); + $itemlink = $params['link']; + } + + if ($params['type'] == NOTIFY_SUGGEST) { + $subject = sprintf( t('[Hubzilla:Notify] Friend suggestion received')); + $preamble = sprintf( t('%1$s, you\'ve received a friend suggestion from \'%2$s\' at %3$s'), $recip['channel_name'], $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, you\'ve received [zrl=%2$s]a friend suggestion[/zrl] for %3$s from %4$s.'), + $recip['channel_name'], + $itemlink, + '[zrl=' . $params['item']['url'] . ']' . $params['item']['name'] . '[/zrl]', + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]'); + + $body = t('Name:') . ' ' . $params['item']['name'] . "\n"; + $body .= t('Photo:') . ' ' . $params['item']['photo'] . "\n"; + $body .= sprintf( t('You may visit their profile at %s'),$params['item']['url']); + + $sitelink = t('Please visit %s to approve or reject the suggestion.'); + $tsitelink = sprintf( $sitelink, $siteurl ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); + $itemlink = $params['link']; + } + + if ($params['type'] == NOTIFY_CONFIRM) { + // ? + } + + if ($params['type'] == NOTIFY_SYSTEM) { + // ? + } + + $h = array( + 'params' => $params, + 'subject' => $subject, + 'preamble' => $preamble, + 'epreamble' => $epreamble, + 'body' => $body, + 'sitelink' => $sitelink, + 'sitename' => $sitename, + 'tsitelink' => $tsitelink, + 'hsitelink' => $hsitelink, + 'itemlink' => $itemlink, + 'sender' => $sender, + 'recipient' => $recip + ); + + call_hooks('enotify', $h); + + $subject = $h['subject']; + $preamble = $h['preamble']; + $epreamble = $h['epreamble']; + $body = $h['body']; + $sitelink = $h['sitelink']; + $tsitelink = $h['tsitelink']; + $hsitelink = $h['hsitelink']; + $itemlink = $h['itemlink']; + + + require_once('include/html2bbcode.php'); + + do { + $dups = false; + $hash = random_string(); + $r = q("SELECT `id` FROM `notify` WHERE `hash` = '%s' LIMIT 1", + dbesc($hash)); + if ($r) + $dups = true; + } while ($dups === true); + + + $datarray = array(); + $datarray['hash'] = $hash; + $datarray['sender_hash'] = $sender['xchan_hash']; + $datarray['xname'] = $sender['xchan_name']; + $datarray['url'] = $sender['xchan_url']; + $datarray['photo'] = $sender['xchan_photo_s']; + $datarray['created'] = datetime_convert(); + $datarray['aid'] = $recip['channel_account_id']; + $datarray['uid'] = $recip['channel_id']; + $datarray['link'] = $itemlink; + $datarray['parent'] = $parent_mid; + $datarray['parent_item'] = $parent_item; + $datarray['ntype'] = $params['type']; + $datarray['verb'] = $params['verb']; + $datarray['otype'] = $params['otype']; + $datarray['abort'] = false; + + $datarray['item'] = $params['item']; + + call_hooks('enotify_store', $datarray); + + if ($datarray['abort']) { + pop_lang(); + return; + } + + + // create notification entry in DB + $seen = 0; + + // Mark some notifications as seen right away + // Note! The notification have to be created, because they are used to send emails + // So easiest solution to hide them from Notices is to mark them as seen right away. + // Another option would be to not add them to the DB, and change how emails are handled (probably would be better that way) + $always_show_in_notices = get_pconfig($recip['channel_id'],'system','always_show_in_notices'); + if (!$always_show_in_notices) { + if (($params['type'] == NOTIFY_WALL) || ($params['type'] == NOTIFY_MAIL) || ($params['type'] == NOTIFY_INTRO)) { + $seen = 1; + } + } + + $r = q("insert into notify (hash,xname,url,photo,created,aid,uid,link,parent,seen,ntype,verb,otype) + values('%s','%s','%s','%s','%s',%d,%d,'%s','%s',%d,%d,'%s','%s')", + dbesc($datarray['hash']), + dbesc($datarray['xname']), + dbesc($datarray['url']), + dbesc($datarray['photo']), + dbesc($datarray['created']), + intval($datarray['aid']), + intval($datarray['uid']), + dbesc($datarray['link']), + dbesc($datarray['parent']), + intval($seen), + intval($datarray['ntype']), + dbesc($datarray['verb']), + dbesc($datarray['otype']) + ); + + $r = q("select id from notify where hash = '%s' and uid = %d limit 1", + dbesc($hash), + intval($recip['channel_id']) + ); + if ($r) { + $notify_id = $r[0]['id']; + } else { + logger('notification not found.'); + pop_lang(); + return; + } + + $itemlink = z_root() . '/notify/view/' . $notify_id; + $msg = str_replace('$itemlink',$itemlink,$epreamble); + + // wretched hack, but we don't want to duplicate all the preamble variations and we also don't want to screw up a translation + + if ((\App::$language === 'en' || (! \App::$language)) && strpos($msg,', ')) + $msg = substr($msg,strpos($msg,', ')+1); + + $r = q("update notify set msg = '%s' where id = %d and uid = %d", + dbesc($msg), + intval($notify_id), + intval($datarray['uid']) + ); + + // send email notification if notification preferences permit + + require_once('bbcode.php'); + if ((intval($recip['channel_notifyflags']) & intval($params['type'])) || $params['type'] == NOTIFY_SYSTEM) { + + logger('notification: sending notification email'); + + $hn = get_pconfig($recip['channel_id'],'system','email_notify_host'); + if($hn && (! stristr(\App::get_hostname(),$hn))) { + // this isn't the email notification host + pop_lang(); + return; + } + + $textversion = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r", "\\n"), array( "", "\n"), $body))),ENT_QUOTES,'UTF-8')); + + $htmlversion = bbcode(stripslashes(str_replace(array("\\r","\\n"), array("","<br />\n"),$body))); + + + // use $_SESSION['zid_override'] to force zid() to use + // the recipient address instead of the current observer + + $_SESSION['zid_override'] = $recip['channel_address'] . '@' . \App::get_hostname(); + $_SESSION['zrl_override'] = z_root() . '/channel/' . $recip['channel_address']; + + $textversion = zidify_links($textversion); + $htmlversion = zidify_links($htmlversion); + + // unset when done to revert to normal behaviour + + unset($_SESSION['zid_override']); + unset($_SESSION['zrl_override']); + + $datarray = array(); + $datarray['banner'] = $banner; + $datarray['product'] = $product; + $datarray['preamble'] = $preamble; + $datarray['sitename'] = $sitename; + $datarray['siteurl'] = $siteurl; + $datarray['type'] = $params['type']; + $datarray['parent'] = $params['parent_mid']; + $datarray['source_name'] = $sender['xchan_name']; + $datarray['source_link'] = $sender['xchan_url']; + $datarray['source_photo'] = $sender['xchan_photo_s']; + $datarray['uid'] = $recip['channel_id']; + $datarray['username'] = $recip['channel_name']; + $datarray['hsitelink'] = $hsitelink; + $datarray['tsitelink'] = $tsitelink; + $datarray['hitemlink'] = '<a href="' . $itemlink . '">' . $itemlink . '</a>'; + $datarray['titemlink'] = $itemlink; + $datarray['thanks'] = $thanks; + $datarray['site_admin'] = $site_admin; + $datarray['title'] = stripslashes($title); + $datarray['htmlversion'] = $htmlversion; + $datarray['textversion'] = $textversion; + $datarray['subject'] = $subject; + $datarray['headers'] = $additional_mail_header; + $datarray['email_secure'] = false; + + call_hooks('enotify_mail', $datarray); + + // Default to private - don't disclose message contents over insecure channels (such as email) + // Might be interesting to use GPG,PGP,S/MIME encryption instead + // but we'll save that for a clever plugin developer to implement + + $private_activity = false; + + if (! $datarray['email_secure']) { + switch ($params['type']) { + case NOTIFY_WALL: + case NOTIFY_TAGSELF: + case NOTIFY_POKE: + case NOTIFY_COMMENT: + if (! $private) + break; + $private_activity = true; + case NOTIFY_MAIL: + $datarray['textversion'] = $datarray['htmlversion'] = $datarray['title'] = ''; + $datarray['subject'] = preg_replace('/' . preg_quote(t('[Hubzilla:Notify]')) . '/','$0*',$datarray['subject']); + break; + default: + break; + } + } + + if ($private_activity + && intval(get_pconfig($datarray['uid'], 'system', 'ignore_private_notifications'))) { + + pop_lang(); + return; + } + + // load the template for private message notifications + $tpl = get_markup_template('email_notify_html.tpl'); + $email_html_body = replace_macros($tpl,array( + '$banner' => $datarray['banner'], + '$notify_icon' => \Zotlabs\Lib\System::get_notify_icon(), + '$product' => $datarray['product'], + '$preamble' => $datarray['preamble'], + '$sitename' => $datarray['sitename'], + '$siteurl' => $datarray['siteurl'], + '$source_name' => $datarray['source_name'], + '$source_link' => $datarray['source_link'], + '$source_photo' => $datarray['source_photo'], + '$username' => $datarray['to_name'], + '$hsitelink' => $datarray['hsitelink'], + '$hitemlink' => $datarray['hitemlink'], + '$thanks' => $datarray['thanks'], + '$site_admin' => $datarray['site_admin'], + '$title' => $datarray['title'], + '$htmlversion' => $datarray['htmlversion'], + )); + + // load the template for private message notifications + $tpl = get_markup_template('email_notify_text.tpl'); + $email_text_body = replace_macros($tpl, array( + '$banner' => $datarray['banner'], + '$product' => $datarray['product'], + '$preamble' => $datarray['preamble'], + '$sitename' => $datarray['sitename'], + '$siteurl' => $datarray['siteurl'], + '$source_name' => $datarray['source_name'], + '$source_link' => $datarray['source_link'], + '$source_photo' => $datarray['source_photo'], + '$username' => $datarray['to_name'], + '$tsitelink' => $datarray['tsitelink'], + '$titemlink' => $datarray['titemlink'], + '$thanks' => $datarray['thanks'], + '$site_admin' => $datarray['site_admin'], + '$title' => $datarray['title'], + '$textversion' => $datarray['textversion'], + )); + +// logger('text: ' . $email_text_body); + + // use the EmailNotification library to send the message + + self::send(array( + 'fromName' => $sender_name, + 'fromEmail' => $sender_email, + 'replyTo' => $sender_email, + 'toEmail' => $recip['account_email'], + 'messageSubject' => $datarray['subject'], + 'htmlVersion' => $email_html_body, + 'textVersion' => $email_text_body, + 'additionalMailHeader' => $datarray['headers'], + )); + } + + pop_lang(); + +} + + + /** + * @brief Send a multipart/alternative message with Text and HTML versions. + * + * @param array $params an assoziative array with: + * * \e string \b fromName name of the sender + * * \e string \b fromEmail email of the sender + * * \e string \b replyTo replyTo address to direct responses + * * \e string \b toEmail destination email address + * * \e string \b messageSubject subject of the message + * * \e string \b htmlVersion html version of the message + * * \e string \b textVersion text only version of the message + * * \e string \b additionalMailHeader additions to the smtp mail header + */ + static public function send($params) { + + $fromName = email_header_encode(html_entity_decode($params['fromName'],ENT_QUOTES,'UTF-8'),'UTF-8'); + $messageSubject = email_header_encode(html_entity_decode($params['messageSubject'],ENT_QUOTES,'UTF-8'),'UTF-8'); + + // generate a mime boundary + $mimeBoundary = rand(0, 9) . "-" + .rand(10000000000, 9999999999) . "-" + .rand(10000000000, 9999999999) . "=:" + .rand(10000, 99999); + + // generate a multipart/alternative message header + $messageHeader = + $params['additionalMailHeader'] . + "From: $fromName <{$params['fromEmail']}>\n" . + "Reply-To: $fromName <{$params['replyTo']}>\n" . + "MIME-Version: 1.0\n" . + "Content-Type: multipart/alternative; boundary=\"{$mimeBoundary}\""; + + // assemble the final multipart message body with the text and html types included + $textBody = chunk_split(base64_encode($params['textVersion'])); + $htmlBody = chunk_split(base64_encode($params['htmlVersion'])); + + $multipartMessageBody = + "--" . $mimeBoundary . "\n" . // plain text section + "Content-Type: text/plain; charset=UTF-8\n" . + "Content-Transfer-Encoding: base64\n\n" . + $textBody . "\n" . + "--" . $mimeBoundary . "\n" . // text/html section + "Content-Type: text/html; charset=UTF-8\n" . + "Content-Transfer-Encoding: base64\n\n" . + $htmlBody . "\n" . + "--" . $mimeBoundary . "--\n"; // message ending + + // send the message + $res = mail( + $params['toEmail'], // send to address + $messageSubject, // subject + $multipartMessageBody, // message body + $messageHeader // message headers + ); + logger("notification: enotify::send returns " . $res, LOGGER_DEBUG); + } + + static public function format($item) { + + $ret = ''; + + require_once('include/conversation.php'); + + // Call localize_item with the "brief" flag to get a one line status for activities. + // This should set $item['localized'] to indicate we have a brief summary. + + localize_item($item); + + if($item_localize) { + $itemem_text = $item['localize']; + } + else { + $itemem_text = (($item['item_thread_top']) + ? t('created a new post') + : sprintf( t('commented on %s\'s post'), $item['owner']['xchan_name'])); + } + + // convert this logic into a json array just like the system notifications + + return array( + 'notify_link' => $item['llink'], + 'name' => $item['author']['xchan_name'], + 'url' => $item['author']['xchan_url'], + 'photo' => $item['author']['xchan_photo_s'], + 'when' => relative_date($item['created']), + 'class' => (intval($item['item_unseen']) ? 'notify-unseen' : 'notify-seen'), + 'message' => strip_tags(bbcode($itemem_text)) + ); + + } + +} diff --git a/Zotlabs/Lib/IConfig.php b/Zotlabs/Lib/IConfig.php new file mode 100644 index 000000000..28c9ab58e --- /dev/null +++ b/Zotlabs/Lib/IConfig.php @@ -0,0 +1,165 @@ +<?php + +namespace Zotlabs\Lib; + + + +class IConfig { + + static public function Load(&$item) { + return; + } + + static public function Get(&$item, $family, $key) { + + $is_item = false; + + if(is_array($item)) { + $is_item = true; + if((! array_key_exists('iconfig',$item)) || (! is_array($item['iconfig']))) + $item['iconfig'] = array(); + + if(array_key_exists('item_id',$item)) + $iid = $item['item_id']; + else + $iid = $item['id']; + } + elseif(intval($item)) + $iid = $item; + + if(! $iid) + return false; + + if(is_array($item) && array_key_exists('iconfig',$item) && is_array($item['iconfig'])) { + foreach($item['iconfig'] as $c) { + if($c['iid'] == $iid && $c['cat'] == $family && $c['k'] == $key) + return $c['v']; + } + } + + $r = q("select * from iconfig where iid = %d and cat = '%s' and k = '%s' limit 1", + intval($iid), + dbesc($family), + dbesc($key) + ); + if($r) { + $r[0]['v'] = ((preg_match('|^a:[0-9]+:{.*}$|s',$r[0]['v'])) ? unserialize($r[0]['v']) : $r[0]['v']); + if($is_item) + $item['iconfig'][] = $r[0]; + return $r[0]['v']; + } + return false; + + } + + /** + * IConfig::Set(&$item, $family, $key, $value, $sharing = false); + * + * $item - item array or item id. If passed an array the iconfig meta information is + * added to the item structure (which will need to be saved with item_store eventually). + * If passed an id, the DB is updated, but may not be federated and/or cloned. + * $family - namespace of meta variable + * $key - key of meta variable + * $value - value of meta variable + * $sharing - boolean (default false); if true the meta information is propagated with the item + * to other sites/channels, mostly useful when $item is an array and has not yet been stored/delivered. + * If the meta information is added after delivery and you wish it to be shared, it may be necessary to + * alter the item edited timestamp and invoke the delivery process on the updated item. The edited + * timestamp needs to be altered in order to trigger an item_store_update() at the receiving end. + */ + + + static public function Set(&$item, $family, $key, $value, $sharing = false) { + + $dbvalue = ((is_array($value)) ? serialize($value) : $value); + $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + + $is_item = false; + $idx = null; + + if(is_array($item)) { + $is_item = true; + if((! array_key_exists('iconfig',$item)) || (! is_array($item['iconfig']))) + $item['iconfig'] = array(); + elseif($item['iconfig']) { + for($x = 0; $x < count($item['iconfig']); $x ++) { + if($item['iconfig'][$x]['cat'] == $family && $item['iconfig'][$x]['k'] == $key) { + $idx = $x; + } + } + } + $entry = array('cat' => $family, 'k' => $key, 'v' => $value, 'sharing' => $sharing); + + if(is_null($idx)) + $item['iconfig'][] = $entry; + else + $item['iconfig'][$idx] = $entry; + return $value; + } + + if(intval($item)) + $iid = intval($item); + + if(! $iid) + return false; + + if(self::Get($item, $family, $key) === false) { + $r = q("insert into iconfig( iid, cat, k, v, sharing ) values ( %d, '%s', '%s', '%s', %d ) ", + intval($iid), + dbesc($family), + dbesc($key), + dbesc($dbvalue), + intval($sharing) + ); + } + else { + $r = q("update iconfig set v = '%s', sharing = %d where iid = %d and cat = '%s' and k = '%s' ", + dbesc($dbvalue), + intval($sharing), + intval($iid), + dbesc($family), + dbesc($key) + ); + } + + if(! $r) + return false; + + return $value; + } + + + + static public function Delete(&$item, $family, $key) { + + + $is_item = false; + $idx = null; + + if(is_array($item)) { + $is_item = true; + if(is_array($item['iconfig'])) { + for($x = 0; $x < count($item['iconfig']); $x ++) { + if($item['iconfig'][$x]['cat'] == $family && $item['iconfig'][$x]['k'] == $key) { + unset($item['iconfig'][$x]); + } + } + } + return true; + } + + if(intval($item)) + $iid = intval($item); + + if(! $iid) + return false; + + return q("delete from iconfig where iid = %d and cat = '%s' and k = '%s' ", + intval($iid), + dbesc($family), + dbesc($key) + ); + + } + +}
\ No newline at end of file diff --git a/Zotlabs/Lib/PConfig.php b/Zotlabs/Lib/PConfig.php new file mode 100644 index 000000000..195321375 --- /dev/null +++ b/Zotlabs/Lib/PConfig.php @@ -0,0 +1,189 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + + +class PConfig { + + /** + * @brief Loads all configuration values of a channel into a cached storage. + * + * All configuration values of the given channel are stored in global cache + * which is available under the global variable App::$config[$uid]. + * + * @param string $uid + * The channel_id + * @return void|false Nothing or false if $uid is false + */ + + static public function Load($uid) { + if($uid === false) + return false; + + if(! array_key_exists($uid, \App::$config)) + \App::$config[$uid] = array(); + + $r = q("SELECT * FROM pconfig WHERE uid = %d", + intval($uid) + ); + + if($r) { + foreach($r as $rr) { + $k = $rr['k']; + $c = $rr['cat']; + if(! array_key_exists($c, \App::$config[$uid])) { + \App::$config[$uid][$c] = array(); + \App::$config[$uid][$c]['config_loaded'] = true; + } + \App::$config[$uid][$c][$k] = $rr['v']; + } + } + } + + /** + * @brief Get a particular channel's config variable given the category name + * ($family) and a key. + * + * Get a particular channel's config value from the given category ($family) + * and the $key from a cached storage in App::$config[$uid]. + * + * Returns false if not set. + * + * @param string $uid + * The channel_id + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to query + * @param boolean $instore (deprecated, without function) + * @return mixed Stored value or false if it does not exist + */ + + static public function Get($uid,$family,$key,$instore = false) { + + if($uid === false) + return false; + + if(! array_key_exists($uid, \App::$config)) + self::Load($uid); + + if((! array_key_exists($family, \App::$config[$uid])) || (! array_key_exists($key, \App::$config[$uid][$family]))) + return false; + + return ((! is_array(\App::$config[$uid][$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', \App::$config[$uid][$family][$key])) + ? unserialize(\App::$config[$uid][$family][$key]) + : \App::$config[$uid][$family][$key] + ); + + } + + /** + * @brief Sets a configuration value for a channel. + * + * Stores a config value ($value) in the category ($family) under the key ($key) + * for the channel_id $uid. + * + * @param string $uid + * The channel_id + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to set + * @param string $value + * The value to store + * @return mixed Stored $value or false + */ + + static public function Set($uid, $family, $key, $value) { + + // this catches subtle errors where this function has been called + // with local_channel() when not logged in (which returns false) + // and throws an error in array_key_exists below. + // we provide a function backtrace in the logs so that we can find + // and fix the calling function. + + if($uid === false) { + btlogger('UID is FALSE!', LOGGER_NORMAL, LOG_ERR); + return; + } + + // manage array value + $dbvalue = ((is_array($value)) ? serialize($value) : $value); + $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + + if(get_pconfig($uid, $family, $key) === false) { + if(! array_key_exists($uid, \App::$config)) + \App::$config[$uid] = array(); + if(! array_key_exists($family, \App::$config[$uid])) + \App::$config[$uid][$family] = array(); + + $ret = q("INSERT INTO pconfig ( uid, cat, k, v ) VALUES ( %d, '%s', '%s', '%s' ) ", + intval($uid), + dbesc($family), + dbesc($key), + dbesc($dbvalue) + ); + + } + else { + + $ret = q("UPDATE pconfig SET v = '%s' WHERE uid = %d and cat = '%s' AND k = '%s'", + dbesc($dbvalue), + intval($uid), + dbesc($family), + dbesc($key) + ); + + } + + // keep a separate copy for all variables which were + // set in the life of this page. We need this to + // synchronise channel clones. + + if(! array_key_exists('transient', \App::$config[$uid])) + \App::$config[$uid]['transient'] = array(); + if(! array_key_exists($family, \App::$config[$uid]['transient'])) + \App::$config[$uid]['transient'][$family] = array(); + + \App::$config[$uid][$family][$key] = $value; + \App::$config[$uid]['transient'][$family][$key] = $value; + + if($ret) + return $value; + + return $ret; + } + + + /** + * @brief Deletes the given key from the channel's configuration. + * + * Removes the configured value from the stored cache in App::$config[$uid] + * and removes it from the database. + * + * @param string $uid + * The channel_id + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to delete + * @return mixed + */ + + static public function Delete($uid, $family, $key) { + + $ret = false; + + if(array_key_exists($key, \App::$config[$uid][$family])) + unset(\App::$config[$uid][$family][$key]); + $ret = q("DELETE FROM pconfig WHERE uid = %d AND cat = '%s' AND k = '%s'", + intval($uid), + dbesc($family), + dbesc($key) + ); + + return $ret; + } + +} +
\ No newline at end of file diff --git a/Zotlabs/Lib/ProtoDriver.php b/Zotlabs/Lib/ProtoDriver.php new file mode 100644 index 000000000..daf887dbb --- /dev/null +++ b/Zotlabs/Lib/ProtoDriver.php @@ -0,0 +1,19 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + +/* + * Abstraction class for dealing with alternate networks (which of course do not exist, hence the abstraction) + */ + + +abstract class ProtoDriver { + abstract protected function discover($channel,$location); + abstract protected function deliver($item,$channel,$recipients); + abstract protected function collect($channel,$connection); + abstract protected function change_permissions($permissions,$channel,$recipient); + abstract protected function acknowledge_permissions($permissions,$channel,$recipient); + abstract protected function deliver_private($item,$channel,$recipients); + abstract protected function collect_private($channel,$connection); + +} diff --git a/Zotlabs/Lib/System.php b/Zotlabs/Lib/System.php new file mode 100644 index 000000000..c52a90338 --- /dev/null +++ b/Zotlabs/Lib/System.php @@ -0,0 +1,58 @@ +<?php + +namespace Zotlabs\Lib; + +class System { + + static public function get_platform_name() { + if(is_array(\App::$config) && is_array(\App::$config['system']) && array_key_exists('platform_name',\App::$config['system'])) + return \App::$config['system']['platform_name']; + return PLATFORM_NAME; + } + + static public function get_site_name() { + if(is_array(\App::$config) && is_array(\App::$config['system']) && \App::$config['system']['sitename']) + return \App::$config['system']['sitename']; + return ''; + } + + static public function get_project_version() { + if(is_array(\App::$config) && is_array(\App::$config['system']) && \App::$config['system']['hide_version']) + return ''; + return self::get_std_version(); + } + + static public function get_update_version() { + if(is_array(\App::$config) && is_array(\App::$config['system']) && \App::$config['system']['hide_version']) + return ''; + return DB_UPDATE_VERSION; + } + + + static public function get_notify_icon() { + if(is_array(\App::$config) && is_array(\App::$config['system']) && \App::$config['system']['email_notify_icon_url']) + return \App::$config['system']['email_notify_icon_url']; + return z_root() . '/images/hz-white-32.png'; + } + + static public function get_site_icon() { + if(is_array(\App::$config) && is_array(\App::$config['system']) && \App::$config['system']['site_icon_url']) + return \App::$config['system']['site_icon_url']; + return z_root() . '/images/hz-32.png'; + } + + + static public function get_server_role() { + if(UNO) + return 'basic'; + return 'pro'; + } + + static public function get_std_version() { + if(defined('STD_VERSION')) + return STD_VERSION; + return '0.0.0'; + } + + +} diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php new file mode 100644 index 000000000..f724ac95d --- /dev/null +++ b/Zotlabs/Lib/ThreadItem.php @@ -0,0 +1,780 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + +require_once('include/text.php'); + +/** + * A thread item + */ + +class ThreadItem { + + public $data = array(); + private $template = 'conv_item.tpl'; + private $comment_box_template = 'comment_item.tpl'; + private $commentable = false; + // list of supported reaction emojis - a site can over-ride this via config system.reactions + private $reactions = ['1f60a','1f44f','1f37e','1f48b','1f61e','2665','1f606','1f62e','1f634','1f61c','1f607','1f608']; + private $toplevel = false; + private $children = array(); + private $parent = null; + private $conversation = null; + private $redirect_url = null; + private $owner_url = ''; + private $owner_photo = ''; + private $owner_name = ''; + private $wall_to_wall = false; + private $threaded = false; + private $visiting = false; + private $channel = null; + private $display_mode = 'normal'; + + + public function __construct($data) { + + $this->data = $data; + $this->toplevel = ($this->get_id() == $this->get_data_value('parent')); + + // Prepare the children + if(count($data['children'])) { + foreach($data['children'] as $item) { + + /* + * Only add those that will be displayed + */ + + if((! visible_activity($item)) || array_key_exists('author_blocked',$item)) { + continue; + } + + $child = new ThreadItem($item); + $this->add_child($child); + } + } + + // allow a site to configure the order and content of the reaction emoji list + if($this->toplevel) { + $x = get_config('system','reactions'); + if($x && is_array($x) && count($x)) { + $this->reactions = $x; + } + } + } + + /** + * Get data in a form usable by a conversation template + * + * Returns: + * _ The data requested on success + * _ false on failure + */ + + public function get_template_data($conv_responses, $thread_level=1) { + + $result = array(); + + $item = $this->get_data(); + + $commentww = ''; + $sparkle = ''; + $buttons = ''; + $dropping = false; + $star = false; + $isstarred = "unstarred fa-star-o"; + $indent = ''; + $osparkle = ''; + $total_children = $this->count_descendants(); + $unseen_comments = (($item['real_uid']) ? 0 : $this->count_unseen_descendants()); + + $conv = $this->get_conversation(); + $observer = $conv->get_observer(); + + $lock = ((($item['item_private'] == 1) || (($item['uid'] == local_channel()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) + || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) + ? t('Private Message') + : false); + $shareable = ((($conv->get_profile_owner() == local_channel() && local_channel()) && ($item['item_private'] != 1)) ? true : false); + + // allow an exemption for sharing stuff from your private feeds + if($item['author']['xchan_network'] === 'rss') + $shareable = true; + + $mode = $conv->get_mode(); + + if(local_channel() && $observer['xchan_hash'] === $item['author_xchan']) + $edpost = array(z_root()."/editpost/".$item['id'], t("Edit")); + else + $edpost = false; + + + if($observer['xchan_hash'] == $this->get_data_value('author_xchan') + || $observer['xchan_hash'] == $this->get_data_value('owner_xchan') + || $this->get_data_value('uid') == local_channel()) + $dropping = true; + + + if(array_key_exists('real_uid',$item)) { + $edpost = false; + $dropping = false; + } + + + if($dropping) { + $drop = array( + 'dropping' => $dropping, + 'delete' => t('Delete'), + ); + } +// FIXME + if($observer_is_pageowner) { + $multidrop = array( + 'select' => t('Select'), + ); + } + + $filer = ((($conv->get_profile_owner() == local_channel()) && (! array_key_exists('real_uid',$item))) ? t("Save to Folder") : false); + + $profile_avatar = $item['author']['xchan_photo_m']; + $profile_link = chanlink_url($item['author']['xchan_url']); + $profile_name = $item['author']['xchan_name']; + + $location = format_location($item); + $isevent = false; + $attend = null; + $canvote = false; + + // process action responses - e.g. like/dislike/attend/agree/whatever + $response_verbs = array('like'); + if(feature_enabled($conv->get_profile_owner(),'dislike')) + $response_verbs[] = 'dislike'; + if($item['obj_type'] === ACTIVITY_OBJ_EVENT) { + $response_verbs[] = 'attendyes'; + $response_verbs[] = 'attendno'; + $response_verbs[] = 'attendmaybe'; + if($this->is_commentable()) { + $isevent = true; + $attend = array( t('I will attend'), t('I will not attend'), t('I might attend')); + } + } + + $consensus = (intval($item['item_consensus']) ? true : false); + if($consensus) { + $response_verbs[] = 'agree'; + $response_verbs[] = 'disagree'; + $response_verbs[] = 'abstain'; + if($this->is_commentable()) { + $conlabels = array( t('I agree'), t('I disagree'), t('I abstain')); + $canvote = true; + } + } + + if(! feature_enabled($conv->get_profile_owner(),'dislike')) + unset($conv_responses['dislike']); + + $responses = get_responses($conv_responses,$response_verbs,$this,$item); + + $like_count = ((x($conv_responses['like'],$item['mid'])) ? $conv_responses['like'][$item['mid']] : ''); + $like_list = ((x($conv_responses['like'],$item['mid'])) ? $conv_responses['like'][$item['mid'] . '-l'] : ''); + if (count($like_list) > MAX_LIKERS) { + $like_list_part = array_slice($like_list, 0, MAX_LIKERS); + array_push($like_list_part, '<a href="#" data-toggle="modal" data-target="#likeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>'); + } else { + $like_list_part = ''; + } + $like_button_label = tt('Like','Likes',$like_count,'noun'); + + if (feature_enabled($conv->get_profile_owner(),'dislike')) { + $dislike_count = ((x($conv_responses['dislike'],$item['mid'])) ? $conv_responses['dislike'][$item['mid']] : ''); + $dislike_list = ((x($conv_responses['dislike'],$item['mid'])) ? $conv_responses['dislike'][$item['mid'] . '-l'] : ''); + $dislike_button_label = tt('Dislike','Dislikes',$dislike_count,'noun'); + if (count($dislike_list) > MAX_LIKERS) { + $dislike_list_part = array_slice($dislike_list, 0, MAX_LIKERS); + array_push($dislike_list_part, '<a href="#" data-toggle="modal" data-target="#dislikeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>'); + } else { + $dislike_list_part = ''; + } + } + + $showlike = ((x($conv_responses['like'],$item['mid'])) ? format_like($conv_responses['like'][$item['mid']],$conv_responses['like'][$item['mid'] . '-l'],'like',$item['mid']) : ''); + $showdislike = ((x($conv_responses['dislike'],$item['mid']) && feature_enabled($conv->get_profile_owner(),'dislike')) + ? format_like($conv_responses['dislike'][$item['mid']],$conv_responses['dislike'][$item['mid'] . '-l'],'dislike',$item['mid']) : ''); + + /* + * We should avoid doing this all the time, but it depends on the conversation mode + * And the conv mode may change when we change the conv, or it changes its mode + * Maybe we should establish a way to be notified about conversation changes + */ + + $this->check_wall_to_wall(); + + if($this->is_toplevel()) { + // FIXME check this permission + if(($conv->get_profile_owner() == local_channel()) && (! array_key_exists('real_uid',$item))) { + +// FIXME we don't need all this stuff, some can be done in the template + + $star = array( + 'do' => t("Add Star"), + 'undo' => t("Remove Star"), + 'toggle' => t("Toggle Star Status"), + 'classdo' => (intval($item['item_starred']) ? "hidden" : ""), + 'classundo' => (intval($item['item_starred']) ? "" : "hidden"), + 'isstarred' => (intval($item['item_starred']) ? "starred fa-star" : "unstarred fa-star-o"), + 'starred' => t('starred'), + ); + + } + } + else { + $indent = 'comment'; + } + + + $verified = (intval($item['item_verified']) ? t('Message signature validated') : ''); + $forged = ((($item['sig']) && (! intval($item['item_verified']))) ? t('Message signature incorrect') : ''); + $unverified = '' ; // (($this->is_wall_to_wall() && (! intval($item['item_verified']))) ? t('Message cannot be verified') : ''); + + + + // FIXME - check this permission + if($conv->get_profile_owner() == local_channel()) { + $tagger = array( + 'tagit' => t("Add Tag"), + 'classtagger' => "", + ); + } + + $has_bookmarks = false; + if(is_array($item['term'])) { + foreach($item['term'] as $t) { + if(!UNO && $t['type'] == TERM_BOOKMARK) + $has_bookmarks = true; + } + } + + $has_event = false; + if(($item['obj_type'] === ACTIVITY_OBJ_EVENT) && $conv->get_profile_owner() == local_channel()) + $has_event = true; + + if($this->is_commentable()) { + $like = array( t("I like this \x28toggle\x29"), t("like")); + $dislike = array( t("I don't like this \x28toggle\x29"), t("dislike")); + } + + if ($shareable) + $share = array( t('Share This'), t('share')); + + $dreport = ''; + + $keep_reports = intval(get_config('system','expire_delivery_reports')); + if($keep_reports === 0) + $keep_reports = 30; + + if((! get_config('system','disable_dreport')) && strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC',"now - $keep_reports days")) > 0) + $dreport = t('Delivery Report'); + + if(strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC','now - 12 hours')) > 0) + $indent .= ' shiny'; + + + localize_item($item); + + $body = prepare_body($item,true); + + // $viewthread (below) is only valid in list mode. If this is a channel page, build the thread viewing link + // since we can't depend on llink or plink pointing to the right local location. + + $owner_address = substr($item['owner']['xchan_addr'],0,strpos($item['owner']['xchan_addr'],'@')); + $viewthread = $item['llink']; + if($conv->get_mode() === 'channel') + $viewthread = z_root() . '/channel/' . $owner_address . '?f=&mid=' . $item['mid']; + + $comment_count_txt = sprintf( tt('%d comment','%d comments',$total_children),$total_children ); + $list_unseen_txt = (($unseen_comments) ? sprintf('%d unseen',$unseen_comments) : ''); + + + + + + $children = $this->get_children(); + + $has_tags = (($body['tags'] || $body['categories'] || $body['mentions'] || $body['attachments'] || $body['folders']) ? true : false); + + $tmp_item = array( + 'template' => $this->get_template(), + 'mode' => $mode, + 'type' => implode("",array_slice(explode("/",$item['verb']),-1)), + 'body' => $body['html'], + 'tags' => $body['tags'], + 'categories' => $body['categories'], + 'mentions' => $body['mentions'], + 'attachments' => $body['attachments'], + 'folders' => $body['folders'], + 'text' => strip_tags($body['html']), + 'id' => $this->get_id(), + 'mid' => $item['mid'], + 'isevent' => $isevent, + 'attend' => $attend, + 'consensus' => $consensus, + 'conlabels' => $conlabels, + 'canvote' => $canvote, + 'linktitle' => sprintf( t('View %s\'s profile - %s'), $profile_name, $item['author']['xchan_addr']), + 'olinktitle' => sprintf( t('View %s\'s profile - %s'), $this->get_owner_name(), $item['owner']['xchan_addr']), + 'llink' => $item['llink'], + 'viewthread' => $viewthread, + 'to' => t('to'), + 'via' => t('via'), + 'wall' => t('Wall-to-Wall'), + 'vwall' => t('via Wall-To-Wall:'), + 'profile_url' => $profile_link, + 'item_photo_menu' => item_photo_menu($item), + 'dreport' => $dreport, + 'name' => $profile_name, + 'thumb' => $profile_avatar, + 'osparkle' => $osparkle, + 'sparkle' => $sparkle, + 'title' => $item['title'], + 'title_tosource' => get_pconfig($conv->get_profile_owner(),'system','title_tosource'), + 'ago' => relative_date($item['created']), + 'app' => $item['app'], + 'str_app' => sprintf( t('from %s'), $item['app']), + 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), + 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), + 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''), + 'expiretime' => (($item['expires'] !== NULL_DATE) ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''), + 'lock' => $lock, + 'verified' => $verified, + 'unverified' => $unverified, + 'forged' => $forged, + 'location' => $location, + 'indent' => $indent, + 'owner_url' => $this->get_owner_url(), + 'owner_photo' => $this->get_owner_photo(), + 'owner_name' => $this->get_owner_name(), + 'photo' => $body['photo'], + 'event' => $body['event'], + 'has_tags' => $has_tags, + 'reactions' => $this->reactions, +// Item toolbar buttons + 'emojis' => (($this->is_toplevel() && $this->is_commentable() && feature_enabled($conv->get_profile_owner(),'emojis')) ? '1' : ''), + 'like' => $like, + 'dislike' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike : ''), + 'share' => $share, + 'rawmid' => $item['mid'], + 'plink' => get_plink($item), + 'edpost' => $edpost, // ((feature_enabled($conv->get_profile_owner(),'edit_posts')) ? $edpost : ''), + 'star' => ((feature_enabled($conv->get_profile_owner(),'star_posts')) ? $star : ''), + 'tagger' => ((feature_enabled($conv->get_profile_owner(),'commtag')) ? $tagger : ''), + 'filer' => ((feature_enabled($conv->get_profile_owner(),'filing')) ? $filer : ''), + 'bookmark' => (($conv->get_profile_owner() == local_channel() && local_channel() && $has_bookmarks) ? t('Save Bookmarks') : ''), + 'addtocal' => (($has_event) ? t('Add to Calendar') : ''), + 'drop' => $drop, + 'multidrop' => ((feature_enabled($conv->get_profile_owner(),'multi_delete')) ? $multidrop : ''), +// end toolbar buttons + + 'unseen_comments' => $unseen_comments, + 'comment_count' => $total_children, + 'comment_count_txt' => $comment_count_txt, + 'list_unseen_txt' => $list_unseen_txt, + 'markseen' => t('Mark all seen'), + 'responses' => $responses, + 'like_count' => $like_count, + 'like_list' => $like_list, + 'like_list_part' => $like_list_part, + 'like_button_label' => $like_button_label, + 'like_modal_title' => t('Likes','noun'), + 'dislike_modal_title' => t('Dislikes','noun'), + 'dislike_count' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_count : ''), + 'dislike_list' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_list : ''), + 'dislike_list_part' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_list_part : ''), + 'dislike_button_label' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_button_label : ''), + 'modal_dismiss' => t('Close'), + 'showlike' => $showlike, + 'showdislike' => $showdislike, + 'comment' => $this->get_comment_box($indent), + 'previewing' => ($conv->is_preview() ? ' preview ' : ''), + 'wait' => t('Please wait'), + 'thread_level' => $thread_level + ); + + $arr = array('item' => $item, 'output' => $tmp_item); + call_hooks('display_item', $arr); + + $result = $arr['output']; + + $result['children'] = array(); + $nb_children = count($children); + + $visible_comments = get_config('system','expanded_comments'); + if($visible_comments === false) + $visible_comments = 3; + + if(($this->get_display_mode() === 'normal') && ($nb_children > 0)) { + foreach($children as $child) { + $result['children'][] = $child->get_template_data($conv_responses, $thread_level + 1); + } + // Collapse + if(($nb_children > $visible_comments) || ($thread_level > 1)) { + $result['children'][0]['comment_firstcollapsed'] = true; + $result['children'][0]['num_comments'] = $comment_count_txt; + $result['children'][0]['hide_text'] = t('[+] show all'); + if($thread_level > 1) { + $result['children'][$nb_children - 1]['comment_lastcollapsed'] = true; + } + else { + $result['children'][$nb_children - ($visible_comments + 1)]['comment_lastcollapsed'] = true; + } + } + } + + $result['private'] = $item['item_private']; + $result['toplevel'] = ($this->is_toplevel() ? 'toplevel_item' : ''); + + if($this->is_threaded()) { + $result['flatten'] = false; + $result['threaded'] = true; + } + else { + $result['flatten'] = true; + $result['threaded'] = false; + } + + return $result; + } + + public function get_id() { + return $this->get_data_value('id'); + } + + public function get_display_mode() { + return $this->display_mode; + } + + public function set_display_mode($mode) { + $this->display_mode = $mode; + } + + public function is_threaded() { + return $this->threaded; + } + + public function set_commentable($val) { + $this->commentable = $val; + foreach($this->get_children() as $child) + $child->set_commentable($val); + } + + public function is_commentable() { + return $this->commentable; + } + + /** + * Add a child item + */ + public function add_child($item) { + $item_id = $item->get_id(); + if(!$item_id) { + logger('[ERROR] Item::add_child : Item has no ID!!', LOGGER_DEBUG); + return false; + } + if($this->get_child($item->get_id())) { + logger('[WARN] Item::add_child : Item already exists ('. $item->get_id() .').', LOGGER_DEBUG); + return false; + } + /* + * Only add what will be displayed + */ + + if(activity_match($item->get_data_value('verb'),ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'),ACTIVITY_DISLIKE)) { + return false; + } + + $item->set_parent($this); + $this->children[] = $item; + return end($this->children); + } + + /** + * Get a child by its ID + */ + public function get_child($id) { + foreach($this->get_children() as $child) { + if($child->get_id() == $id) + return $child; + } + return null; + } + + /** + * Get all our children + */ + public function get_children() { + return $this->children; + } + + /** + * Set our parent + */ + protected function set_parent($item) { + $parent = $this->get_parent(); + if($parent) { + $parent->remove_child($this); + } + $this->parent = $item; + $this->set_conversation($item->get_conversation()); + } + + /** + * Remove our parent + */ + protected function remove_parent() { + $this->parent = null; + $this->conversation = null; + } + + /** + * Remove a child + */ + public function remove_child($item) { + $id = $item->get_id(); + foreach($this->get_children() as $key => $child) { + if($child->get_id() == $id) { + $child->remove_parent(); + unset($this->children[$key]); + // Reindex the array, in order to make sure there won't be any trouble on loops using count() + $this->children = array_values($this->children); + return true; + } + } + logger('[WARN] Item::remove_child : Item is not a child ('. $id .').', LOGGER_DEBUG); + return false; + } + + /** + * Get parent item + */ + protected function get_parent() { + return $this->parent; + } + + /** + * set conversation + */ + public function set_conversation($conv) { + $previous_mode = ($this->conversation ? $this->conversation->get_mode() : ''); + + $this->conversation = $conv; + + // Set it on our children too + foreach($this->get_children() as $child) + $child->set_conversation($conv); + } + + /** + * get conversation + */ + public function get_conversation() { + return $this->conversation; + } + + /** + * Get raw data + * + * We shouldn't need this + */ + public function get_data() { + return $this->data; + } + + /** + * Get a data value + * + * Returns: + * _ value on success + * _ false on failure + */ + public function get_data_value($name) { + if(!isset($this->data[$name])) { +// logger('[ERROR] Item::get_data_value : Item has no value name "'. $name .'".', LOGGER_DEBUG); + return false; + } + + return $this->data[$name]; + } + + /** + * Get template + */ + public function get_template() { + return $this->template; + } + + + public function set_template($t) { + $this->template = $t; + } + + /** + * Check if this is a toplevel post + */ + private function is_toplevel() { + return $this->toplevel; + } + + /** + * Count the total of our descendants + */ + private function count_descendants() { + $children = $this->get_children(); + $total = count($children); + if($total > 0) { + foreach($children as $child) { + $total += $child->count_descendants(); + } + } + return $total; + } + + private function count_unseen_descendants() { + $children = $this->get_children(); + $total = count($children); + if($total > 0) { + $total = 0; + foreach($children as $child) { + if((! visible_activity($child->data)) || array_key_exists('author_blocked',$child->data)) { + continue; + } + if(intval($child->data['item_unseen'])) + $total ++; + } + } + return $total; + } + + + /** + * Get the template for the comment box + */ + private function get_comment_box_template() { + return $this->comment_box_template; + } + + /** + * Get the comment box + * + * Returns: + * _ The comment box string (empty if no comment box) + * _ false on failure + */ + private function get_comment_box($indent) { + + if(!$this->is_toplevel() && !get_config('system','thread_allow')) { + return ''; + } + + $comment_box = ''; + $conv = $this->get_conversation(); + +// logger('Commentable conv: ' . $conv->is_commentable()); + + if(! $this->is_commentable()) + return; + + $template = get_markup_template($this->get_comment_box_template()); + + $observer = $conv->get_observer(); + + $qc = ((local_channel()) ? get_pconfig(local_channel(),'system','qcomment') : null); + $qcomment = (($qc) ? explode("\n",$qc) : null); + + $arr = array('comment_buttons' => '','id' => $this->get_id()); + call_hooks('comment_buttons',$arr); + $comment_buttons = $arr['comment_buttons']; + + + $comment_box = replace_macros($template,array( + '$return_path' => '', + '$threaded' => $this->is_threaded(), + '$jsreload' => '', //(($conv->get_mode() === 'display') ? $_SESSION['return_url'] : ''), + '$type' => (($conv->get_mode() === 'channel') ? 'wall-comment' : 'net-comment'), + '$id' => $this->get_id(), + '$parent' => $this->get_id(), + '$qcomment' => $qcomment, + '$comment_buttons' => $comment_buttons, + '$profile_uid' => $conv->get_profile_owner(), + '$mylink' => $observer['xchan_url'], + '$mytitle' => t('This is you'), + '$myphoto' => $observer['xchan_photo_s'], + '$comment' => t('Comment'), + '$submit' => t('Submit'), + '$edbold' => t('Bold'), + '$editalic' => t('Italic'), + '$eduline' => t('Underline'), + '$edquote' => t('Quote'), + '$edcode' => t('Code'), + '$edimg' => t('Image'), + '$edurl' => t('Insert Link'), + '$edvideo' => t('Video'), + '$preview' => t('Preview'), // ((feature_enabled($conv->get_profile_owner(),'preview')) ? t('Preview') : ''), + '$indent' => $indent, + '$feature_encrypt' => ((feature_enabled($conv->get_profile_owner(),'content_encrypt')) ? true : false), + '$encrypt' => t('Encrypt text'), + '$cipher' => $conv->get_cipher(), + '$sourceapp' => \App::$sourcename + + )); + + return $comment_box; + } + + private function get_redirect_url() { + return $this->redirect_url; + } + + /** + * Check if we are a wall to wall item and set the relevant properties + */ + protected function check_wall_to_wall() { + $conv = $this->get_conversation(); + $this->wall_to_wall = false; + $this->owner_url = ''; + $this->owner_photo = ''; + $this->owner_name = ''; + + if($conv->get_mode() === 'channel') + return; + + if($this->is_toplevel() && ($this->get_data_value('author_xchan') != $this->get_data_value('owner_xchan'))) { + $this->owner_url = chanlink_url($this->data['owner']['xchan_url']); + $this->owner_photo = $this->data['owner']['xchan_photo_m']; + $this->owner_name = $this->data['owner']['xchan_name']; + $this->wall_to_wall = true; + } + } + + private function is_wall_to_wall() { + return $this->wall_to_wall; + } + + private function get_owner_url() { + return $this->owner_url; + } + + private function get_owner_photo() { + return $this->owner_photo; + } + + private function get_owner_name() { + return $this->owner_name; + } + + private function is_visiting() { + return $this->visiting; + } + + + + +} + diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php new file mode 100644 index 000000000..a6d4f8517 --- /dev/null +++ b/Zotlabs/Lib/ThreadStream.php @@ -0,0 +1,220 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + +require_once('boot.php'); +require_once('include/text.php'); +require_once('include/items.php'); + +/** + * A list of threads + * + */ + +class ThreadStream { + + private $threads = array(); + private $mode = null; + private $observer = null; + private $writable = false; + private $commentable = false; + private $profile_owner = 0; + private $preview = false; + private $prepared_item = ''; + private $cipher = 'aes256'; + + // $prepared_item is for use by alternate conversation structures such as photos + // wherein we've already prepared a top level item which doesn't look anything like + // a normal "post" item + + public function __construct($mode, $preview, $prepared_item = '') { + $this->set_mode($mode); + $this->preview = $preview; + $this->prepared_item = $prepared_item; + $c = ((local_channel()) ? get_pconfig(local_channel(),'system','default_cipher') : ''); + if($c) + $this->cipher = $c; + } + + /** + * Set the mode we'll be displayed on + */ + private function set_mode($mode) { + if($this->get_mode() == $mode) + return; + + $this->observer = \App::get_observer(); + $ob_hash = (($this->observer) ? $this->observer['xchan_hash'] : ''); + + switch($mode) { + case 'network': + $this->profile_owner = local_channel(); + $this->writable = true; + break; + case 'channel': + $this->profile_owner = \App::$profile['profile_uid']; + $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); + break; + case 'display': + // in this mode we set profile_owner after initialisation (from conversation()) and then + // pull some trickery which allows us to re-invoke this function afterward + // it's an ugly hack so FIXME + $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); + break; + case 'page': + $this->profile_owner = \App::$profile['uid']; + $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); + break; + default: + logger('[ERROR] Conversation::set_mode : Unhandled mode ('. $mode .').', LOGGER_DEBUG); + return false; + break; + } + $this->mode = $mode; + } + + /** + * Get mode + */ + public function get_mode() { + return $this->mode; + } + + /** + * Check if page is writable + */ + public function is_writable() { + return $this->writable; + } + + public function is_commentable() { + return $this->commentable; + } + + /** + * Check if page is a preview + */ + public function is_preview() { + return $this->preview; + } + + /** + * Get profile owner + */ + public function get_profile_owner() { + return $this->profile_owner; + } + + public function set_profile_owner($uid) { + $this->profile_owner = $uid; + $mode = $this->get_mode(); + $this->mode = null; + $this->set_mode($mode); + } + + public function get_observer() { + return $this->observer; + } + + public function get_cipher() { + return $this->cipher; + } + + + /** + * Add a thread to the conversation + * + * Returns: + * _ The inserted item on success + * _ false on failure + */ + public function add_thread($item) { + $item_id = $item->get_id(); + if(!$item_id) { + logger('Item has no ID!!', LOGGER_DEBUG, LOG_ERR); + return false; + } + if($this->get_thread($item->get_id())) { + logger('Thread already exists ('. $item->get_id() .').', LOGGER_DEBUG, LOG_WARNING); + return false; + } + + /* + * Only add things that will be displayed + */ + + + if(($item->get_data_value('id') != $item->get_data_value('parent')) && (activity_match($item->get_data_value('verb'),ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'),ACTIVITY_DISLIKE))) { + return false; + } + + $item->set_commentable(false); + $ob_hash = (($this->observer) ? $this->observer['xchan_hash'] : ''); + + if(! comments_are_now_closed($item->get_data())) { + if(($item->get_data_value('author_xchan') === $ob_hash) || ($item->get_data_value('owner_xchan') === $ob_hash)) + $item->set_commentable(true); + + if(intval($item->get_data_value('item_nocomment'))) { + $item->set_commentable(false); + } + elseif(($this->observer) && (! $item->is_commentable())) { + if((array_key_exists('owner',$item->data)) && intval($item->data['owner']['abook_self'])) + $item->set_commentable(perm_is_allowed($this->profile_owner,$this->observer['xchan_hash'],'post_comments')); + else + $item->set_commentable(can_comment_on_post($this->observer['xchan_hash'],$item->data)); + } + } + require_once('include/channel.php'); + + $item->set_conversation($this); + $this->threads[] = $item; + return end($this->threads); + } + + /** + * Get data in a form usable by a conversation template + * + * We should find a way to avoid using those arguments (at least most of them) + * + * Returns: + * _ The data requested on success + * _ false on failure + */ + public function get_template_data($conv_responses) { + $result = array(); + + foreach($this->threads as $item) { + + if(($item->get_data_value('id') == $item->get_data_value('parent')) && $this->prepared_item) { + $item_data = $this->prepared_item; + } + else { + $item_data = $item->get_template_data($conv_responses); + } + if(!$item_data) { + logger('Failed to get item template data ('. $item->get_id() .').', LOGGER_DEBUG, LOG_ERR); + return false; + } + $result[] = $item_data; + } + + return $result; + } + + /** + * Get a thread based on its item id + * + * Returns: + * _ The found item on success + * _ false on failure + */ + private function get_thread($id) { + foreach($this->threads as $item) { + if($item->get_id() == $id) + return $item; + } + + return false; + } +} diff --git a/Zotlabs/Lib/XConfig.php b/Zotlabs/Lib/XConfig.php new file mode 100644 index 000000000..e28dcf559 --- /dev/null +++ b/Zotlabs/Lib/XConfig.php @@ -0,0 +1,160 @@ +<?php + +namespace Zotlabs\Lib; + + +class XConfig { + + /** + * @brief Loads a full xchan's configuration into a cached storage. + * + * All configuration values of the given observer hash are stored in global + * cache which is available under the global variable App::$config[$xchan]. + * + * @param string $xchan + * The observer's hash + * @return void|false Returns false if xchan is not set + */ + + static public function Load($xchan) { + + if(! $xchan) + return false; + + if(! array_key_exists($xchan, \App::$config)) + \App::$config[$xchan] = array(); + + $r = q("SELECT * FROM xconfig WHERE xchan = '%s'", + dbesc($xchan) + ); + + if($r) { + foreach($r as $rr) { + $k = $rr['k']; + $c = $rr['cat']; + if(! array_key_exists($c, \App::$config[$xchan])) { + \App::$config[$xchan][$c] = array(); + \App::$config[$xchan][$c]['config_loaded'] = true; + } + \App::$config[$xchan][$c][$k] = $rr['v']; + } + } + } + + /** + * @brief Get a particular observer's config variable given the category + * name ($family) and a key. + * + * Get a particular observer's config value from the given category ($family) + * and the $key from a cached storage in App::$config[$xchan]. + * + * Returns false if not set. + * + * @param string $xchan + * The observer's hash + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to query + * @return mixed Stored $value or false if it does not exist + */ + + static public function Get($xchan, $family, $key) { + + if(! $xchan) + return false; + + if(! array_key_exists($xchan, \App::$config)) + load_xconfig($xchan); + + if((! array_key_exists($family, \App::$config[$xchan])) || (! array_key_exists($key, \App::$config[$xchan][$family]))) + return false; + + return ((! is_array(\App::$config[$xchan][$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', \App::$config[$xchan][$family][$key])) + ? unserialize(\App::$config[$xchan][$family][$key]) + : \App::$config[$xchan][$family][$key] + ); + } + + /** + * @brief Sets a configuration value for an observer. + * + * Stores a config value ($value) in the category ($family) under the key ($key) + * for the observer's $xchan hash. + * + * + * @param string $xchan + * The observer's hash + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to set + * @param string $value + * The value to store + * @return mixed Stored $value or false + */ + + static public function Set($xchan, $family, $key, $value) { + + // manage array value + $dbvalue = ((is_array($value)) ? serialize($value) : $value); + $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + + if(self::Get($xchan, $family, $key) === false) { + if(! array_key_exists($xchan, \App::$config)) + \App::$config[$xchan] = array(); + if(! array_key_exists($family, \App::$config[$xchan])) + \App::$config[$xchan][$family] = array(); + + $ret = q("INSERT INTO xconfig ( xchan, cat, k, v ) VALUES ( '%s', '%s', '%s', '%s' ) ", + dbesc($xchan), + dbesc($family), + dbesc($key), + dbesc($dbvalue) + ); + } + else { + $ret = q("UPDATE xconfig SET v = '%s' WHERE xchan = '%s' and cat = '%s' AND k = '%s'", + dbesc($dbvalue), + dbesc($xchan), + dbesc($family), + dbesc($key) + ); + } + + App::$config[$xchan][$family][$key] = $value; + + if($ret) + return $value; + return $ret; + } + + /** + * @brief Deletes the given key from the observer's config. + * + * Removes the configured value from the stored cache in App::$config[$xchan] + * and removes it from the database. + * + * @param string $xchan + * The observer's hash + * @param string $family + * The category of the configuration value + * @param string $key + * The configuration key to delete + * @return mixed + */ + + static public function Delete($xchan, $family, $key) { + + if(x(\App::$config[$xchan][$family], $key)) + unset(\App::$config[$xchan][$family][$key]); + $ret = q("DELETE FROM xconfig WHERE xchan = '%s' AND cat = '%s' AND k = '%s'", + dbesc($xchan), + dbesc($family), + dbesc($key) + ); + + return $ret; + } + +}
\ No newline at end of file diff --git a/Zotlabs/Lib/ZotDriver.php b/Zotlabs/Lib/ZotDriver.php new file mode 100644 index 000000000..e14cc7f35 --- /dev/null +++ b/Zotlabs/Lib/ZotDriver.php @@ -0,0 +1,30 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + + +class ZotDriver extends ProtoDriver { + + protected function discover($channel,$location) { + + } + protected function deliver($item,$channel,$recipients) { + + } + protected function collect($channel,$connection) { + + } + protected function change_permissions($permissions,$channel,$recipient) { + + } + protected function acknowledge_permissions($permissions,$channel,$recipient) { + + } + protected function deliver_private($item,$channel,$recipients) { + + } + protected function collect_private($channel,$connection) { + + } + +} |